1
# Copyright (C) 2005-2010 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
from bzrlib.tests import per_repository
27
class FileIdInvolvedBase(per_repository.TestCaseWithRepository):
29
def touch(self, tree, filename):
30
# use the trees transport to not depend on the tree's location or type.
31
tree.bzrdir.root_transport.append_bytes(filename, "appended line\n")
33
def compare_tree_fileids(self, branch, old_rev, new_rev):
34
old_tree = self.branch.repository.revision_tree(old_rev)
35
new_tree = self.branch.repository.revision_tree(new_rev)
36
delta = new_tree.changes_from(old_tree)
38
l2 = [id for path, id, kind in delta.added] + \
39
[id for oldpath, newpath, id, kind, text_modified, \
40
meta_modified in delta.renamed] + \
41
[id for path, id, kind, text_modified, meta_modified in \
46
class TestFileIdInvolved(FileIdInvolvedBase):
49
super(TestFileIdInvolved, self).setUp()
50
# create three branches, and merge it
52
# ,-->J------>K (branch2)
54
# A --->B --->C---->D-->G (main)
56
# '--->E---+---->F (branch1)
59
# B changes: 'a-file-id-2006-01-01-abcd'
60
# C changes: Nothing (perfect merge)
61
# D changes: 'b-file-id-2006-01-01-defg'
64
# G changes: 'b-file-id-2006-01-01-defg'
65
# J changes: 'b-file-id-2006-01-01-defg'
66
# K changes: 'c-funky<file-id>quiji%bo'
68
main_wt = self.make_branch_and_tree('main')
69
main_branch = main_wt.branch
70
self.build_tree(["main/a","main/b","main/c"])
72
main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd',
73
'b-file-id-2006-01-01-defg',
74
'c-funky<file-id>quiji%bo'])
76
main_wt.commit("Commit one", rev_id="rev-A")
77
except errors.IllegalPath:
78
# TODO: jam 20060701 Consider raising a different exception
79
# newer formats do support this, and nothin can done to
80
# correct this test - its not a bug.
81
if sys.platform == 'win32':
82
raise tests.TestSkipped('Old repository formats do not'
83
' support file ids with <> on win32')
84
# This is not a known error condition
87
#-------- end A -----------
89
bt1 = self.make_branch_and_tree('branch1')
92
self.build_tree(["branch1/d"])
93
bt1.add(['d'], ['file-d'])
94
bt1.commit("branch1, Commit one", rev_id="rev-E")
96
#-------- end E -----------
98
self.touch(main_wt, "a")
99
main_wt.commit("Commit two", rev_id="rev-B")
101
#-------- end B -----------
103
bt2 = self.make_branch_and_tree('branch2')
104
bt2.pull(main_branch)
105
branch2_branch = bt2.branch
106
set_executability(bt2, 'b', True)
107
bt2.commit("branch2, Commit one", rev_id="rev-J")
109
#-------- end J -----------
111
main_wt.merge_from_branch(b1)
112
main_wt.commit("merge branch1, rev-11", rev_id="rev-C")
114
#-------- end C -----------
116
bt1.rename_one("d","e")
117
bt1.commit("branch1, commit two", rev_id="rev-F")
119
#-------- end F -----------
122
bt2.commit("branch2, commit two", rev_id="rev-K")
124
#-------- end K -----------
126
main_wt.merge_from_branch(b1)
127
self.touch(main_wt, "b")
128
# D gets some funky characters to make sure the unescaping works
129
main_wt.commit("merge branch1, rev-12", rev_id="rev-<D>")
133
main_wt.merge_from_branch(branch2_branch)
134
main_wt.commit("merge branch1, rev-22", rev_id="rev-G")
137
self.branch = main_branch
139
def test_fileids_altered_between_two_revs(self):
140
self.branch.lock_read()
141
self.addCleanup(self.branch.unlock)
142
self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"])
144
{'b-file-id-2006-01-01-defg':set(['rev-J']),
145
'c-funky<file-id>quiji%bo':set(['rev-K'])
147
self.branch.repository.fileids_altered_by_revision_ids(["rev-J","rev-K"]))
150
{'b-file-id-2006-01-01-defg': set(['rev-<D>']),
151
'file-d': set(['rev-F']),
153
self.branch.repository.fileids_altered_by_revision_ids(['rev-<D>', 'rev-F']))
157
'b-file-id-2006-01-01-defg': set(['rev-<D>', 'rev-G', 'rev-J']),
158
'c-funky<file-id>quiji%bo': set(['rev-K']),
159
'file-d': set(['rev-F']),
161
self.branch.repository.fileids_altered_by_revision_ids(
162
['rev-<D>', 'rev-G', 'rev-F', 'rev-K', 'rev-J']))
165
{'a-file-id-2006-01-01-abcd': set(['rev-B']),
166
'b-file-id-2006-01-01-defg': set(['rev-<D>', 'rev-G', 'rev-J']),
167
'c-funky<file-id>quiji%bo': set(['rev-K']),
168
'file-d': set(['rev-F']),
170
self.branch.repository.fileids_altered_by_revision_ids(
171
['rev-G', 'rev-F', 'rev-C', 'rev-B', 'rev-<D>', 'rev-K', 'rev-J']))
173
def fileids_altered_by_revision_ids(self, revision_ids):
174
"""This is a wrapper to strip TREE_ROOT if it occurs"""
175
repo = self.branch.repository
176
root_id = self.branch.basis_tree().get_root_id()
177
result = repo.fileids_altered_by_revision_ids(revision_ids)
178
if root_id in result:
182
def test_fileids_altered_by_revision_ids(self):
183
self.branch.lock_read()
184
self.addCleanup(self.branch.unlock)
186
{'a-file-id-2006-01-01-abcd':set(['rev-A']),
187
'b-file-id-2006-01-01-defg': set(['rev-A']),
188
'c-funky<file-id>quiji%bo': set(['rev-A']),
190
self.fileids_altered_by_revision_ids(["rev-A"]))
192
{'a-file-id-2006-01-01-abcd':set(['rev-B'])
194
self.branch.repository.fileids_altered_by_revision_ids(["rev-B"]))
196
{'b-file-id-2006-01-01-defg':set(['rev-<D>'])
198
self.branch.repository.fileids_altered_by_revision_ids(["rev-<D>"]))
200
def test_fileids_involved_full_compare(self):
201
# this tests that the result of each fileid_involved calculation
202
# along a revision history selects only the fileids selected by
203
# comparing the trees - no less, and no more. This is correct
204
# because in our sample data we do not revert any file ids along
205
# the revision history.
206
self.branch.lock_read()
207
self.addCleanup(self.branch.unlock)
209
history = self.branch.revision_history( )
211
if len(history) < 2: return
213
graph = self.branch.repository.get_graph()
214
for start in range(0,len(history)-1):
215
start_id = history[start]
216
for end in range(start+1,len(history)):
217
end_id = history[end]
218
unique_revs = graph.find_unique_ancestors(end_id, [start_id])
219
l1 = self.branch.repository.fileids_altered_by_revision_ids(
222
l2 = self.compare_tree_fileids(self.branch, start_id, end_id)
223
self.assertEquals(l1, l2)
226
class TestFileIdInvolvedNonAscii(FileIdInvolvedBase):
228
def test_utf8_file_ids_and_revision_ids(self):
229
main_wt = self.make_branch_and_tree('main')
230
main_branch = main_wt.branch
231
self.build_tree(["main/a"])
233
file_id = u'a-f\xedle-id'.encode('utf8')
234
main_wt.add(['a'], [file_id])
235
revision_id = u'r\xe9v-a'.encode('utf8')
237
main_wt.commit('a', rev_id=revision_id)
238
except errors.NonAsciiRevisionId:
239
raise tests.TestSkipped('non-ascii revision ids not supported by %s'
240
% self.repository_format)
242
repo = main_wt.branch.repository
244
self.addCleanup(repo.unlock)
245
file_ids = repo.fileids_altered_by_revision_ids([revision_id])
246
root_id = main_wt.basis_tree().get_root_id()
247
if root_id in file_ids:
248
self.assertEqual({file_id:set([revision_id]),
249
root_id:set([revision_id])
252
self.assertEqual({file_id:set([revision_id])}, file_ids)
255
class TestFileIdInvolvedSuperset(FileIdInvolvedBase):
258
super(TestFileIdInvolvedSuperset, self).setUp()
261
main_wt = self.make_branch_and_tree('main')
262
main_branch = main_wt.branch
263
self.build_tree(["main/a","main/b","main/c"])
265
main_wt.add(['a', 'b', 'c'], ['a-file-id-2006-01-01-abcd',
266
'b-file-id-2006-01-01-defg',
267
'c-funky<file-id>quiji\'"%bo'])
269
main_wt.commit("Commit one", rev_id="rev-A")
270
except errors.IllegalPath:
271
# TODO: jam 20060701 Consider raising a different exception
272
# newer formats do support this, and nothin can done to
273
# correct this test - its not a bug.
274
if sys.platform == 'win32':
275
raise tests.TestSkipped('Old repository formats do not'
276
' support file ids with <> on win32')
277
# This is not a known error condition
280
branch2_wt = self.make_branch_and_tree('branch2')
281
branch2_wt.pull(main_branch)
282
branch2_bzrdir = branch2_wt.bzrdir
283
branch2_branch = branch2_bzrdir.open_branch()
284
set_executability(branch2_wt, 'b', True)
285
branch2_wt.commit("branch2, Commit one", rev_id="rev-J")
287
main_wt.merge_from_branch(branch2_branch)
288
set_executability(main_wt, 'b', False)
289
main_wt.commit("merge branch1, rev-22", rev_id="rev-G")
292
self.branch = main_branch
294
def test_fileid_involved_full_compare2(self):
295
# this tests that fileids_altered_by_revision_ids returns
296
# more information than compare_tree can, because it
297
# sees each change rather than the aggregate delta.
298
self.branch.lock_read()
299
self.addCleanup(self.branch.unlock)
300
history = self.branch.revision_history()
303
graph = self.branch.repository.get_graph()
304
unique_revs = graph.find_unique_ancestors(new_rev, [old_rev])
306
l1 = self.branch.repository.fileids_altered_by_revision_ids(
310
l2 = self.compare_tree_fileids(self.branch, old_rev, new_rev)
311
self.assertNotEqual(l2, l1)
312
self.assertSubset(l2, l1)
315
def set_executability(wt, path, executable=True):
316
"""Set the executable bit for the file at path in the working tree
318
os.chmod() doesn't work on windows. But TreeTransform can mark or
319
unmark a file as executable.
321
file_id = wt.path2id(path)
322
tt = transform.TreeTransform(wt)
324
tt.set_executability(executable, tt.trans_id_tree_file_id(file_id))