1
# Copyright (C) 2011, 2016 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24
revision as _mod_revision,
28
from bzrlib.tests.scenarios import load_tests_apply_scenarios
29
from bzrlib.tests.per_repository_vf import (
30
TestCaseWithRepository,
31
all_repository_vf_format_scenarios,
35
load_tests = load_tests_apply_scenarios
38
class FileIdInvolvedWGhosts(TestCaseWithRepository):
40
scenarios = all_repository_vf_format_scenarios()
42
def create_branch_with_ghost_text(self):
43
builder = self.make_branch_builder('ghost')
44
builder.build_snapshot('A-id', None, [
45
('add', ('', 'root-id', 'directory', None)),
46
('add', ('a', 'a-file-id', 'file', 'some content\n'))])
47
b = builder.get_branch()
48
old_rt = b.repository.revision_tree('A-id')
49
new_inv = inventory.mutable_inventory_from_tree(old_rt)
50
new_inv.revision_id = 'B-id'
51
new_inv['a-file-id'].revision = 'ghost-id'
52
new_rev = _mod_revision.Revision('B-id',
53
timestamp=time.time(),
55
message='Committing against a ghost',
56
committer='Joe Foo <joe@foo.com>',
58
parent_ids=('A-id', 'ghost-id'),
61
self.addCleanup(b.unlock)
62
b.repository.start_write_group()
63
b.repository.add_revision('B-id', new_rev, new_inv)
64
self.disable_commit_write_group_paranoia(b.repository)
65
b.repository.commit_write_group()
68
def disable_commit_write_group_paranoia(self, repo):
69
if isinstance(repo, remote.RemoteRepository):
70
# We can't easily disable the checks in a remote repo.
71
repo.abort_write_group()
72
raise tests.TestSkipped(
73
"repository format does not support storing revisions with "
75
pack_coll = getattr(repo, '_pack_collection', None)
76
if pack_coll is not None:
77
# Monkey-patch the pack collection instance to allow storing
78
# incomplete revisions.
79
pack_coll._check_new_inventories = lambda: []
81
def test_file_ids_include_ghosts(self):
82
b = self.create_branch_with_ghost_text()
85
{'a-file-id':set(['ghost-id'])},
86
repo.fileids_altered_by_revision_ids(['B-id']))
88
def test_file_ids_uses_fallbacks(self):
89
builder = self.make_branch_builder('source',
90
format=self.bzrdir_format)
91
repo = builder.get_branch().repository
92
if not repo._format.supports_external_lookups:
93
raise tests.TestNotApplicable('format does not support stacking')
94
builder.start_series()
95
builder.build_snapshot('A-id', None, [
96
('add', ('', 'root-id', 'directory', None)),
97
('add', ('file', 'file-id', 'file', 'contents\n'))])
98
builder.build_snapshot('B-id', ['A-id'], [
99
('modify', ('file-id', 'new-content\n'))])
100
builder.build_snapshot('C-id', ['B-id'], [
101
('modify', ('file-id', 'yet more content\n'))])
102
builder.finish_series()
103
source_b = builder.get_branch()
105
self.addCleanup(source_b.unlock)
106
base = self.make_branch('base')
107
base.pull(source_b, stop_revision='B-id')
108
stacked = self.make_branch('stacked')
109
stacked.set_stacked_on_url('../base')
110
stacked.pull(source_b, stop_revision='C-id')
113
self.addCleanup(stacked.unlock)
114
repo = stacked.repository
115
keys = {'file-id': set(['A-id'])}
116
if stacked.repository.supports_rich_root():
117
keys['root-id'] = set(['A-id'])
118
self.assertEqual(keys, repo.fileids_altered_by_revision_ids(['A-id']))
121
class FileIdInvolvedBase(TestCaseWithRepository):
123
def touch(self, tree, filename):
124
# use the trees transport to not depend on the tree's location or type.
125
tree.bzrdir.root_transport.append_bytes(filename, "appended line\n")
127
def compare_tree_fileids(self, branch, old_rev, new_rev):
128
old_tree = self.branch.repository.revision_tree(old_rev)
129
new_tree = self.branch.repository.revision_tree(new_rev)
130
delta = new_tree.changes_from(old_tree)
132
l2 = [id for path, id, kind in delta.added] + \
133
[id for oldpath, newpath, id, kind, text_modified, \
134
meta_modified in delta.renamed] + \
135
[id for path, id, kind, text_modified, meta_modified in \
140
class TestFileIdInvolved(FileIdInvolvedBase):
142
scenarios = all_repository_vf_format_scenarios()
145
super(TestFileIdInvolved, self).setUp()
146
# create three branches, and merge it
148
# ,-->J------>K (branch2)
150
# A --->B --->C---->D-->G (main)
152
# '--->E---+---->F (branch1)
155
# B changes: 'a-file-id-2006-01-01-abcd'
156
# C changes: Nothing (perfect merge)
157
# D changes: 'b-file-id-2006-01-01-defg'
158
# E changes: 'file-d'
159
# F changes: 'file-d'
160
# G changes: 'b-file-id-2006-01-01-defg'
161
# J changes: 'b-file-id-2006-01-01-defg'
162
# K changes: 'c-funky<file-id>quiji%bo'
164
main_wt = self.make_branch_and_tree('main')
165
main_branch = main_wt.branch
166
self.build_tree(["main/a","main/b","main/c"])
168
main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd',
169
'b-file-id-2006-01-01-defg',
170
'c-funky<file-id>quiji%bo'])
172
main_wt.commit("Commit one", rev_id="rev-A")
173
except errors.IllegalPath:
174
# TODO: jam 20060701 Consider raising a different exception
175
# newer formats do support this, and nothin can done to
176
# correct this test - its not a bug.
177
if sys.platform == 'win32':
178
raise tests.TestSkipped('Old repository formats do not'
179
' support file ids with <> on win32')
180
# This is not a known error condition
183
#-------- end A -----------
185
bt1 = self.make_branch_and_tree('branch1')
186
bt1.pull(main_branch)
188
self.build_tree(["branch1/d"])
189
bt1.add(['d'], ['file-d'])
190
bt1.commit("branch1, Commit one", rev_id="rev-E")
192
#-------- end E -----------
194
self.touch(main_wt, "a")
195
main_wt.commit("Commit two", rev_id="rev-B")
197
#-------- end B -----------
199
bt2 = self.make_branch_and_tree('branch2')
200
bt2.pull(main_branch)
201
branch2_branch = bt2.branch
202
set_executability(bt2, 'b', True)
203
bt2.commit("branch2, Commit one", rev_id="rev-J")
205
#-------- end J -----------
207
main_wt.merge_from_branch(b1)
208
main_wt.commit("merge branch1, rev-11", rev_id="rev-C")
210
#-------- end C -----------
212
bt1.rename_one("d","e")
213
bt1.commit("branch1, commit two", rev_id="rev-F")
215
#-------- end F -----------
218
bt2.commit("branch2, commit two", rev_id="rev-K")
220
#-------- end K -----------
222
main_wt.merge_from_branch(b1)
223
self.touch(main_wt, "b")
224
# D gets some funky characters to make sure the unescaping works
225
main_wt.commit("merge branch1, rev-12", rev_id="rev-<D>")
229
main_wt.merge_from_branch(branch2_branch)
230
main_wt.commit("merge branch1, rev-22", rev_id="rev-G")
233
self.branch = main_branch
235
def test_fileids_altered_between_two_revs(self):
236
self.branch.lock_read()
237
self.addCleanup(self.branch.unlock)
238
self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"])
240
{'b-file-id-2006-01-01-defg':set(['rev-J']),
241
'c-funky<file-id>quiji%bo':set(['rev-K'])
243
self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"]))
246
{'b-file-id-2006-01-01-defg': set(['rev-<D>']),
247
'file-d': set(['rev-F']),
249
self.branch.repository.fileids_altered_by_revision_ids(['rev-<D>', 'rev-F']))
253
'b-file-id-2006-01-01-defg': set(['rev-<D>', 'rev-G', 'rev-J']),
254
'c-funky<file-id>quiji%bo': set(['rev-K']),
255
'file-d': set(['rev-F']),
257
self.branch.repository.fileids_altered_by_revision_ids(
258
['rev-<D>', 'rev-G', 'rev-F', 'rev-K', 'rev-J']))
261
{'a-file-id-2006-01-01-abcd': set(['rev-B']),
262
'b-file-id-2006-01-01-defg': set(['rev-<D>', 'rev-G', 'rev-J']),
263
'c-funky<file-id>quiji%bo': set(['rev-K']),
264
'file-d': set(['rev-F']),
266
self.branch.repository.fileids_altered_by_revision_ids(
267
['rev-G', 'rev-F', 'rev-C', 'rev-B', 'rev-<D>', 'rev-K', 'rev-J']))
269
def fileids_altered_by_revision_ids(self, revision_ids):
270
"""This is a wrapper to strip TREE_ROOT if it occurs"""
271
repo = self.branch.repository
272
root_id = self.branch.basis_tree().get_root_id()
273
result = repo.fileids_altered_by_revision_ids(revision_ids)
274
if root_id in result:
278
def test_fileids_altered_by_revision_ids(self):
279
self.branch.lock_read()
280
self.addCleanup(self.branch.unlock)
282
{'a-file-id-2006-01-01-abcd':set(['rev-A']),
283
'b-file-id-2006-01-01-defg': set(['rev-A']),
284
'c-funky<file-id>quiji%bo': set(['rev-A']),
286
self.fileids_altered_by_revision_ids(["rev-A"]))
288
{'a-file-id-2006-01-01-abcd':set(['rev-B'])
290
self.branch.repository.fileids_altered_by_revision_ids(["rev-B"]))
292
{'b-file-id-2006-01-01-defg':set(['rev-<D>'])
294
self.branch.repository.fileids_altered_by_revision_ids(["rev-<D>"]))
296
def test_fileids_involved_full_compare(self):
297
# this tests that the result of each fileid_involved calculation
298
# along a revision history selects only the fileids selected by
299
# comparing the trees - no less, and no more. This is correct
300
# because in our sample data we do not revert any file ids along
301
# the revision history.
302
self.branch.lock_read()
303
self.addCleanup(self.branch.unlock)
305
graph = self.branch.repository.get_graph()
306
history = list(graph.iter_lefthand_ancestry(self.branch.last_revision(),
307
[_mod_revision.NULL_REVISION]))
313
for start in range(0,len(history)-1):
314
start_id = history[start]
315
for end in range(start+1,len(history)):
316
end_id = history[end]
317
unique_revs = graph.find_unique_ancestors(end_id, [start_id])
318
l1 = self.branch.repository.fileids_altered_by_revision_ids(
321
l2 = self.compare_tree_fileids(self.branch, start_id, end_id)
322
self.assertEqual(l1, l2)
325
class TestFileIdInvolvedNonAscii(FileIdInvolvedBase):
327
scenarios = all_repository_vf_format_scenarios()
329
def test_utf8_file_ids_and_revision_ids(self):
330
main_wt = self.make_branch_and_tree('main')
331
main_branch = main_wt.branch
332
self.build_tree(["main/a"])
334
file_id = u'a-f\xedle-id'.encode('utf8')
335
main_wt.add(['a'], [file_id])
336
revision_id = u'r\xe9v-a'.encode('utf8')
338
main_wt.commit('a', rev_id=revision_id)
339
except errors.NonAsciiRevisionId:
340
raise tests.TestSkipped('non-ascii revision ids not supported by %s'
341
% self.repository_format)
343
repo = main_wt.branch.repository
345
self.addCleanup(repo.unlock)
346
file_ids = repo.fileids_altered_by_revision_ids([revision_id])
347
root_id = main_wt.basis_tree().get_root_id()
348
if root_id in file_ids:
349
self.assertEqual({file_id:set([revision_id]),
350
root_id:set([revision_id])
353
self.assertEqual({file_id:set([revision_id])}, file_ids)
356
class TestFileIdInvolvedSuperset(FileIdInvolvedBase):
358
scenarios = all_repository_vf_format_scenarios()
361
super(TestFileIdInvolvedSuperset, self).setUp()
364
main_wt = self.make_branch_and_tree('main')
365
main_branch = main_wt.branch
366
self.build_tree(["main/a","main/b","main/c"])
368
main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd',
369
'b-file-id-2006-01-01-defg',
370
'c-funky<file-id>quiji\'"%bo'])
372
main_wt.commit("Commit one", rev_id="rev-A")
373
except errors.IllegalPath:
374
# TODO: jam 20060701 Consider raising a different exception
375
# newer formats do support this, and nothin can done to
376
# correct this test - its not a bug.
377
if sys.platform == 'win32':
378
raise tests.TestSkipped('Old repository formats do not'
379
' support file ids with <> on win32')
380
# This is not a known error condition
383
branch2_wt = self.make_branch_and_tree('branch2')
384
branch2_wt.pull(main_branch)
385
branch2_bzrdir = branch2_wt.bzrdir
386
branch2_branch = branch2_bzrdir.open_branch()
387
set_executability(branch2_wt, 'b', True)
388
branch2_wt.commit("branch2, Commit one", rev_id="rev-J")
390
main_wt.merge_from_branch(branch2_branch)
391
set_executability(main_wt, 'b', False)
392
main_wt.commit("merge branch1, rev-22", rev_id="rev-G")
395
self.branch = main_branch
397
def test_fileid_involved_full_compare2(self):
398
# this tests that fileids_altered_by_revision_ids returns
399
# more information than compare_tree can, because it
400
# sees each change rather than the aggregate delta.
401
self.branch.lock_read()
402
self.addCleanup(self.branch.unlock)
403
graph = self.branch.repository.get_graph()
404
history = list(graph.iter_lefthand_ancestry(self.branch.last_revision(),
405
[_mod_revision.NULL_REVISION]))
409
unique_revs = graph.find_unique_ancestors(new_rev, [old_rev])
411
l1 = self.branch.repository.fileids_altered_by_revision_ids(
415
l2 = self.compare_tree_fileids(self.branch, old_rev, new_rev)
416
self.assertNotEqual(l2, l1)
417
self.assertSubset(l2, l1)
420
def set_executability(wt, path, executable=True):
421
"""Set the executable bit for the file at path in the working tree
423
os.chmod() doesn't work on windows. But TreeTransform can mark or
424
unmark a file as executable.
426
file_id = wt.path2id(path)
427
tt = transform.TreeTransform(wt)
429
tt.set_executability(executable, tt.trans_id_tree_file_id(file_id))