43
51
# set_path_id setting id when state is in memory unmodified
44
52
# set_path_id setting id when state is in memory modified
47
def load_tests(basic_tests, module, loader):
48
suite = loader.suiteClass()
49
dir_reader_tests, remaining_tests = tests.split_suite_by_condition(
50
basic_tests, tests.condition_isinstance(TestCaseWithDirState))
51
tests.multiply_tests(dir_reader_tests,
52
test_osutils.dir_reader_scenarios(), suite)
53
suite.addTest(remaining_tests)
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
58
"""Helper functions for creating DirState objects with various content."""
61
_dir_reader_class = None
62
_native_to_unicode = None # Not used yet
65
tests.TestCaseWithTransport.setUp(self)
67
self.overrideAttr(osutils,
68
'_selected_dir_reader', self._dir_reader_class())
70
def create_empty_dirstate(self):
71
"""Return a locked but empty dirstate"""
72
state = dirstate.DirState.initialize('dirstate')
75
def create_dirstate_with_root(self):
76
"""Return a write-locked state with a single root entry."""
77
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
78
root_entry_direntry = ('', '', 'a-root-value'), [
79
('d', '', 0, False, packed_stat),
82
dirblocks.append(('', [root_entry_direntry]))
83
dirblocks.append(('', []))
84
state = self.create_empty_dirstate()
86
state._set_data([], dirblocks)
93
def create_dirstate_with_root_and_subdir(self):
94
"""Return a locked DirState with a root and a subdir"""
95
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
96
subdir_entry = ('', 'subdir', 'subdir-id'), [
97
('d', '', 0, False, packed_stat),
99
state = self.create_dirstate_with_root()
101
dirblocks = list(state._dirblocks)
102
dirblocks[1][1].append(subdir_entry)
103
state._set_data([], dirblocks)
109
def create_complex_dirstate(self):
110
"""This dirstate contains multiple files and directories.
120
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
122
Notice that a/e is an empty directory.
124
:return: The dirstate, still write-locked.
126
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
127
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
128
root_entry = ('', '', 'a-root-value'), [
129
('d', '', 0, False, packed_stat),
131
a_entry = ('', 'a', 'a-dir'), [
132
('d', '', 0, False, packed_stat),
134
b_entry = ('', 'b', 'b-dir'), [
135
('d', '', 0, False, packed_stat),
137
c_entry = ('', 'c', 'c-file'), [
138
('f', null_sha, 10, False, packed_stat),
140
d_entry = ('', 'd', 'd-file'), [
141
('f', null_sha, 20, False, packed_stat),
143
e_entry = ('a', 'e', 'e-dir'), [
144
('d', '', 0, False, packed_stat),
146
f_entry = ('a', 'f', 'f-file'), [
147
('f', null_sha, 30, False, packed_stat),
149
g_entry = ('b', 'g', 'g-file'), [
150
('f', null_sha, 30, False, packed_stat),
152
h_entry = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file'), [
153
('f', null_sha, 40, False, packed_stat),
156
dirblocks.append(('', [root_entry]))
157
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
158
dirblocks.append(('a', [e_entry, f_entry]))
159
dirblocks.append(('b', [g_entry, h_entry]))
160
state = dirstate.DirState.initialize('dirstate')
163
state._set_data([], dirblocks)
169
def check_state_with_reopen(self, expected_result, state):
170
"""Check that state has current state expected_result.
172
This will check the current state, open the file anew and check it
174
This function expects the current state to be locked for writing, and
175
will unlock it before re-opening.
176
This is required because we can't open a lock_read() while something
177
else has a lock_write().
178
write => mutually exclusive lock
181
# The state should already be write locked, since we just had to do
182
# some operation to get here.
183
self.assertTrue(state._lock_token is not None)
185
self.assertEqual(expected_result[0], state.get_parent_ids())
54
class TestTreeToDirstate(TestCaseWithTransport):
56
def test_empty_to_dirstate(self):
57
"""We should be able to create a dirstate for an empty tree."""
58
# There are no files on disk and no parents
59
tree = self.make_branch_and_tree('tree')
60
state = dirstate.DirState.from_tree(tree, 'dirstate')
62
# an inner function because there is no parameterisation at this point
63
# if we make it reusable that would be a good thing.
64
self.assertEqual([], state.get_parent_ids())
186
65
# there should be no ghosts in this tree.
187
66
self.assertEqual([], state.get_ghosts())
188
67
# there should be one fileid in this tree - the root of the tree.
189
self.assertEqual(expected_result[1], list(state._iter_entries()))
194
state = dirstate.DirState.on_file('dirstate')
197
self.assertEqual(expected_result[1], list(state._iter_entries()))
201
def create_basic_dirstate(self):
202
"""Create a dirstate with a few files and directories.
212
tree = self.make_branch_and_tree('tree')
213
paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
214
file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
215
self.build_tree(['tree/' + p for p in paths])
216
tree.set_root_id('TREE_ROOT')
217
tree.add([p.rstrip('/') for p in paths], file_ids)
218
tree.commit('initial', rev_id='rev-1')
219
revision_id = 'rev-1'
220
# a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
221
t = self.get_transport('tree')
222
a_text = t.get_bytes('a')
223
a_sha = osutils.sha_string(a_text)
225
# b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
226
# c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
227
c_text = t.get_bytes('b/c')
228
c_sha = osutils.sha_string(c_text)
230
# d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
231
# e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
232
e_text = t.get_bytes('b/d/e')
233
e_sha = osutils.sha_string(e_text)
235
b_c_text = t.get_bytes('b-c')
236
b_c_sha = osutils.sha_string(b_c_text)
237
b_c_len = len(b_c_text)
238
# f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
239
f_text = t.get_bytes('f')
240
f_sha = osutils.sha_string(f_text)
242
null_stat = dirstate.DirState.NULLSTAT
244
'':(('', '', 'TREE_ROOT'), [
245
('d', '', 0, False, null_stat),
246
('d', '', 0, False, revision_id),
248
'a':(('', 'a', 'a-id'), [
249
('f', '', 0, False, null_stat),
250
('f', a_sha, a_len, False, revision_id),
252
'b':(('', 'b', 'b-id'), [
253
('d', '', 0, False, null_stat),
254
('d', '', 0, False, revision_id),
256
'b/c':(('b', 'c', 'c-id'), [
257
('f', '', 0, False, null_stat),
258
('f', c_sha, c_len, False, revision_id),
260
'b/d':(('b', 'd', 'd-id'), [
261
('d', '', 0, False, null_stat),
262
('d', '', 0, False, revision_id),
264
'b/d/e':(('b/d', 'e', 'e-id'), [
265
('f', '', 0, False, null_stat),
266
('f', e_sha, e_len, False, revision_id),
268
'b-c':(('', 'b-c', 'b-c-id'), [
269
('f', '', 0, False, null_stat),
270
('f', b_c_sha, b_c_len, False, revision_id),
272
'f':(('', 'f', 'f-id'), [
273
('f', '', 0, False, null_stat),
274
('f', f_sha, f_len, False, revision_id),
277
state = dirstate.DirState.from_tree(tree, 'dirstate')
282
# Use a different object, to make sure nothing is pre-cached in memory.
283
state = dirstate.DirState.on_file('dirstate')
285
self.addCleanup(state.unlock)
286
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
287
state._dirblock_state)
288
# This is code is only really tested if we actually have to make more
289
# than one read, so set the page size to something smaller.
290
# We want it to contain about 2.2 records, so that we have a couple
291
# records that we can read per attempt
292
state._bisect_page_size = 200
293
return tree, state, expected
295
def create_duplicated_dirstate(self):
296
"""Create a dirstate with a deleted and added entries.
298
This grabs a basic_dirstate, and then removes and re adds every entry
301
tree, state, expected = self.create_basic_dirstate()
302
# Now we will just remove and add every file so we get an extra entry
303
# per entry. Unversion in reverse order so we handle subdirs
304
tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
305
tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
306
['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
308
# Update the expected dictionary.
309
for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
310
orig = expected[path]
312
# This record was deleted in the current tree
313
expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
315
new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
316
# And didn't exist in the basis tree
317
expected[path2] = (new_key, [orig[1][0],
318
dirstate.DirState.NULL_PARENT_DETAILS])
320
# We will replace the 'dirstate' file underneath 'state', but that is
321
# okay as lock as we unlock 'state' first.
324
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
330
# But we need to leave state in a read-lock because we already have
331
# a cleanup scheduled
333
return tree, state, expected
335
def create_renamed_dirstate(self):
336
"""Create a dirstate with a few internal renames.
338
This takes the basic dirstate, and moves the paths around.
340
tree, state, expected = self.create_basic_dirstate()
342
tree.rename_one('a', 'b/g')
344
tree.rename_one('b/d', 'h')
346
old_a = expected['a']
347
expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
348
expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
349
('r', 'a', 0, False, '')])
350
old_d = expected['b/d']
351
expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
352
expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
353
('r', 'b/d', 0, False, '')])
355
old_e = expected['b/d/e']
356
expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
358
expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
359
('r', 'b/d/e', 0, False, '')])
363
new_state = dirstate.DirState.from_tree(tree, 'dirstate')
370
return tree, state, expected
373
class TestTreeToDirState(TestCaseWithDirState):
375
def test_empty_to_dirstate(self):
376
"""We should be able to create a dirstate for an empty tree."""
377
# There are no files on disk and no parents
378
tree = self.make_branch_and_tree('tree')
379
expected_result = ([], [
380
(('', '', tree.get_root_id()), # common details
381
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
383
state = dirstate.DirState.from_tree(tree, 'dirstate')
385
self.check_state_with_reopen(expected_result, state)
68
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
70
[(('', '', 'directory', tree.inventory.root.file_id, 0, root_stat_pack, ''), [])],
71
list(state._iter_rows()))
73
state = dirstate.DirState.on_file('dirstate')
387
76
def test_1_parents_empty_to_dirstate(self):
388
77
# create a parent by doing a commit
389
78
tree = self.make_branch_and_tree('tree')
390
rev_id = tree.commit('first post').encode('utf8')
391
root_stat_pack = dirstate.pack_stat(os.stat(tree.basedir))
392
expected_result = ([rev_id], [
393
(('', '', tree.get_root_id()), # common details
394
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
395
('d', '', 0, False, rev_id), # first parent details
79
rev_id = tree.commit('first post')
397
80
state = dirstate.DirState.from_tree(tree, 'dirstate')
398
self.check_state_with_reopen(expected_result, state)
81
# we want to be able to get the lines of the dirstate that we will
83
lines = state.get_lines()
84
# we now have parent revisions, and all the files in the tree were
85
# last modified in the parent.
87
'#bazaar dirstate flat format 1\n'
88
'adler32: [0-9-][0-9]*\n'
92
'\x00\x00d\x00TREE_ROOT\x00[0-9]+\x00[0-9a-zA-Z+/]{32}\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00\n'
93
'\x00$') % rev_id.encode('utf8')
94
self.assertContainsRe(''.join(lines), expected_lines_re)
405
96
def test_2_parents_empty_to_dirstate(self):
406
97
# create a parent by doing a commit
409
100
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
410
101
rev_id2 = tree2.commit('second post', allow_pointless=True)
411
102
tree.merge_from_branch(tree2.branch)
412
expected_result = ([rev_id, rev_id2], [
413
(('', '', tree.get_root_id()), # common details
414
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
415
('d', '', 0, False, rev_id), # first parent details
416
('d', '', 0, False, rev_id), # second parent details
418
103
state = dirstate.DirState.from_tree(tree, 'dirstate')
419
self.check_state_with_reopen(expected_result, state)
104
# we want to be able to get the lines of the dirstate that we will
106
lines = state.get_lines()
107
# we now have parent revisions, and all the files in the tree were
108
# last modified in the parent.
109
expected_lines_re = (
110
'#bazaar dirstate flat format 1\n'
111
'adler32: [0-9-][0-9]*\n'
113
'2\x00.*\x00.*\x00\n\x00'
115
'\x00\x00d\x00TREE_ROOT\x000\x00[0-9a-zA-Z+/]{32}\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00\n'
116
'\x00$') % (rev_id.encode('utf8'), rev_id2.encode('utf8'))
117
self.assertContainsRe(''.join(lines), expected_lines_re)
426
119
def test_empty_unknowns_are_ignored_to_dirstate(self):
427
120
"""We should be able to create a dirstate for an empty tree."""
428
121
# There are no files on disk and no parents
429
122
tree = self.make_branch_and_tree('tree')
430
123
self.build_tree(['tree/unknown'])
431
expected_result = ([], [
432
(('', '', tree.get_root_id()), # common details
433
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
435
124
state = dirstate.DirState.from_tree(tree, 'dirstate')
436
self.check_state_with_reopen(expected_result, state)
125
# we want to be able to get the lines of the dirstate that we will
127
lines = state.get_lines()
128
expected_lines_re = (
129
'#bazaar dirstate flat format 1\n'
130
'adler32: [0-9-][0-9]*\n'
134
'\x00\x00d\x00TREE_ROOT\x00[0-9]+\x00[0-9a-zA-Z+/]{32}\x00\x00\n'
136
self.assertContainsRe(''.join(lines), expected_lines_re)
438
138
def get_tree_with_a_file(self):
439
139
tree = self.make_branch_and_tree('tree')
440
140
self.build_tree(['tree/a file'])
441
tree.add('a file', 'a-file-id')
141
tree.add('a file', 'a file id')
444
144
def test_non_empty_no_parents_to_dirstate(self):
445
145
"""We should be able to create a dirstate for an empty tree."""
446
146
# There are files on disk and no parents
447
147
tree = self.get_tree_with_a_file()
448
expected_result = ([], [
449
(('', '', tree.get_root_id()), # common details
450
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
452
(('', 'a file', 'a-file-id'), # common
453
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
456
148
state = dirstate.DirState.from_tree(tree, 'dirstate')
457
self.check_state_with_reopen(expected_result, state)
149
# we want to be able to get the lines of the dirstate that we will
151
lines = state.get_lines()
152
expected_lines_re = (
153
'#bazaar dirstate flat format 1\n'
154
'adler32: [0-9-][0-9]*\n'
158
'\x00\x00d\x00TREE_ROOT\x00[0-9]+\x00[0-9a-zA-Z+/]{32}\x00\x00\n'
159
'\x00\x00a file\x00f\x00a file id\x0024\x00[0-9a-zA-Z+/]{32}\x00c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8\x00'
161
self.assertContainsRe(''.join(lines), expected_lines_re)
459
163
def test_1_parents_not_empty_to_dirstate(self):
460
164
# create a parent by doing a commit
461
165
tree = self.get_tree_with_a_file()
462
rev_id = tree.commit('first post').encode('utf8')
166
rev_id = tree.commit('first post')
463
167
# change the current content to be different this will alter stat, sha
465
169
self.build_tree_contents([('tree/a file', 'new content\n')])
466
expected_result = ([rev_id], [
467
(('', '', tree.get_root_id()), # common details
468
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
469
('d', '', 0, False, rev_id), # first parent details
471
(('', 'a file', 'a-file-id'), # common
472
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
473
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
474
rev_id), # first parent
477
170
state = dirstate.DirState.from_tree(tree, 'dirstate')
478
self.check_state_with_reopen(expected_result, state)
171
# we want to be able to get the lines of the dirstate that we will
173
lines = state.get_lines()
174
# we now have parent revisions, and all the files in the tree were
175
# last modified in the parent.
176
expected_lines_re = (
177
'#bazaar dirstate flat format 1\n'
178
'adler32: [0-9-][0-9]*\n'
182
'\x00\x00d\x00TREE_ROOT\x00[0-9]+\x00[0-9a-zA-Z+/]{32}\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00\n'
183
'\x00\x00a file\x00f\x00a file id\x0012\x00[0-9a-zA-Z+/]{32}\x008b787bd9293c8b962c7a637a9fdbf627fe68610e\x00%s\x00f\x00\x00a file\x0024\x00n\x00c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8\x00\n'
184
'\x00$') % (rev_id.encode('utf8'), rev_id.encode('utf8'))
185
self.assertContainsRe(''.join(lines), expected_lines_re)
480
187
def test_2_parents_not_empty_to_dirstate(self):
481
188
# create a parent by doing a commit
482
189
tree = self.get_tree_with_a_file()
483
rev_id = tree.commit('first post').encode('utf8')
190
rev_id = tree.commit('first post')
484
191
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
485
192
# change the current content to be different this will alter stat, sha
487
194
self.build_tree_contents([('tree2/a file', 'merge content\n')])
488
rev_id2 = tree2.commit('second post').encode('utf8')
195
rev_id2 = tree2.commit('second post')
489
196
tree.merge_from_branch(tree2.branch)
490
197
# change the current content to be different this will alter stat, sha
491
198
# and length again, giving us three distinct values:
492
199
self.build_tree_contents([('tree/a file', 'new content\n')])
493
expected_result = ([rev_id, rev_id2], [
494
(('', '', tree.get_root_id()), # common details
495
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
496
('d', '', 0, False, rev_id), # first parent details
497
('d', '', 0, False, rev_id), # second parent details
499
(('', 'a file', 'a-file-id'), # common
500
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
501
('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
502
rev_id), # first parent
503
('f', '314d796174c9412647c3ce07dfb5d36a94e72958', 14, False,
504
rev_id2), # second parent
507
200
state = dirstate.DirState.from_tree(tree, 'dirstate')
508
self.check_state_with_reopen(expected_result, state)
510
def test_colliding_fileids(self):
511
# test insertion of parents creating several entries at the same path.
512
# we used to have a bug where they could cause the dirstate to break
513
# its ordering invariants.
514
# create some trees to test from
517
tree = self.make_branch_and_tree('tree%d' % i)
518
self.build_tree(['tree%d/name' % i,])
519
tree.add(['name'], ['file-id%d' % i])
520
revision_id = 'revid-%d' % i
521
tree.commit('message', rev_id=revision_id)
522
parents.append((revision_id,
523
tree.branch.repository.revision_tree(revision_id)))
524
# now fold these trees into a dirstate
525
state = dirstate.DirState.initialize('dirstate')
527
state.set_parent_trees(parents, [])
533
class TestDirStateOnFile(TestCaseWithDirState):
201
# we want to be able to get the lines of the dirstate that we will
203
lines = state.get_lines()
204
# we now have parent revisions, and all the files in the tree were
205
# last modified in the parent.
206
expected_lines_re = (
207
'#bazaar dirstate flat format 1\n'
208
'adler32: [0-9-][0-9]*\n'
210
'2\x00.*\x00.*\x00\n\x00'
212
'\x00\x00d\x00TREE_ROOT\x000\x00[0-9a-zA-Z+/]{32}\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00%s\x00d\x00\x00\x00\x00n\x00\x00\n\x00'
213
'\x00a file\x00f\x00a file id\x0012\x00[0-9a-zA-Z+/]{32}\x008b787bd9293c8b962c7a637a9fdbf627fe68610e\x00%s\x00f\x00\x00a file\x0024\x00n\x00c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8\x00%s\x00f\x00\x00a file\x0014\x00n\x00314d796174c9412647c3ce07dfb5d36a94e72958\x00\n\x00$'
214
% (rev_id.encode('utf8'), rev_id2.encode('utf8'),
215
rev_id.encode('utf8'), rev_id2.encode('utf8')))
216
self.assertContainsRe(''.join(lines), expected_lines_re)
219
class TestDirStateOnFile(TestCaseWithTransport):
535
221
def test_construct_with_path(self):
536
222
tree = self.make_branch_and_tree('tree')
537
state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
223
state = dirstate.DirState.from_tree(tree, 'dirstate')
538
224
# we want to be able to get the lines of the dirstate that we will
540
226
lines = state.get_lines()
542
227
self.build_tree_contents([('dirstate', ''.join(lines))])
543
228
# get a state object
544
# no parents, default tree content
545
expected_result = ([], [
546
(('', '', tree.get_root_id()), # common details
547
# current tree details, but new from_tree skips statting, it
548
# uses set_state_from_inventory, and thus depends on the
550
[('d', '', 0, False, dirstate.DirState.NULLSTAT),
553
state = dirstate.DirState.on_file('dirstate')
554
state.lock_write() # check_state_with_reopen will save() and unlock it
555
self.check_state_with_reopen(expected_result, state)
557
def test_can_save_clean_on_file(self):
558
tree = self.make_branch_and_tree('tree')
559
state = dirstate.DirState.from_tree(tree, 'dirstate')
561
# doing a save should work here as there have been no changes.
563
# TODO: stat it and check it hasn't changed; may require waiting
564
# for the state accuracy window.
568
def test_can_save_in_read_lock(self):
569
self.build_tree(['a-file'])
570
state = dirstate.DirState.initialize('dirstate')
572
# No stat and no sha1 sum.
573
state.add('a-file', 'a-file-id', 'file', None, '')
578
# Now open in readonly mode
579
state = dirstate.DirState.on_file('dirstate')
582
entry = state._get_entry(0, path_utf8='a-file')
583
# The current size should be 0 (default)
584
self.assertEqual(0, entry[1][0][2])
585
# We should have a real entry.
586
self.assertNotEqual((None, None), entry)
587
# Make sure everything is old enough
588
state._sha_cutoff_time()
589
state._cutoff_time += 10
590
# Change the file length
591
self.build_tree_contents([('a-file', 'shorter')])
592
sha1sum = dirstate.update_entry(state, entry, 'a-file',
594
# new file, no cached sha:
595
self.assertEqual(None, sha1sum)
597
# The dirblock has been updated
598
self.assertEqual(7, entry[1][0][2])
599
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
600
state._dirblock_state)
603
# Now, since we are the only one holding a lock, we should be able
604
# to save and have it written to disk
609
# Re-open the file, and ensure that the state has been updated.
610
state = dirstate.DirState.on_file('dirstate')
613
entry = state._get_entry(0, path_utf8='a-file')
614
self.assertEqual(7, entry[1][0][2])
618
def test_save_fails_quietly_if_locked(self):
619
"""If dirstate is locked, save will fail without complaining."""
620
self.build_tree(['a-file'])
621
state = dirstate.DirState.initialize('dirstate')
623
# No stat and no sha1 sum.
624
state.add('a-file', 'a-file-id', 'file', None, '')
629
state = dirstate.DirState.on_file('dirstate')
632
entry = state._get_entry(0, path_utf8='a-file')
633
sha1sum = dirstate.update_entry(state, entry, 'a-file',
636
self.assertEqual(None, sha1sum)
637
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
638
state._dirblock_state)
640
# Now, before we try to save, grab another dirstate, and take out a
642
# TODO: jam 20070315 Ideally this would be locked by another
643
# process. To make sure the file is really OS locked.
644
state2 = dirstate.DirState.on_file('dirstate')
647
# This won't actually write anything, because it couldn't grab
648
# a write lock. But it shouldn't raise an error, either.
649
# TODO: jam 20070315 We should probably distinguish between
650
# being dirty because of 'update_entry'. And dirty
651
# because of real modification. So that save() *does*
652
# raise a real error if it fails when we have real
660
# The file on disk should not be modified.
661
state = dirstate.DirState.on_file('dirstate')
664
entry = state._get_entry(0, path_utf8='a-file')
665
self.assertEqual('', entry[1][0][1])
669
def test_save_refuses_if_changes_aborted(self):
670
self.build_tree(['a-file', 'a-dir/'])
671
state = dirstate.DirState.initialize('dirstate')
673
# No stat and no sha1 sum.
674
state.add('a-file', 'a-file-id', 'file', None, '')
679
# The dirstate should include TREE_ROOT and 'a-file' and nothing else
681
('', [(('', '', 'TREE_ROOT'),
682
[('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
683
('', [(('', 'a-file', 'a-file-id'),
684
[('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
687
state = dirstate.DirState.on_file('dirstate')
690
state._read_dirblocks_if_needed()
691
self.assertEqual(expected_blocks, state._dirblocks)
693
# Now modify the state, but mark it as inconsistent
694
state.add('a-dir', 'a-dir-id', 'directory', None, '')
695
state._changes_aborted = True
700
state = dirstate.DirState.on_file('dirstate')
703
state._read_dirblocks_if_needed()
704
self.assertEqual(expected_blocks, state._dirblocks)
709
class TestDirStateInitialize(TestCaseWithDirState):
229
state = dirstate.DirState.on_file('dirstate')
230
# ask it for a parents list
231
self.assertEqual([], state.get_parent_ids())
232
# doing a save should work here as there have been no changes.
236
class TestDirStateInitialize(TestCaseWithTransport):
711
238
def test_initialize(self):
712
expected_result = ([], [
713
(('', '', 'TREE_ROOT'), # common details
714
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
717
239
state = dirstate.DirState.initialize('dirstate')
719
self.assertIsInstance(state, dirstate.DirState)
720
lines = state.get_lines()
723
# On win32 you can't read from a locked file, even within the same
724
# process. So we have to unlock and release before we check the file
726
self.assertFileEqual(''.join(lines), 'dirstate')
727
state.lock_read() # check_state_with_reopen will unlock
728
self.check_state_with_reopen(expected_result, state)
731
class TestDirStateManipulations(TestCaseWithDirState):
240
self.assertIsInstance(state, dirstate.DirState)
241
self.assertFileEqual(
242
'#bazaar dirstate flat format 1\n'
243
'adler32: -455929114\n'
247
# after the 0 parent count, there is the \x00\n\x00 line delim
248
# then '' for dir, '' for basame, and then 'd' for directory.
249
# then the root value, 0 size, our constant xxxx packed stat, and
250
# an empty sha value. Finally a new \x00\n\x00 delimiter
251
'\x00\x00d\x00TREE_ROOT\x000\x00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x00\x00\n'
255
(('', '', 'directory', 'TREE_ROOT', 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), [])]
256
self.assertEqual(expected_rows, list(state._iter_rows()))
257
state = dirstate.DirState.on_file('dirstate')
258
self.assertEqual(expected_rows, list(state._iter_rows()))
261
class TestDirstateManipulations(TestCaseWithTransport):
733
263
def test_set_state_from_inventory_no_content_no_parents(self):
734
264
# setting the current inventory is a slow but important api to support.
265
state = dirstate.DirState.initialize('dirstate')
735
266
tree1 = self.make_branch_and_memory_tree('tree1')
736
267
tree1.lock_write()
739
revid1 = tree1.commit('foo').encode('utf8')
740
root_id = tree1.get_root_id()
741
inv = tree1.inventory
744
expected_result = [], [
745
(('', '', root_id), [
746
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
747
state = dirstate.DirState.initialize('dirstate')
749
state.set_state_from_inventory(inv)
750
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
752
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
753
state._dirblock_state)
758
# This will unlock it
759
self.check_state_with_reopen(expected_result, state)
761
def test_set_state_from_inventory_preserves_hashcache(self):
762
# https://bugs.launchpad.net/bzr/+bug/146176
763
# set_state_from_inventory should preserve the stat and hash value for
764
# workingtree files that are not changed by the inventory.
766
tree = self.make_branch_and_tree('.')
767
# depends on the default format using dirstate...
770
# make a dirstate with some valid hashcache data
771
# file on disk, but that's not needed for this test
772
foo_contents = 'contents of foo'
773
self.build_tree_contents([('foo', foo_contents)])
774
tree.add('foo', 'foo-id')
776
foo_stat = os.stat('foo')
777
foo_packed = dirstate.pack_stat(foo_stat)
778
foo_sha = osutils.sha_string(foo_contents)
779
foo_size = len(foo_contents)
781
# should not be cached yet, because the file's too fresh
783
(('', 'foo', 'foo-id',),
784
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
785
tree._dirstate._get_entry(0, 'foo-id'))
786
# poke in some hashcache information - it wouldn't normally be
787
# stored because it's too fresh
788
tree._dirstate.update_minimal(
789
('', 'foo', 'foo-id'),
790
'f', False, foo_sha, foo_packed, foo_size, 'foo')
791
# now should be cached
793
(('', 'foo', 'foo-id',),
794
[('f', foo_sha, foo_size, False, foo_packed)]),
795
tree._dirstate._get_entry(0, 'foo-id'))
797
# extract the inventory, and add something to it
798
inv = tree._get_inventory()
799
# should see the file we poked in...
800
self.assertTrue(inv.has_id('foo-id'))
801
self.assertTrue(inv.has_filename('foo'))
802
inv.add_path('bar', 'file', 'bar-id')
803
tree._dirstate._validate()
804
# this used to cause it to lose its hashcache
805
tree._dirstate.set_state_from_inventory(inv)
806
tree._dirstate._validate()
812
# now check that the state still has the original hashcache value
813
state = tree._dirstate
815
foo_tuple = state._get_entry(0, path_utf8='foo')
817
(('', 'foo', 'foo-id',),
818
[('f', foo_sha, len(foo_contents), False,
819
dirstate.pack_stat(foo_stat))]),
824
def test_set_state_from_inventory_mixed_paths(self):
825
tree1 = self.make_branch_and_tree('tree1')
826
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
827
'tree1/a/b/foo', 'tree1/a-b/bar'])
830
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
831
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
832
tree1.commit('rev1', rev_id='rev1')
833
root_id = tree1.get_root_id()
834
inv = tree1.inventory
837
expected_result1 = [('', '', root_id, 'd'),
838
('', 'a', 'a-id', 'd'),
839
('', 'a-b', 'a-b-id', 'd'),
840
('a', 'b', 'b-id', 'd'),
841
('a/b', 'foo', 'foo-id', 'f'),
842
('a-b', 'bar', 'bar-id', 'f'),
844
expected_result2 = [('', '', root_id, 'd'),
845
('', 'a', 'a-id', 'd'),
846
('', 'a-b', 'a-b-id', 'd'),
847
('a-b', 'bar', 'bar-id', 'f'),
849
state = dirstate.DirState.initialize('dirstate')
851
state.set_state_from_inventory(inv)
853
for entry in state._iter_entries():
854
values.append(entry[0] + entry[1][0][:1])
855
self.assertEqual(expected_result1, values)
857
state.set_state_from_inventory(inv)
859
for entry in state._iter_entries():
860
values.append(entry[0] + entry[1][0][:1])
861
self.assertEqual(expected_result2, values)
269
revid1 = tree1.commit('foo')
270
root_id = tree1.inventory.root.file_id
271
state.set_state_from_inventory(tree1.inventory)
273
self.assertEqual(DirState.IN_MEMORY_UNMODIFIED, state._header_state)
274
self.assertEqual(DirState.IN_MEMORY_MODIFIED, state._dirblock_state)
275
expected_rows = [(('', '', 'directory', root_id, 0, DirState.NULLSTAT, ''), [])]
276
self.assertEqual(expected_rows, list(state._iter_rows()))
277
# check we can reopen and have the change preserved.
279
state = dirstate.DirState.on_file('dirstate')
280
self.assertEqual(expected_rows, list(state._iter_rows()))
865
282
def test_set_path_id_no_parents(self):
866
283
"""The id of a path can be changed trivally with no parents."""
867
284
state = dirstate.DirState.initialize('dirstate')
869
# check precondition to be sure the state does change appropriately.
870
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
871
self.assertEqual([root_entry], list(state._iter_entries()))
872
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
873
self.assertEqual(root_entry,
874
state._get_entry(0, fileid_utf8='TREE_ROOT'))
875
self.assertEqual((None, None),
876
state._get_entry(0, fileid_utf8='second-root-id'))
877
state.set_path_id('', 'second-root-id')
878
new_root_entry = (('', '', 'second-root-id'),
879
[('d', '', 0, False, 'x'*32)])
880
expected_rows = [new_root_entry]
881
self.assertEqual(expected_rows, list(state._iter_entries()))
882
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
883
self.assertEqual(new_root_entry,
884
state._get_entry(0, fileid_utf8='second-root-id'))
885
self.assertEqual((None, None),
886
state._get_entry(0, fileid_utf8='TREE_ROOT'))
887
# should work across save too
891
state = dirstate.DirState.on_file('dirstate')
895
self.assertEqual(expected_rows, list(state._iter_entries()))
899
def test_set_path_id_with_parents(self):
900
"""Set the root file id in a dirstate with parents"""
901
mt = self.make_branch_and_tree('mt')
902
# in case the default tree format uses a different root id
903
mt.set_root_id('TREE_ROOT')
904
mt.commit('foo', rev_id='parent-revid')
905
rt = mt.branch.repository.revision_tree('parent-revid')
906
state = dirstate.DirState.initialize('dirstate')
909
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
910
root_entry = (('', '', 'TREE_ROOT'),
911
[('d', '', 0, False, 'x'*32),
912
('d', '', 0, False, 'parent-revid')])
913
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
914
self.assertEqual(root_entry,
915
state._get_entry(0, fileid_utf8='TREE_ROOT'))
916
self.assertEqual((None, None),
917
state._get_entry(0, fileid_utf8='Asecond-root-id'))
918
state.set_path_id('', 'Asecond-root-id')
920
# now see that it is what we expected
921
old_root_entry = (('', '', 'TREE_ROOT'),
922
[('a', '', 0, False, ''),
923
('d', '', 0, False, 'parent-revid')])
924
new_root_entry = (('', '', 'Asecond-root-id'),
925
[('d', '', 0, False, ''),
926
('a', '', 0, False, '')])
927
expected_rows = [new_root_entry, old_root_entry]
929
self.assertEqual(expected_rows, list(state._iter_entries()))
930
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
931
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
932
self.assertEqual((None, None),
933
state._get_entry(0, fileid_utf8='TREE_ROOT'))
934
self.assertEqual(old_root_entry,
935
state._get_entry(1, fileid_utf8='TREE_ROOT'))
936
self.assertEqual(new_root_entry,
937
state._get_entry(0, fileid_utf8='Asecond-root-id'))
938
self.assertEqual((None, None),
939
state._get_entry(1, fileid_utf8='Asecond-root-id'))
940
# should work across save too
944
# now flush & check we get the same
945
state = dirstate.DirState.on_file('dirstate')
949
self.assertEqual(expected_rows, list(state._iter_entries()))
952
# now change within an existing file-backed state
956
state.set_path_id('', 'tree-root-2')
285
# check precondition to be sure the state does change appropriately.
287
[(('', '', 'directory', 'TREE_ROOT', 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), [])],
288
list(state._iter_rows()))
289
state.set_path_id('', 'foobarbaz')
291
(('', '', 'directory', 'foobarbaz', 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), [])]
292
self.assertEqual(expected_rows, list(state._iter_rows()))
293
# should work across save too
295
state = dirstate.DirState.on_file('dirstate')
296
self.assertEqual(expected_rows, list(state._iter_rows()))
961
298
def test_set_parent_trees_no_content(self):
962
299
# set_parent_trees is a slow but important api to support.
300
state = dirstate.DirState.initialize('dirstate')
963
301
tree1 = self.make_branch_and_memory_tree('tree1')
964
302
tree1.lock_write()
967
revid1 = tree1.commit('foo')
304
revid1 = tree1.commit('foo')
970
306
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
971
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
307
tree2 = MemoryTree.create_on_branch(branch2)
972
308
tree2.lock_write()
974
revid2 = tree2.commit('foo')
975
root_id = tree2.get_root_id()
978
state = dirstate.DirState.initialize('dirstate')
980
state.set_path_id('', root_id)
981
state.set_parent_trees(
982
((revid1, tree1.branch.repository.revision_tree(revid1)),
983
(revid2, tree2.branch.repository.revision_tree(revid2)),
984
('ghost-rev', None)),
986
# check we can reopen and use the dirstate after setting parent
309
revid2 = tree2.commit('foo')
310
root_id = tree2.inventory.root.file_id
311
state.set_path_id('', root_id)
313
state.set_parent_trees(
314
((revid1, tree1.branch.repository.revision_tree(revid1)),
315
(revid2, tree2.branch.repository.revision_tree(revid2)),
316
('ghost-rev', None)),
318
# check we can reopen and use the dirstate after setting parent trees.
993
320
state = dirstate.DirState.on_file('dirstate')
996
self.assertEqual([revid1, revid2, 'ghost-rev'],
997
state.get_parent_ids())
998
# iterating the entire state ensures that the state is parsable.
999
list(state._iter_entries())
1000
# be sure that it sets not appends - change it
1001
state.set_parent_trees(
1002
((revid1, tree1.branch.repository.revision_tree(revid1)),
1003
('ghost-rev', None)),
1005
# and now put it back.
1006
state.set_parent_trees(
1007
((revid1, tree1.branch.repository.revision_tree(revid1)),
1008
(revid2, tree2.branch.repository.revision_tree(revid2)),
1009
('ghost-rev', tree2.branch.repository.revision_tree(
1010
_mod_revision.NULL_REVISION))),
1012
self.assertEqual([revid1, revid2, 'ghost-rev'],
1013
state.get_parent_ids())
1014
# the ghost should be recorded as such by set_parent_trees.
1015
self.assertEqual(['ghost-rev'], state.get_ghosts())
1017
[(('', '', root_id), [
1018
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1019
('d', '', 0, False, revid1),
1020
('d', '', 0, False, revid1)
1022
list(state._iter_entries()))
321
self.assertEqual([revid1, revid2, 'ghost-rev'], state.get_parent_ids())
322
# iterating the entire state ensures that the state is parsable.
323
list(state._iter_rows())
324
# be sure that it sets not appends - change it
325
state.set_parent_trees(
326
((revid1, tree1.branch.repository.revision_tree(revid1)),
327
('ghost-rev', None)),
329
# and now put it back.
330
state.set_parent_trees(
331
((revid1, tree1.branch.repository.revision_tree(revid1)),
332
(revid2, tree2.branch.repository.revision_tree(revid2)),
333
('ghost-rev', tree2.branch.repository.revision_tree(None))),
335
self.assertEqual([revid1, revid2, 'ghost-rev'], state.get_parent_ids())
336
# the ghost should be recorded as such by set_parent_trees.
337
self.assertEqual(['ghost-rev'], state.get_ghosts())
339
[(('', '', 'directory', root_id, 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), [
340
(revid1, 'directory', '', '', 0, False, ''),
341
(revid2, 'directory', '', '', 0, False, '')])],
342
list(state._iter_rows()))
1026
344
def test_set_parent_trees_file_missing_from_tree(self):
1027
345
# Adding a parent tree may reference files not in the current state.
1028
# they should get listed just once by id, even if they are in two
346
# they should get listed just once by id, even if they are in two
1029
347
# separate trees.
1030
348
# set_parent_trees is a slow but important api to support.
349
state = dirstate.DirState.initialize('dirstate')
1031
350
tree1 = self.make_branch_and_memory_tree('tree1')
1032
351
tree1.lock_write()
1035
tree1.add(['a file'], ['file-id'], ['file'])
1036
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
1037
revid1 = tree1.commit('foo')
353
tree1.add(['a file'], ['file-id'], ['file'])
354
tree1.put_file_bytes_non_atomic('file-id', 'file-content')
355
revid1 = tree1.commit('foo')
1040
357
branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1041
tree2 = memorytree.MemoryTree.create_on_branch(branch2)
358
tree2 = MemoryTree.create_on_branch(branch2)
1042
359
tree2.lock_write()
1044
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1045
revid2 = tree2.commit('foo')
1046
root_id = tree2.get_root_id()
360
tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
361
revid2 = tree2.commit('foo')
362
root_id = tree2.inventory.root.file_id
363
state.set_path_id('', root_id)
365
state.set_parent_trees(
366
((revid1, tree1.branch.repository.revision_tree(revid1)),
367
(revid2, tree2.branch.repository.revision_tree(revid2)),
1049
369
# check the layout in memory
1050
expected_result = [revid1.encode('utf8'), revid2.encode('utf8')], [
1051
(('', '', root_id), [
1052
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1053
('d', '', 0, False, revid1.encode('utf8')),
1054
('d', '', 0, False, revid1.encode('utf8'))
1056
(('', 'a file', 'file-id'), [
1057
('a', '', 0, False, ''),
1058
('f', '2439573625385400f2a669657a7db6ae7515d371', 12, False,
1059
revid1.encode('utf8')),
1060
('f', '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d', 16, False,
1061
revid2.encode('utf8'))
371
(('', '', 'directory', root_id, 0, DirState.NULLSTAT, ''),
372
[(revid1.encode('utf8'), 'directory', '', '', 0, False, ''),
373
(revid2.encode('utf8'), 'directory', '', '', 0, False, '')]),
374
(('/', 'RECYCLED.BIN', 'file', 'file-id', 0, DirState.NULLSTAT, ''),
375
[(revid1.encode('utf8'), 'file', '', 'a file', 12, False, '2439573625385400f2a669657a7db6ae7515d371'),
376
(revid2.encode('utf8'), 'file', '', 'a file', 16, False, '542e57dc1cda4af37cb8e55ec07ce60364bb3c7d')])
1064
state = dirstate.DirState.initialize('dirstate')
1066
state.set_path_id('', root_id)
1067
state.set_parent_trees(
1068
((revid1, tree1.branch.repository.revision_tree(revid1)),
1069
(revid2, tree2.branch.repository.revision_tree(revid2)),
1075
# check_state_with_reopen will unlock
1076
self.check_state_with_reopen(expected_result, state)
378
self.assertEqual(expected_rows, list(state._iter_rows()))
379
# check we can reopen and use the dirstate after setting parent trees.
381
state = dirstate.DirState.on_file('dirstate')
382
self.assertEqual(expected_rows, list(state._iter_rows()))
1078
384
### add a path via _set_data - so we dont need delta work, just
1079
385
# raw data in, and ensure that it comes out via get_lines happily.
1081
387
def test_add_path_to_root_no_parents_all_data(self):
1082
388
# The most trivial addition of a path is when there are no parents and
1083
389
# its in the root and all data about the file is supplied
390
state = dirstate.DirState.initialize('dirstate')
1084
391
self.build_tree(['a file'])
1085
392
stat = os.lstat('a file')
1086
393
# the 1*20 is the sha1 pretend value.
1087
state = dirstate.DirState.initialize('dirstate')
1088
expected_entries = [
1089
(('', '', 'TREE_ROOT'), [
1090
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1092
(('', 'a file', 'a-file-id'), [
1093
('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
394
state.add('a file', 'a file id', 'file', stat, '1'*20)
395
# having added it, it should be in the output of iter_rows.
397
(('', '', 'directory', 'TREE_ROOT', 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), []),
398
(('', 'a file', 'file', 'a file id', 19, dirstate.pack_stat(stat), '1'*20), []),
1097
state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1098
# having added it, it should be in the output of iter_entries.
1099
self.assertEqual(expected_entries, list(state._iter_entries()))
1100
# saving and reloading should not affect this.
400
self.assertEqual(expected_rows, list(state._iter_rows()))
401
# saving and reloading should not affect this.
1104
403
state = dirstate.DirState.on_file('dirstate')
1106
self.addCleanup(state.unlock)
1107
self.assertEqual(expected_entries, list(state._iter_entries()))
404
self.assertEqual(expected_rows, list(state._iter_rows()))
1109
406
def test_add_path_to_unversioned_directory(self):
1110
407
"""Adding a path to an unversioned directory should error.
1112
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
409
This is a duplicate of TestWorkingTree.test_add_in_unversioned,
1113
410
once dirstate is stable and if it is merged with WorkingTree3, consider
1114
411
removing this copy of the test.
413
state = dirstate.DirState.initialize('dirstate')
1116
414
self.build_tree(['unversioned/', 'unversioned/a file'])
1117
state = dirstate.DirState.initialize('dirstate')
1118
self.addCleanup(state.unlock)
1119
415
self.assertRaises(errors.NotVersionedError, state.add,
1120
'unversioned/a file', 'a-file-id', 'file', None, None)
416
'unversioned/a file', 'a file id', 'file', None, None)
1122
418
def test_add_directory_to_root_no_parents_all_data(self):
1123
419
# The most trivial addition of a dir is when there are no parents and
1124
420
# its in the root and all data about the file is supplied
421
state = dirstate.DirState.initialize('dirstate')
1125
422
self.build_tree(['a dir/'])
1126
423
stat = os.lstat('a dir')
1127
expected_entries = [
1128
(('', '', 'TREE_ROOT'), [
1129
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1131
(('', 'a dir', 'a dir id'), [
1132
('d', '', 0, False, dirstate.pack_stat(stat)), # current tree
424
state.add('a dir', 'a dir id', 'directory', stat, None)
425
# having added it, it should be in the output of iter_rows.
427
(('', '', 'directory', 'TREE_ROOT', 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), []),
428
(('', 'a dir', 'directory', 'a dir id', 0, dirstate.pack_stat(stat), ''), []),
1135
state = dirstate.DirState.initialize('dirstate')
1137
state.add('a dir', 'a dir id', 'directory', stat, None)
1138
# having added it, it should be in the output of iter_entries.
1139
self.assertEqual(expected_entries, list(state._iter_entries()))
1140
# saving and reloading should not affect this.
430
self.assertEqual(expected_rows, list(state._iter_rows()))
431
# saving and reloading should not affect this.
1144
433
state = dirstate.DirState.on_file('dirstate')
1146
self.addCleanup(state.unlock)
1148
self.assertEqual(expected_entries, list(state._iter_entries()))
434
self.assertEqual(expected_rows, list(state._iter_rows()))
1150
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
436
def test_add_symlink_to_root_no_parents_all_data(self):
1151
437
# The most trivial addition of a symlink when there are no parents and
1152
438
# its in the root and all data about the file is supplied
1153
# bzr doesn't support fake symlinks on windows, yet.
1154
self.requireFeature(tests.SymlinkFeature)
1155
os.symlink(target, link_name)
1156
stat = os.lstat(link_name)
1157
expected_entries = [
1158
(('', '', 'TREE_ROOT'), [
1159
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1161
(('', link_name.encode('UTF-8'), 'a link id'), [
1162
('l', target.encode('UTF-8'), stat[6],
1163
False, dirstate.pack_stat(stat)), # current tree
439
state = dirstate.DirState.initialize('dirstate')
440
## TODO: windows: dont fail this test. Also, how are symlinks meant to
441
# be represented on windows.
442
os.symlink('target', 'a link')
443
stat = os.lstat('a link')
444
state.add('a link', 'a link id', 'symlink', stat, 'target')
445
# having added it, it should be in the output of iter_rows.
447
(('', '', 'directory', 'TREE_ROOT', 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), []),
448
(('', 'a link', 'symlink', 'a link id', 6, dirstate.pack_stat(stat), 'target'), []),
1166
state = dirstate.DirState.initialize('dirstate')
1168
state.add(link_name, 'a link id', 'symlink', stat,
1169
target.encode('UTF-8'))
1170
# having added it, it should be in the output of iter_entries.
1171
self.assertEqual(expected_entries, list(state._iter_entries()))
1172
# saving and reloading should not affect this.
450
self.assertEqual(expected_rows, list(state._iter_rows()))
451
# saving and reloading should not affect this.
1176
453
state = dirstate.DirState.on_file('dirstate')
1178
self.addCleanup(state.unlock)
1179
self.assertEqual(expected_entries, list(state._iter_entries()))
1181
def test_add_symlink_to_root_no_parents_all_data(self):
1182
self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1184
def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1185
self.requireFeature(tests.UnicodeFilenameFeature)
1186
self._test_add_symlink_to_root_no_parents_all_data(
1187
u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
454
self.assertEqual(expected_rows, list(state._iter_rows()))
1189
456
def test_add_directory_and_child_no_parents_all_data(self):
1190
457
# after adding a directory, we should be able to add children to it.
458
state = dirstate.DirState.initialize('dirstate')
1191
459
self.build_tree(['a dir/', 'a dir/a file'])
1192
dirstat = os.lstat('a dir')
460
stat = os.lstat('a dir')
461
state.add('a dir', 'a dir id', 'directory', stat, None)
1193
462
filestat = os.lstat('a dir/a file')
1194
expected_entries = [
1195
(('', '', 'TREE_ROOT'), [
1196
('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1198
(('', 'a dir', 'a dir id'), [
1199
('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1201
(('a dir', 'a file', 'a-file-id'), [
1202
('f', '1'*20, 25, False,
1203
dirstate.pack_stat(filestat)), # current tree details
463
state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
464
# having added it, it should be in the output of iter_rows.
466
(('', '', 'directory', 'TREE_ROOT', 0, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ''), []),
467
(('', 'a dir', 'directory', 'a dir id', 0, dirstate.pack_stat(stat), ''), []),
468
(('a dir', 'a file', 'file', 'a file id', 25, dirstate.pack_stat(filestat), '1'*20), []),
1206
state = dirstate.DirState.initialize('dirstate')
1208
state.add('a dir', 'a dir id', 'directory', dirstat, None)
1209
state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1210
# added it, it should be in the output of iter_entries.
1211
self.assertEqual(expected_entries, list(state._iter_entries()))
1212
# saving and reloading should not affect this.
470
self.assertEqual(expected_rows, list(state._iter_rows()))
471
# saving and reloading should not affect this.
1216
473
state = dirstate.DirState.on_file('dirstate')
1218
self.addCleanup(state.unlock)
1219
self.assertEqual(expected_entries, list(state._iter_entries()))
1221
def test_add_tree_reference(self):
1222
# make a dirstate and add a tree reference
1223
state = dirstate.DirState.initialize('dirstate')
1225
('', 'subdir', 'subdir-id'),
1226
[('t', 'subtree-123123', 0, False,
1227
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')],
1230
state.add('subdir', 'subdir-id', 'tree-reference', None, 'subtree-123123')
1231
entry = state._get_entry(0, 'subdir-id', 'subdir')
1232
self.assertEqual(entry, expected_entry)
1237
# now check we can read it back
1239
self.addCleanup(state.unlock)
1241
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1242
self.assertEqual(entry, entry2)
1243
self.assertEqual(entry, expected_entry)
1244
# and lookup by id should work too
1245
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1246
self.assertEqual(entry, expected_entry)
1248
def test_add_forbidden_names(self):
1249
state = dirstate.DirState.initialize('dirstate')
1250
self.addCleanup(state.unlock)
1251
self.assertRaises(errors.BzrError,
1252
state.add, '.', 'ass-id', 'directory', None, None)
1253
self.assertRaises(errors.BzrError,
1254
state.add, '..', 'ass-id', 'directory', None, None)
1256
def test_set_state_with_rename_b_a_bug_395556(self):
1257
# bug 395556 uncovered a bug where the dirstate ends up with a false
1258
# relocation record - in a tree with no parents there should be no
1259
# absent or relocated records. This then leads to further corruption
1260
# when a commit occurs, as the incorrect relocation gathers an
1261
# incorrect absent in tree 1, and future changes go to pot.
1262
tree1 = self.make_branch_and_tree('tree1')
1263
self.build_tree(['tree1/b'])
1266
tree1.add(['b'], ['b-id'])
1267
root_id = tree1.get_root_id()
1268
inv = tree1.inventory
1269
state = dirstate.DirState.initialize('dirstate')
1271
# Set the initial state with 'b'
1272
state.set_state_from_inventory(inv)
1273
inv.rename('b-id', root_id, 'a')
1274
# Set the new state with 'a', which currently corrupts.
1275
state.set_state_from_inventory(inv)
1276
expected_result1 = [('', '', root_id, 'd'),
1277
('', 'a', 'b-id', 'f'),
1280
for entry in state._iter_entries():
1281
values.append(entry[0] + entry[1][0][:1])
1282
self.assertEqual(expected_result1, values)
1289
class TestGetLines(TestCaseWithDirState):
474
self.assertEqual(expected_rows, list(state._iter_rows()))
477
class TestCaseWithDirstate(TestCaseWithTransport):
478
"""Helper functions for creating Dirstate objects with various content."""
480
def create_empty_dirstate(self):
481
state = dirstate.DirState.initialize('dirstate')
484
def create_dirstate_with_root(self):
485
state = self.create_empty_dirstate()
486
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
487
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
488
root_row = (root_row_direntry, [])
489
state._set_data([], root_row, [])
492
def create_dirstate_with_root_and_subdir(self):
493
state = self.create_dirstate_with_root()
494
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
496
subdir_row = (('', 'subdir', 'directory', 'subdir-id', 0, packed_stat, ''), [])
497
dirblocks.append(('', [subdir_row]))
498
state._set_data([], state._root_row, dirblocks)
501
def create_complex_dirstate(self):
502
"""This dirstate contains multiple files and directories.
512
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
514
# Notice that a/e is an empty directory.
516
state = dirstate.DirState.initialize('dirstate')
517
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
518
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
519
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
520
root_row = (root_row_direntry, [])
521
a_row = (('', 'a', 'directory', 'a-dir', 0, packed_stat, ''), [])
522
b_row = (('', 'b', 'directory', 'b-dir', 0, packed_stat, ''), [])
523
c_row = (('', 'c', 'file', 'c-file', 10, packed_stat, null_sha), [])
524
d_row = (('', 'd', 'file', 'd-file', 20, packed_stat, null_sha), [])
525
e_row = (('a', 'e', 'directory', 'e-dir', 0, packed_stat, ''), [])
526
f_row = (('a', 'f', 'file', 'f-file', 30, packed_stat, null_sha), [])
527
g_row = (('b', 'g', 'file', 'g-file', 30, packed_stat, null_sha), [])
528
h_row = (('b', 'h\xc3\xa5', 'file', 'h-\xc3\xa5-file', 40,
529
packed_stat, null_sha), [])
531
dirblocks.append(('', [a_row, b_row, c_row, d_row]))
532
dirblocks.append(('a', [e_row, f_row]))
533
dirblocks.append(('b', [g_row, h_row]))
534
state._set_data([], root_row, dirblocks)
538
class TestGetLines(TestCaseWithDirstate):
1291
540
def test_get_line_with_2_rows(self):
1292
541
state = self.create_dirstate_with_root_and_subdir()
1294
self.assertEqual(['#bazaar dirstate flat format 3\n',
1295
'crc32: 41262208\n',
1299
'\x00\x00a-root-value\x00'
1300
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1301
'\x00subdir\x00subdir-id\x00'
1302
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\n\x00'
1303
], state.get_lines())
542
self.assertEqual(['#bazaar dirstate flat format 1\n',
543
'adler32: 1283137489\n',
547
'\x00\x00d\x00a-root-value\x000'
548
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\x00\n\x00\x00subdir\x00'
549
'd\x00subdir-id\x000\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00\x00'
1307
def test_entry_to_line(self):
553
def test_row_to_line(self):
1308
554
state = self.create_dirstate_with_root()
1311
'\x00\x00a-root-value\x00d\x00\x000\x00n'
1312
'\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk',
1313
state._entry_to_line(state._dirblocks[0][1][0]))
1317
def test_entry_to_line_with_parent(self):
1318
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1319
root_entry = ('', '', 'a-root-value'), [
1320
('d', '', 0, False, packed_stat), # current tree details
1321
# first: a pointer to the current location
1322
('a', 'dirname/basename', 0, False, ''),
1324
state = dirstate.DirState.initialize('dirstate')
1327
'\x00\x00a-root-value\x00'
1328
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1329
'a\x00dirname/basename\x000\x00n\x00',
1330
state._entry_to_line(root_entry))
1334
def test_entry_to_line_with_two_parents_at_different_paths(self):
1335
# / in the tree, at / in one parent and /dirname/basename in the other.
1336
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1337
root_entry = ('', '', 'a-root-value'), [
1338
('d', '', 0, False, packed_stat), # current tree details
1339
('d', '', 0, False, 'rev_id'), # first parent details
1340
# second: a pointer to the current location
1341
('a', 'dirname/basename', 0, False, ''),
1343
state = dirstate.DirState.initialize('dirstate')
1346
'\x00\x00a-root-value\x00'
1347
'd\x00\x000\x00n\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
1348
'd\x00\x000\x00n\x00rev_id\x00'
1349
'a\x00dirname/basename\x000\x00n\x00',
1350
state._entry_to_line(root_entry))
1354
def test_iter_entries(self):
1355
# we should be able to iterate the dirstate entries from end to end
555
self.assertEqual('\x00\x00d\x00a-root-value\x000\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00',
556
state._row_to_line(state._root_row))
558
def test_row_to_line_with_parent(self):
559
state = dirstate.DirState.initialize('dirstate')
560
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
561
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
562
# one parent that was a file at path /dirname/basename
563
root_parent_direntries = [('revid', 'file', 'dirname', 'basename', 0, False, '')]
564
root_row = (root_row_direntry, root_parent_direntries)
566
'\x00\x00d\x00a-root-value\x000\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
567
'\x00revid\x00f\x00dirname\x00basename\x000\x00n\x00',
568
state._row_to_line(root_row))
570
def test_row_to_line_with_two_parents(self):
571
state = dirstate.DirState.initialize('dirstate')
572
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
573
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
574
# two parent entires: one that was a file at path /dirname/basename
575
# and one that was a directory at /
576
root_parent_direntries = [('revid', 'file', 'dirname', 'basename', 0, False, ''),
577
('revid2', 'directory', '', '', 0, False, '')]
578
root_row = (root_row_direntry, root_parent_direntries)
580
'\x00\x00d\x00a-root-value\x000\x00AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk\x00'
581
'\x00revid\x00f\x00dirname\x00basename\x000\x00n\x00'
582
'\x00revid2\x00d\x00\x00\x000\x00n\x00',
583
state._row_to_line(root_row))
585
def test_iter_rows(self):
586
# we should be able to iterate the dirstate rows from end to end
1356
587
# this is for get_lines to be easy to read.
588
state = dirstate.DirState.initialize('dirstate')
1357
589
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
590
root_row_direntry = ('', '', 'directory', 'a-root-value', 0, packed_stat, '')
591
root_row = (root_row_direntry, [])
1359
root_entries = [(('', '', 'a-root-value'), [
1360
('d', '', 0, False, packed_stat), # current tree details
1362
dirblocks.append(('', root_entries))
1363
593
# add two files in the root
1364
subdir_entry = ('', 'subdir', 'subdir-id'), [
1365
('d', '', 0, False, packed_stat), # current tree details
1367
afile_entry = ('', 'afile', 'afile-id'), [
1368
('f', 'sha1value', 34, False, packed_stat), # current tree details
1370
dirblocks.append(('', [subdir_entry, afile_entry]))
594
subdir_row = (['', 'subdir', 'directory', 'subdir-id', 0, packed_stat, ''], [])
595
afile_row = (['', 'afile', 'file', 'afile-id', 34, packed_stat, 'sha1value'], [])
596
dirblocks.append(('', [subdir_row, afile_row]))
1371
597
# and one in subdir
1372
file_entry2 = ('subdir', '2file', '2file-id'), [
1373
('f', 'sha1value', 23, False, packed_stat), # current tree details
1375
dirblocks.append(('subdir', [file_entry2]))
1376
state = dirstate.DirState.initialize('dirstate')
1378
state._set_data([], dirblocks)
1379
expected_entries = [root_entries[0], subdir_entry, afile_entry,
1381
self.assertEqual(expected_entries, list(state._iter_entries()))
1386
class TestGetBlockRowIndex(TestCaseWithDirState):
598
file_row2 = (['', '2file', 'file', '2file-id', 23, packed_stat, 'sha1value'], [])
599
dirblocks.append(('subdir', [file_row2]))
600
state._set_data([], root_row, dirblocks)
601
expected_rows = [root_row, subdir_row, afile_row, file_row2]
602
self.assertEqual(expected_rows, list(state._iter_rows()))
605
class TestGetBlockRowIndex(TestCaseWithDirstate):
1388
607
def assertBlockRowIndexEqual(self, block_index, row_index, dir_present,
1389
file_present, state, dirname, basename, tree_index):
608
file_present, state, dirname, basename):
1390
609
self.assertEqual((block_index, row_index, dir_present, file_present),
1391
state._get_block_entry_index(dirname, basename, tree_index))
610
state._get_block_row_index(dirname, basename))
1393
612
block = state._dirblocks[block_index]
1394
613
self.assertEqual(dirname, block[0])
1400
619
def test_simple_structure(self):
1401
620
state = self.create_dirstate_with_root_and_subdir()
1402
self.addCleanup(state.unlock)
1403
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'subdir', 0)
1404
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', 'bdir', 0)
1405
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'zdir', 0)
1406
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a', 'foo', 0)
1407
self.assertBlockRowIndexEqual(2, 0, False, False, state,
621
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', 'subdir')
622
self.assertBlockRowIndexEqual(0, 0, True, False, state, '', 'bdir')
623
self.assertBlockRowIndexEqual(0, 1, True, False, state, '', 'zdir')
624
self.assertBlockRowIndexEqual(1, 0, False, False, state, 'a', 'foo')
625
self.assertBlockRowIndexEqual(1, 0, False, False, state, 'subdir', 'foo')
1410
627
def test_complex_structure_exists(self):
1411
628
state = self.create_complex_dirstate()
1412
self.addCleanup(state.unlock)
1413
629
# Make sure we can find everything that exists
1414
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1415
self.assertBlockRowIndexEqual(1, 0, True, True, state, '', 'a', 0)
1416
self.assertBlockRowIndexEqual(1, 1, True, True, state, '', 'b', 0)
1417
self.assertBlockRowIndexEqual(1, 2, True, True, state, '', 'c', 0)
1418
self.assertBlockRowIndexEqual(1, 3, True, True, state, '', 'd', 0)
1419
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'a', 'e', 0)
1420
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'a', 'f', 0)
1421
self.assertBlockRowIndexEqual(3, 0, True, True, state, 'b', 'g', 0)
1422
self.assertBlockRowIndexEqual(3, 1, True, True, state,
1423
'b', 'h\xc3\xa5', 0)
630
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', 'a')
631
self.assertBlockRowIndexEqual(0, 1, True, True, state, '', 'b')
632
self.assertBlockRowIndexEqual(0, 2, True, True, state, '', 'c')
633
self.assertBlockRowIndexEqual(0, 3, True, True, state, '', 'd')
634
self.assertBlockRowIndexEqual(1, 0, True, True, state, 'a', 'e')
635
self.assertBlockRowIndexEqual(1, 1, True, True, state, 'a', 'f')
636
self.assertBlockRowIndexEqual(2, 0, True, True, state, 'b', 'g')
637
self.assertBlockRowIndexEqual(2, 1, True, True, state, 'b', 'h\xc3\xa5')
1425
639
def test_complex_structure_missing(self):
1426
640
state = self.create_complex_dirstate()
1427
self.addCleanup(state.unlock)
1428
641
# Make sure things would be inserted in the right locations
1429
642
# '_' comes before 'a'
1430
self.assertBlockRowIndexEqual(0, 0, True, True, state, '', '', 0)
1431
self.assertBlockRowIndexEqual(1, 0, True, False, state, '', '_', 0)
1432
self.assertBlockRowIndexEqual(1, 1, True, False, state, '', 'aa', 0)
1433
self.assertBlockRowIndexEqual(1, 4, True, False, state,
1435
self.assertBlockRowIndexEqual(2, 0, False, False, state, '_', 'a', 0)
1436
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'aa', 'a', 0)
1437
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'bb', 'a', 0)
643
self.assertBlockRowIndexEqual(0, 0, True, False, state, '', '_')
644
self.assertBlockRowIndexEqual(0, 1, True, False, state, '', 'aa')
645
self.assertBlockRowIndexEqual(0, 4, True, False, state, '', 'h\xc3\xa5')
646
self.assertBlockRowIndexEqual(1, 0, False, False, state, '_', 'a')
647
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'aa', 'a')
648
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'bb', 'a')
1438
649
# This would be inserted between a/ and b/
1439
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'a/e', 'a', 0)
650
self.assertBlockRowIndexEqual(2, 0, False, False, state, 'a/e', 'a')
1440
651
# Put at the end
1441
self.assertBlockRowIndexEqual(4, 0, False, False, state, 'e', 'a', 0)
1444
class TestGetEntry(TestCaseWithDirState):
1446
def assertEntryEqual(self, dirname, basename, file_id, state, path, index):
1447
"""Check that the right entry is returned for a request to getEntry."""
1448
entry = state._get_entry(index, path_utf8=path)
1450
self.assertEqual((None, None), entry)
652
self.assertBlockRowIndexEqual(3, 0, False, False, state, 'e', 'a')
655
class TestGetRow(TestCaseWithDirstate):
657
def assertRowEqual(self, dirname, basename, kind, state, path):
658
row = state._get_row(path)
660
self.assertEqual((None, None), row)
1453
self.assertEqual((dirname, basename, file_id), cur[:3])
663
self.assertEqual((dirname, basename, kind), cur[:3])
1455
665
def test_simple_structure(self):
1456
666
state = self.create_dirstate_with_root_and_subdir()
1457
self.addCleanup(state.unlock)
1458
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1459
self.assertEntryEqual('', 'subdir', 'subdir-id', state, 'subdir', 0)
1460
self.assertEntryEqual(None, None, None, state, 'missing', 0)
1461
self.assertEntryEqual(None, None, None, state, 'missing/foo', 0)
1462
self.assertEntryEqual(None, None, None, state, 'subdir/foo', 0)
667
self.assertRowEqual('', '', 'directory', state, '')
668
self.assertRowEqual('', 'subdir', 'directory', state, 'subdir')
669
self.assertRowEqual(None, None, None, state, 'missing')
670
self.assertRowEqual(None, None, None, state, 'missing/foo')
671
self.assertRowEqual(None, None, None, state, 'subdir/foo')
1464
673
def test_complex_structure_exists(self):
1465
674
state = self.create_complex_dirstate()
1466
self.addCleanup(state.unlock)
1467
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1468
self.assertEntryEqual('', 'a', 'a-dir', state, 'a', 0)
1469
self.assertEntryEqual('', 'b', 'b-dir', state, 'b', 0)
1470
self.assertEntryEqual('', 'c', 'c-file', state, 'c', 0)
1471
self.assertEntryEqual('', 'd', 'd-file', state, 'd', 0)
1472
self.assertEntryEqual('a', 'e', 'e-dir', state, 'a/e', 0)
1473
self.assertEntryEqual('a', 'f', 'f-file', state, 'a/f', 0)
1474
self.assertEntryEqual('b', 'g', 'g-file', state, 'b/g', 0)
1475
self.assertEntryEqual('b', 'h\xc3\xa5', 'h-\xc3\xa5-file', state,
675
self.assertRowEqual('', '', 'directory', state, '')
676
self.assertRowEqual('', 'a', 'directory', state, 'a')
677
self.assertRowEqual('', 'b', 'directory', state, 'b')
678
self.assertRowEqual('', 'c', 'file', state, 'c')
679
self.assertRowEqual('', 'd', 'file', state, 'd')
680
self.assertRowEqual('a', 'e', 'directory', state, 'a/e')
681
self.assertRowEqual('a', 'f', 'file', state, 'a/f')
682
self.assertRowEqual('b', 'g', 'file', state, 'b/g')
683
self.assertRowEqual('b', 'h\xc3\xa5', 'file', state, 'b/h\xc3\xa5')
1478
685
def test_complex_structure_missing(self):
1479
686
state = self.create_complex_dirstate()
1480
self.addCleanup(state.unlock)
1481
self.assertEntryEqual(None, None, None, state, '_', 0)
1482
self.assertEntryEqual(None, None, None, state, '_\xc3\xa5', 0)
1483
self.assertEntryEqual(None, None, None, state, 'a/b', 0)
1484
self.assertEntryEqual(None, None, None, state, 'c/d', 0)
687
self.assertRowEqual(None, None, None, state, '_')
688
self.assertRowEqual(None, None, None, state, '_\xc3\xa5')
689
self.assertRowEqual(None, None, None, state, 'a/b')
690
self.assertRowEqual(None, None, None, state, 'c/d')
1486
def test_get_entry_uninitialized(self):
1487
"""Calling get_entry will load data if it needs to"""
692
def test_get_row_uninitialized(self):
693
"""Calling get_row will load data if it needs to"""
1488
694
state = self.create_dirstate_with_root()
1494
697
state = dirstate.DirState.on_file('dirstate')
1497
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1498
state._header_state)
1499
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1500
state._dirblock_state)
1501
self.assertEntryEqual('', '', 'a-root-value', state, '', 0)
1506
class TestIterChildEntries(TestCaseWithDirState):
1508
def create_dirstate_with_two_trees(self):
1509
"""This dirstate contains multiple files and directories.
1519
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1521
Notice that a/e is an empty directory.
1523
There is one parent tree, which has the same shape with the following variations:
1524
b/g in the parent is gone.
1525
b/h in the parent has a different id
1526
b/i is new in the parent
1527
c is renamed to b/j in the parent
1529
:return: The dirstate, still write-locked.
1531
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1532
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1533
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1534
root_entry = ('', '', 'a-root-value'), [
1535
('d', '', 0, False, packed_stat),
1536
('d', '', 0, False, 'parent-revid'),
1538
a_entry = ('', 'a', 'a-dir'), [
1539
('d', '', 0, False, packed_stat),
1540
('d', '', 0, False, 'parent-revid'),
1542
b_entry = ('', 'b', 'b-dir'), [
1543
('d', '', 0, False, packed_stat),
1544
('d', '', 0, False, 'parent-revid'),
1546
c_entry = ('', 'c', 'c-file'), [
1547
('f', null_sha, 10, False, packed_stat),
1548
('r', 'b/j', 0, False, ''),
1550
d_entry = ('', 'd', 'd-file'), [
1551
('f', null_sha, 20, False, packed_stat),
1552
('f', 'd', 20, False, 'parent-revid'),
1554
e_entry = ('a', 'e', 'e-dir'), [
1555
('d', '', 0, False, packed_stat),
1556
('d', '', 0, False, 'parent-revid'),
1558
f_entry = ('a', 'f', 'f-file'), [
1559
('f', null_sha, 30, False, packed_stat),
1560
('f', 'f', 20, False, 'parent-revid'),
1562
g_entry = ('b', 'g', 'g-file'), [
1563
('f', null_sha, 30, False, packed_stat),
1564
NULL_PARENT_DETAILS,
1566
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1567
('f', null_sha, 40, False, packed_stat),
1568
NULL_PARENT_DETAILS,
1570
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1571
NULL_PARENT_DETAILS,
1572
('f', 'h', 20, False, 'parent-revid'),
1574
i_entry = ('b', 'i', 'i-file'), [
1575
NULL_PARENT_DETAILS,
1576
('f', 'h', 20, False, 'parent-revid'),
1578
j_entry = ('b', 'j', 'c-file'), [
1579
('r', 'c', 0, False, ''),
1580
('f', 'j', 20, False, 'parent-revid'),
1583
dirblocks.append(('', [root_entry]))
1584
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1585
dirblocks.append(('a', [e_entry, f_entry]))
1586
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1587
state = dirstate.DirState.initialize('dirstate')
1590
state._set_data(['parent'], dirblocks)
1594
return state, dirblocks
1596
def test_iter_children_b(self):
1597
state, dirblocks = self.create_dirstate_with_two_trees()
1598
self.addCleanup(state.unlock)
1599
expected_result = []
1600
expected_result.append(dirblocks[3][1][2]) # h2
1601
expected_result.append(dirblocks[3][1][3]) # i
1602
expected_result.append(dirblocks[3][1][4]) # j
1603
self.assertEqual(expected_result,
1604
list(state._iter_child_entries(1, 'b')))
1606
def test_iter_child_root(self):
1607
state, dirblocks = self.create_dirstate_with_two_trees()
1608
self.addCleanup(state.unlock)
1609
expected_result = []
1610
expected_result.append(dirblocks[1][1][0]) # a
1611
expected_result.append(dirblocks[1][1][1]) # b
1612
expected_result.append(dirblocks[1][1][3]) # d
1613
expected_result.append(dirblocks[2][1][0]) # e
1614
expected_result.append(dirblocks[2][1][1]) # f
1615
expected_result.append(dirblocks[3][1][2]) # h2
1616
expected_result.append(dirblocks[3][1][3]) # i
1617
expected_result.append(dirblocks[3][1][4]) # j
1618
self.assertEqual(expected_result,
1619
list(state._iter_child_entries(1, '')))
1622
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1623
"""Test that DirState adds entries in the right order."""
1625
def test_add_sorting(self):
1626
"""Add entries in lexicographical order, we get path sorted order.
1628
This tests it to a depth of 4, to make sure we don't just get it right
1629
at a single depth. 'a/a' should come before 'a-a', even though it
1630
doesn't lexicographically.
1632
dirs = ['a', 'a/a', 'a/a/a', 'a/a/a/a',
1633
'a-a', 'a/a-a', 'a/a/a-a', 'a/a/a/a-a',
1636
state = dirstate.DirState.initialize('dirstate')
1637
self.addCleanup(state.unlock)
1639
fake_stat = os.stat('dirstate')
1641
d_id = d.replace('/', '_')+'-id'
1642
file_path = d + '/f'
1643
file_id = file_path.replace('/', '_')+'-id'
1644
state.add(d, d_id, 'directory', fake_stat, null_sha)
1645
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1647
expected = ['', '', 'a',
1648
'a/a', 'a/a/a', 'a/a/a/a',
1649
'a/a/a/a-a', 'a/a/a-a', 'a/a-a', 'a-a',
1651
split = lambda p:p.split('/')
1652
self.assertEqual(sorted(expected, key=split), expected)
1653
dirblock_names = [d[0] for d in state._dirblocks]
1654
self.assertEqual(expected, dirblock_names)
1656
def test_set_parent_trees_correct_order(self):
1657
"""After calling set_parent_trees() we should maintain the order."""
1658
dirs = ['a', 'a-a', 'a/a']
1660
state = dirstate.DirState.initialize('dirstate')
1661
self.addCleanup(state.unlock)
1663
fake_stat = os.stat('dirstate')
1665
d_id = d.replace('/', '_')+'-id'
1666
file_path = d + '/f'
1667
file_id = file_path.replace('/', '_')+'-id'
1668
state.add(d, d_id, 'directory', fake_stat, null_sha)
1669
state.add(file_path, file_id, 'file', fake_stat, null_sha)
1671
expected = ['', '', 'a', 'a/a', 'a-a']
1672
dirblock_names = [d[0] for d in state._dirblocks]
1673
self.assertEqual(expected, dirblock_names)
1675
# *really* cheesy way to just get an empty tree
1676
repo = self.make_repository('repo')
1677
empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1678
state.set_parent_trees([('null:', empty_tree)], [])
1680
dirblock_names = [d[0] for d in state._dirblocks]
1681
self.assertEqual(expected, dirblock_names)
1684
class InstrumentedDirState(dirstate.DirState):
1685
"""An DirState with instrumented sha1 functionality."""
1687
def __init__(self, path, sha1_provider):
1688
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1689
self._time_offset = 0
1691
# member is dynamically set in DirState.__init__ to turn on trace
1692
self._sha1_provider = sha1_provider
1693
self._sha1_file = self._sha1_file_and_log
1695
def _sha_cutoff_time(self):
1696
timestamp = super(InstrumentedDirState, self)._sha_cutoff_time()
1697
self._cutoff_time = timestamp + self._time_offset
1699
def _sha1_file_and_log(self, abspath):
1700
self._log.append(('sha1', abspath))
1701
return self._sha1_provider.sha1(abspath)
1703
def _read_link(self, abspath, old_link):
1704
self._log.append(('read_link', abspath, old_link))
1705
return super(InstrumentedDirState, self)._read_link(abspath, old_link)
1707
def _lstat(self, abspath, entry):
1708
self._log.append(('lstat', abspath))
1709
return super(InstrumentedDirState, self)._lstat(abspath, entry)
1711
def _is_executable(self, mode, old_executable):
1712
self._log.append(('is_exec', mode, old_executable))
1713
return super(InstrumentedDirState, self)._is_executable(mode,
1716
def adjust_time(self, secs):
1717
"""Move the clock forward or back.
1719
:param secs: The amount to adjust the clock by. Positive values make it
1720
seem as if we are in the future, negative values make it seem like we
1723
self._time_offset += secs
1724
self._cutoff_time = None
1727
class _FakeStat(object):
1728
"""A class with the same attributes as a real stat result."""
1730
def __init__(self, size, mtime, ctime, dev, ino, mode):
1732
self.st_mtime = mtime
1733
self.st_ctime = ctime
1740
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1741
st.st_ino, st.st_mode)
1744
class TestPackStat(tests.TestCaseWithTransport):
1746
def assertPackStat(self, expected, stat_value):
1747
"""Check the packed and serialized form of a stat value."""
1748
self.assertEqual(expected, dirstate.pack_stat(stat_value))
1750
def test_pack_stat_int(self):
1751
st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
1752
# Make sure that all parameters have an impact on the packed stat.
1753
self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1756
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1757
st.st_mtime = 1172758620
1759
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1760
st.st_ctime = 1172758630
1762
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1765
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1766
st.st_ino = 6499540L
1768
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1769
st.st_mode = 0100744
1771
self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1773
def test_pack_stat_float(self):
1774
"""On some platforms mtime and ctime are floats.
1776
Make sure we don't get warnings or errors, and that we ignore changes <
1779
st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1780
777L, 6499538L, 0100644)
1781
# These should all be the same as the integer counterparts
1782
self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1783
st.st_mtime = 1172758620.0
1785
self.assertPackStat('AAAbWEXm4FxF5uBZAAADCQBjLNIAAIGk', st)
1786
st.st_ctime = 1172758630.0
1788
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1789
# fractional seconds are discarded, so no change from above
1790
st.st_mtime = 1172758620.453
1791
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1792
st.st_ctime = 1172758630.228
1793
self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1796
class TestBisect(TestCaseWithDirState):
1797
"""Test the ability to bisect into the disk format."""
1799
def assertBisect(self, expected_map, map_keys, state, paths):
1800
"""Assert that bisecting for paths returns the right result.
1802
:param expected_map: A map from key => entry value
1803
:param map_keys: The keys to expect for each path
1804
:param state: The DirState object.
1805
:param paths: A list of paths, these will automatically be split into
1806
(dir, name) tuples, and sorted according to how _bisect
1809
result = state._bisect(paths)
1810
# For now, results are just returned in whatever order we read them.
1811
# We could sort by (dir, name, file_id) or something like that, but in
1812
# the end it would still be fairly arbitrary, and we don't want the
1813
# extra overhead if we can avoid it. So sort everything to make sure
1815
self.assertEqual(len(map_keys), len(paths))
1817
for path, keys in zip(paths, map_keys):
1819
# This should not be present in the output
1821
expected[path] = sorted(expected_map[k] for k in keys)
1823
# The returned values are just arranged randomly based on when they
1824
# were read, for testing, make sure it is properly sorted.
1828
self.assertEqual(expected, result)
1830
def assertBisectDirBlocks(self, expected_map, map_keys, state, paths):
1831
"""Assert that bisecting for dirbblocks returns the right result.
1833
:param expected_map: A map from key => expected values
1834
:param map_keys: A nested list of paths we expect to be returned.
1835
Something like [['a', 'b', 'f'], ['b/c', 'b/d']]
1836
:param state: The DirState object.
1837
:param paths: A list of directories
1839
result = state._bisect_dirblocks(paths)
1840
self.assertEqual(len(map_keys), len(paths))
1842
for path, keys in zip(paths, map_keys):
1844
# This should not be present in the output
1846
expected[path] = sorted(expected_map[k] for k in keys)
1850
self.assertEqual(expected, result)
1852
def assertBisectRecursive(self, expected_map, map_keys, state, paths):
1853
"""Assert the return value of a recursive bisection.
1855
:param expected_map: A map from key => entry value
1856
:param map_keys: A list of paths we expect to be returned.
1857
Something like ['a', 'b', 'f', 'b/d', 'b/d2']
1858
:param state: The DirState object.
1859
:param paths: A list of files and directories. It will be broken up
1860
into (dir, name) pairs and sorted before calling _bisect_recursive.
1863
for key in map_keys:
1864
entry = expected_map[key]
1865
dir_name_id, trees_info = entry
1866
expected[dir_name_id] = trees_info
1868
result = state._bisect_recursive(paths)
1870
self.assertEqual(expected, result)
1872
def test_bisect_each(self):
1873
"""Find a single record using bisect."""
1874
tree, state, expected = self.create_basic_dirstate()
1876
# Bisect should return the rows for the specified files.
1877
self.assertBisect(expected, [['']], state, [''])
1878
self.assertBisect(expected, [['a']], state, ['a'])
1879
self.assertBisect(expected, [['b']], state, ['b'])
1880
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1881
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1882
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1883
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1884
self.assertBisect(expected, [['f']], state, ['f'])
1886
def test_bisect_multi(self):
1887
"""Bisect can be used to find multiple records at the same time."""
1888
tree, state, expected = self.create_basic_dirstate()
1889
# Bisect should be capable of finding multiple entries at the same time
1890
self.assertBisect(expected, [['a'], ['b'], ['f']],
1891
state, ['a', 'b', 'f'])
1892
self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1893
state, ['f', 'b/d', 'b/d/e'])
1894
self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1895
state, ['b', 'b-c', 'b/c'])
1897
def test_bisect_one_page(self):
1898
"""Test bisect when there is only 1 page to read"""
1899
tree, state, expected = self.create_basic_dirstate()
1900
state._bisect_page_size = 5000
1901
self.assertBisect(expected,[['']], state, [''])
1902
self.assertBisect(expected,[['a']], state, ['a'])
1903
self.assertBisect(expected,[['b']], state, ['b'])
1904
self.assertBisect(expected,[['b/c']], state, ['b/c'])
1905
self.assertBisect(expected,[['b/d']], state, ['b/d'])
1906
self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1907
self.assertBisect(expected,[['b-c']], state, ['b-c'])
1908
self.assertBisect(expected,[['f']], state, ['f'])
1909
self.assertBisect(expected,[['a'], ['b'], ['f']],
1910
state, ['a', 'b', 'f'])
1911
self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
1912
state, ['b/d', 'b/d/e', 'f'])
1913
self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1914
state, ['b', 'b/c', 'b-c'])
1916
def test_bisect_duplicate_paths(self):
1917
"""When bisecting for a path, handle multiple entries."""
1918
tree, state, expected = self.create_duplicated_dirstate()
1920
# Now make sure that both records are properly returned.
1921
self.assertBisect(expected, [['']], state, [''])
1922
self.assertBisect(expected, [['a', 'a2']], state, ['a'])
1923
self.assertBisect(expected, [['b', 'b2']], state, ['b'])
1924
self.assertBisect(expected, [['b/c', 'b/c2']], state, ['b/c'])
1925
self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1926
self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1928
self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1929
self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1931
def test_bisect_page_size_too_small(self):
1932
"""If the page size is too small, we will auto increase it."""
1933
tree, state, expected = self.create_basic_dirstate()
1934
state._bisect_page_size = 50
1935
self.assertBisect(expected, [None], state, ['b/e'])
1936
self.assertBisect(expected, [['a']], state, ['a'])
1937
self.assertBisect(expected, [['b']], state, ['b'])
1938
self.assertBisect(expected, [['b/c']], state, ['b/c'])
1939
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1940
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1941
self.assertBisect(expected, [['b-c']], state, ['b-c'])
1942
self.assertBisect(expected, [['f']], state, ['f'])
1944
def test_bisect_missing(self):
1945
"""Test that bisect return None if it cannot find a path."""
1946
tree, state, expected = self.create_basic_dirstate()
1947
self.assertBisect(expected, [None], state, ['foo'])
1948
self.assertBisect(expected, [None], state, ['b/foo'])
1949
self.assertBisect(expected, [None], state, ['bar/foo'])
1950
self.assertBisect(expected, [None], state, ['b-c/foo'])
1952
self.assertBisect(expected, [['a'], None, ['b/d']],
1953
state, ['a', 'foo', 'b/d'])
1955
def test_bisect_rename(self):
1956
"""Check that we find a renamed row."""
1957
tree, state, expected = self.create_renamed_dirstate()
1959
# Search for the pre and post renamed entries
1960
self.assertBisect(expected, [['a']], state, ['a'])
1961
self.assertBisect(expected, [['b/g']], state, ['b/g'])
1962
self.assertBisect(expected, [['b/d']], state, ['b/d'])
1963
self.assertBisect(expected, [['h']], state, ['h'])
1965
# What about b/d/e? shouldn't that also get 2 directory entries?
1966
self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1967
self.assertBisect(expected, [['h/e']], state, ['h/e'])
1969
def test_bisect_dirblocks(self):
1970
tree, state, expected = self.create_duplicated_dirstate()
1971
self.assertBisectDirBlocks(expected,
1972
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
1974
self.assertBisectDirBlocks(expected,
1975
[['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
1976
self.assertBisectDirBlocks(expected,
1977
[['b/d/e', 'b/d/e2']], state, ['b/d'])
1978
self.assertBisectDirBlocks(expected,
1979
[['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
1980
['b/c', 'b/c2', 'b/d', 'b/d2'],
1981
['b/d/e', 'b/d/e2'],
1982
], state, ['', 'b', 'b/d'])
1984
def test_bisect_dirblocks_missing(self):
1985
tree, state, expected = self.create_basic_dirstate()
1986
self.assertBisectDirBlocks(expected, [['b/d/e'], None],
1987
state, ['b/d', 'b/e'])
1988
# Files don't show up in this search
1989
self.assertBisectDirBlocks(expected, [None], state, ['a'])
1990
self.assertBisectDirBlocks(expected, [None], state, ['b/c'])
1991
self.assertBisectDirBlocks(expected, [None], state, ['c'])
1992
self.assertBisectDirBlocks(expected, [None], state, ['b/d/e'])
1993
self.assertBisectDirBlocks(expected, [None], state, ['f'])
1995
def test_bisect_recursive_each(self):
1996
tree, state, expected = self.create_basic_dirstate()
1997
self.assertBisectRecursive(expected, ['a'], state, ['a'])
1998
self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
1999
self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2000
self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2001
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2003
self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2005
self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
2009
def test_bisect_recursive_multiple(self):
2010
tree, state, expected = self.create_basic_dirstate()
2011
self.assertBisectRecursive(expected, ['a', 'b/c'], state, ['a', 'b/c'])
2012
self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2013
state, ['b/d', 'b/d/e'])
2015
def test_bisect_recursive_missing(self):
2016
tree, state, expected = self.create_basic_dirstate()
2017
self.assertBisectRecursive(expected, [], state, ['d'])
2018
self.assertBisectRecursive(expected, [], state, ['b/e'])
2019
self.assertBisectRecursive(expected, [], state, ['g'])
2020
self.assertBisectRecursive(expected, ['a'], state, ['a', 'g'])
2022
def test_bisect_recursive_renamed(self):
2023
tree, state, expected = self.create_renamed_dirstate()
2025
# Looking for either renamed item should find the other
2026
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['a'])
2027
self.assertBisectRecursive(expected, ['a', 'b/g'], state, ['b/g'])
2028
# Looking in the containing directory should find the rename target,
2029
# and anything in a subdir of the renamed target.
2030
self.assertBisectRecursive(expected, ['a', 'b', 'b/c', 'b/d',
2031
'b/d/e', 'b/g', 'h', 'h/e'],
2035
class TestDirstateValidation(TestCaseWithDirState):
2037
def test_validate_correct_dirstate(self):
2038
state = self.create_complex_dirstate()
2041
# and make sure we can also validate with a read lock
2048
def test_dirblock_not_sorted(self):
2049
tree, state, expected = self.create_renamed_dirstate()
2050
state._read_dirblocks_if_needed()
2051
last_dirblock = state._dirblocks[-1]
2052
# we're appending to the dirblock, but this name comes before some of
2053
# the existing names; that's wrong
2054
last_dirblock[1].append(
2055
(('h', 'aaaa', 'a-id'),
2056
[('a', '', 0, False, ''),
2057
('a', '', 0, False, '')]))
2058
e = self.assertRaises(AssertionError,
2060
self.assertContainsRe(str(e), 'not sorted')
2062
def test_dirblock_name_mismatch(self):
2063
tree, state, expected = self.create_renamed_dirstate()
2064
state._read_dirblocks_if_needed()
2065
last_dirblock = state._dirblocks[-1]
2066
# add an entry with the wrong directory name
2067
last_dirblock[1].append(
2069
[('a', '', 0, False, ''),
2070
('a', '', 0, False, '')]))
2071
e = self.assertRaises(AssertionError,
2073
self.assertContainsRe(str(e),
2074
"doesn't match directory name")
2076
def test_dirblock_missing_rename(self):
2077
tree, state, expected = self.create_renamed_dirstate()
2078
state._read_dirblocks_if_needed()
2079
last_dirblock = state._dirblocks[-1]
2080
# make another entry for a-id, without a correct 'r' pointer to
2081
# the real occurrence in the working tree
2082
last_dirblock[1].append(
2083
(('h', 'z', 'a-id'),
2084
[('a', '', 0, False, ''),
2085
('a', '', 0, False, '')]))
2086
e = self.assertRaises(AssertionError,
2088
self.assertContainsRe(str(e),
2089
'file a-id is absent in row')
2092
class TestDirstateTreeReference(TestCaseWithDirState):
2094
def test_reference_revision_is_none(self):
2095
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2096
subtree = self.make_branch_and_tree('tree/subtree',
2097
format='dirstate-with-subtree')
2098
subtree.set_root_id('subtree')
2099
tree.add_reference(subtree)
2101
state = dirstate.DirState.from_tree(tree, 'dirstate')
2102
key = ('', 'subtree', 'subtree')
2103
expected = ('', [(key,
2104
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2107
self.assertEqual(expected, state._find_block(key))
2112
class TestDiscardMergeParents(TestCaseWithDirState):
2114
def test_discard_no_parents(self):
2115
# This should be a no-op
2116
state = self.create_empty_dirstate()
2117
self.addCleanup(state.unlock)
2118
state._discard_merge_parents()
2121
def test_discard_one_parent(self):
2123
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2124
root_entry_direntry = ('', '', 'a-root-value'), [
2125
('d', '', 0, False, packed_stat),
2126
('d', '', 0, False, packed_stat),
2129
dirblocks.append(('', [root_entry_direntry]))
2130
dirblocks.append(('', []))
2132
state = self.create_empty_dirstate()
2133
self.addCleanup(state.unlock)
2134
state._set_data(['parent-id'], dirblocks[:])
2137
state._discard_merge_parents()
2139
self.assertEqual(dirblocks, state._dirblocks)
2141
def test_discard_simple(self):
2143
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2144
root_entry_direntry = ('', '', 'a-root-value'), [
2145
('d', '', 0, False, packed_stat),
2146
('d', '', 0, False, packed_stat),
2147
('d', '', 0, False, packed_stat),
2149
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2150
('d', '', 0, False, packed_stat),
2151
('d', '', 0, False, packed_stat),
2154
dirblocks.append(('', [root_entry_direntry]))
2155
dirblocks.append(('', []))
2157
state = self.create_empty_dirstate()
2158
self.addCleanup(state.unlock)
2159
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2162
# This should strip of the extra column
2163
state._discard_merge_parents()
2165
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2166
self.assertEqual(expected_dirblocks, state._dirblocks)
2168
def test_discard_absent(self):
2169
"""If entries are only in a merge, discard should remove the entries"""
2170
null_stat = dirstate.DirState.NULLSTAT
2171
present_dir = ('d', '', 0, False, null_stat)
2172
present_file = ('f', '', 0, False, null_stat)
2173
absent = dirstate.DirState.NULL_PARENT_DETAILS
2174
root_key = ('', '', 'a-root-value')
2175
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2176
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2177
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2178
('', [(file_in_merged_key,
2179
[absent, absent, present_file]),
2181
[present_file, present_file, present_file]),
2185
state = self.create_empty_dirstate()
2186
self.addCleanup(state.unlock)
2187
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2190
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2191
('', [(file_in_root_key,
2192
[present_file, present_file]),
2195
state._discard_merge_parents()
2197
self.assertEqual(exp_dirblocks, state._dirblocks)
2199
def test_discard_renamed(self):
2200
null_stat = dirstate.DirState.NULLSTAT
2201
present_dir = ('d', '', 0, False, null_stat)
2202
present_file = ('f', '', 0, False, null_stat)
2203
absent = dirstate.DirState.NULL_PARENT_DETAILS
2204
root_key = ('', '', 'a-root-value')
2205
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2206
# Renamed relative to parent
2207
file_rename_s_key = ('', 'file-s', 'b-file-id')
2208
file_rename_t_key = ('', 'file-t', 'b-file-id')
2209
# And one that is renamed between the parents, but absent in this
2210
key_in_1 = ('', 'file-in-1', 'c-file-id')
2211
key_in_2 = ('', 'file-in-2', 'c-file-id')
2214
('', [(root_key, [present_dir, present_dir, present_dir])]),
2216
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2218
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2220
[present_file, present_file, present_file]),
2222
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2224
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2228
('', [(root_key, [present_dir, present_dir])]),
2229
('', [(key_in_1, [absent, present_file]),
2230
(file_in_root_key, [present_file, present_file]),
2231
(file_rename_t_key, [present_file, absent]),
2234
state = self.create_empty_dirstate()
2235
self.addCleanup(state.unlock)
2236
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2239
state._discard_merge_parents()
2241
self.assertEqual(exp_dirblocks, state._dirblocks)
2243
def test_discard_all_subdir(self):
2244
null_stat = dirstate.DirState.NULLSTAT
2245
present_dir = ('d', '', 0, False, null_stat)
2246
present_file = ('f', '', 0, False, null_stat)
2247
absent = dirstate.DirState.NULL_PARENT_DETAILS
2248
root_key = ('', '', 'a-root-value')
2249
subdir_key = ('', 'sub', 'dir-id')
2250
child1_key = ('sub', 'child1', 'child1-id')
2251
child2_key = ('sub', 'child2', 'child2-id')
2252
child3_key = ('sub', 'child3', 'child3-id')
2255
('', [(root_key, [present_dir, present_dir, present_dir])]),
2256
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2257
('sub', [(child1_key, [absent, absent, present_file]),
2258
(child2_key, [absent, absent, present_file]),
2259
(child3_key, [absent, absent, present_file]),
2263
('', [(root_key, [present_dir, present_dir])]),
2264
('', [(subdir_key, [present_dir, present_dir])]),
2267
state = self.create_empty_dirstate()
2268
self.addCleanup(state.unlock)
2269
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2272
state._discard_merge_parents()
2274
self.assertEqual(exp_dirblocks, state._dirblocks)
2277
class Test_InvEntryToDetails(tests.TestCase):
2279
def assertDetails(self, expected, inv_entry):
2280
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2281
self.assertEqual(expected, details)
2282
# details should always allow join() and always be a plain str when
2284
(minikind, fingerprint, size, executable, tree_data) = details
2285
self.assertIsInstance(minikind, str)
2286
self.assertIsInstance(fingerprint, str)
2287
self.assertIsInstance(tree_data, str)
2289
def test_unicode_symlink(self):
2290
inv_entry = inventory.InventoryLink('link-file-id',
2291
u'nam\N{Euro Sign}e',
2293
inv_entry.revision = 'link-revision-id'
2294
target = u'link-targ\N{Euro Sign}t'
2295
inv_entry.symlink_target = target
2296
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2297
'link-revision-id'), inv_entry)
2300
class TestSHA1Provider(tests.TestCaseInTempDir):
2302
def test_sha1provider_is_an_interface(self):
2303
p = dirstate.SHA1Provider()
2304
self.assertRaises(NotImplementedError, p.sha1, "foo")
2305
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2307
def test_defaultsha1provider_sha1(self):
2308
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2309
self.build_tree_contents([('foo', text)])
2310
expected_sha = osutils.sha_string(text)
2311
p = dirstate.DefaultSHA1Provider()
2312
self.assertEqual(expected_sha, p.sha1('foo'))
2314
def test_defaultsha1provider_stat_and_sha1(self):
2315
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2316
self.build_tree_contents([('foo', text)])
2317
expected_sha = osutils.sha_string(text)
2318
p = dirstate.DefaultSHA1Provider()
2319
statvalue, sha1 = p.stat_and_sha1('foo')
2320
self.assertTrue(len(statvalue) >= 10)
2321
self.assertEqual(len(text), statvalue.st_size)
2322
self.assertEqual(expected_sha, sha1)
698
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY, state._header_state)
699
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY, state._dirblock_state)
700
self.assertRowEqual('', '', 'directory', state, '')