52
34
precursor=precursor,
53
35
precursor_sha1=precursor_sha1)
37
def _get_revision_set(branch):
38
"""Get the set of all revisions that are in the ancestry
42
to_search = [branch.last_patch()]
44
while len(to_search) > 0:
45
rev_id = to_search.pop(0)
46
if rev_id in this_revs:
49
rev = branch.get_revision(rev_id)
50
for parent in rev.parents:
51
if parent.revision_id not in this_revs:
52
to_search.append(parent.revision_id)
55
def _find_best_base(branch, other_rev_id, other_branch=None):
56
"""Find the best base revision based on ancestry.
57
All revisions should already be pulled into the local tree.
59
this_revs = _get_revision_set(branch)
61
# This does a breadth first search through history, looking for
62
# something which matches
64
to_check = [other_rev_id]
65
while len(to_check) > 0:
66
# Removing the '0' would make this depth-first search
67
rev_id = to_check.pop(0)
71
if rev_id in this_revs:
74
if rev_id in branch.revision_store:
75
rev = branch.get_revision(rev_id)
76
elif (other_branch is not None
77
and rev_id in other_branch.revision_store):
78
rev = other_branch.get_revision(rev_id)
80
# Should we just continue here?
81
warning('Could not find revision for rev: {%s}'
86
for parent in rev.parents:
87
if parent.revision_id not in checked:
88
to_check.append(parent.revision_id)
92
def _create_ancestry_to_rev(branch, ancestor_rev_id, this_rev_id):
93
"""Return a listing of revisions, which traces back from this_rev_id
94
all the way back to the ancestor_rev_id.
96
# This is an optimization, when both target and base
97
# exist in the revision history, we should already have
98
# a valid listing of revision ancestry.
99
rh = branch.revision_history()
100
if ancestor_rev_id in rh and this_rev_id in rh:
101
ancestor_idx = rh.index(ancestor_rev_id)
102
this_rev_idx = rh.index(this_rev_id)
103
if ancestor_idx > this_rev_idx:
104
raise BzrCommandError('Revision {%s} is a child not an ancestor'
105
' of {%s}' % (ancestor_rev_id, this_rev_id))
106
rh_list = rh[ancestor_idx:this_rev_idx+1]
110
# I considered using depth-first search, as it is a little
111
# bit less resource intensive, and it should favor generating
112
# paths that are the same as revision_history
113
# but since breadth-first-search is generally used
116
# WARNING: In the presence of merges, there are cases where
117
# breadth first search will return a very different path
118
# than revision_history or depth first search. Imaging the following:
120
# rh: A -> B -> C -> D -> E -> F
123
# +--> Z ------------------+
125
# In this case, Starting with F, looking for A will return
126
# A-F for a revision_history search, but breadth-first will
127
# return A,Z,F since it is a much shorter path, and with
128
# F merging Z, it looks like a shortcut.
130
# But since A-F seems to be the more "correct" history
131
# for F, we might consider that revision_history should always
132
# be consulted first, and if not found there, to use breadth
134
checked_rev_ids = set()
136
cur_trails = [[this_rev_id]]
138
while len(cur_trails) > 0:
139
cur_trail = cur_trails.pop(0)
140
cur_rev_id = cur_trail[-1]
141
if cur_rev_id in checked_rev_ids:
143
checked_rev_ids.add(cur_rev_id)
145
if cur_rev_id == ancestor_rev_id:
148
if rev_id in branch.revision_store:
149
rev = branch.get_revision(rev_id)
151
# Should we just continue here?
152
warning('Could not find revision for rev: {%s}, unable to'
153
' trace ancestry.' % rev_id)
156
for parent in rev.parents:
157
if parent.revision_id not in checked:
158
to_check.append(cur_trail + [parent.revision_id])
160
raise BzrCommandError('Revision id {%s} not an ancestor of {%s}'
161
% (ancestor_rev_id, this_rev_id))
56
163
class MetaInfoHeader(object):
57
164
"""Maintain all of the header information about this
61
def __init__(self, branch, revisions, delta,
62
full_remove=True, full_rename=False,
63
new_tree=None, old_tree=None,
64
old_label = '', new_label = ''):
169
base_branch, base_rev_id, base_tree,
170
target_branch, target_rev_id, target_tree,
172
starting_rev_id=None,
173
full_remove=False, full_rename=False,
174
base_label = 'BASE', target_label = 'TARGET'):
66
176
:param full_remove: Include the full-text for a delete
67
177
:param full_rename: Include an add+delete patch for a rename
180
self.base_branch = base_branch
181
self.base_rev_id = base_rev_id
182
self.base_tree = base_tree
184
self.target_branch = target_branch
185
self.target_rev_id = target_rev_id
186
self.target_tree = target_tree
71
188
self.delta = delta
190
self.starting_rev_id = starting_rev_id
72
192
self.full_remove=full_remove
73
193
self.full_rename=full_rename
74
self.old_label = old_label
195
self.base_label = base_label
75
196
self.new_label = new_label
76
self.old_tree = old_tree
77
self.new_tree = new_tree
78
198
self.to_file = None
79
199
#self.revno = None
80
200
#self.parent_revno = None
86
206
self.committer = None
87
207
self.message = None
89
self._get_revision_list(revisions)
209
self._get_revision_list()
91
def _get_revision_list(self, revisions):
211
def _get_revision_list(self):
92
212
"""This generates the list of all revisions from->to.
94
This is for having a rollup changeset.
213
It fills out the internal self.revision_list with Revision
214
entries which should be in the changeset.
98
rh = self.branch.revision_history()
99
216
for revno, rev in enumerate(rh):
100
217
if rev == revisions[0]:
102
219
if rev == revisions[1]:
103
220
new_revno = revno
105
222
self.revision_list = []
106
if old_revno is None:
223
if base_revno is None:
107
224
self.base_revision = None # Effectively the EmptyTree()
110
self.base_revision = self.branch.get_revision(rh[old_revno])
227
self.base_revision = self.branch.get_revision(rh[base_revno])
111
228
if new_revno is None:
112
229
# For the future, when we support working tree changesets.
113
for rev_id in rh[old_revno+1:]:
230
for rev_id in rh[base_revno+1:]:
114
231
self.revision_list.append(self.branch.get_revision(rev_id))
115
232
self.revision_list.append(_fake_working_revision(self.branch))
117
for rev_id in rh[old_revno+1:new_revno+1]:
234
for rev_id in rh[base_revno+1:new_revno+1]:
118
235
self.revision_list.append(self.branch.get_revision(rev_id))
119
#self.parent_revno = old_revno+1
236
#self.parent_revno = base_revno+1
120
237
#self.revno = new_revno+1
122
239
def _write(self, txt, key=None):
213
327
for line in rev.message.split('\n'):
214
328
self.to_file.write('# %s\n' % line)
217
def _write_ids(self):
218
if hasattr(self.branch, 'get_root_id'):
219
root_id = self.branch.get_root_id()
226
for path, file_id, kind in self.delta.removed:
228
for path, file_id, kind in self.delta.added:
230
for old_path, new_path, file_id, kind, text_modified in self.delta.renamed:
233
for path, file_id, kind in self.delta.modified:
236
self._write(root_id, key='tree root id')
238
def write_ids(tree, id_set, name):
240
self.to_file.write('# %s ids:\n' % name)
241
seen_ids = set([root_id])
242
while len(id_set) > 0:
243
file_id = id_set.pop()
244
if file_id in seen_ids:
246
seen_ids.add(file_id)
247
ie = tree.inventory[file_id]
248
if ie.parent_id not in seen_ids:
249
id_set.add(ie.parent_id)
250
path = tree.inventory.id2path(file_id)
251
self.to_file.write('# %s\t%s\t%s\n'
254
write_ids(self.new_tree, new_ids, 'file')
255
write_ids(self.old_tree, old_ids, 'old file')
257
def _write_text_ids(self):
260
330
def _write_diffs(self):
261
331
"""Write out the specific diffs"""
262
332
from bzrlib.diff import internal_diff, external_diff
313
383
get_text_id_str(file_id, text_modified))
314
384
if self.full_rename and kind == 'file':
315
385
diff_file(self.old_label + old_path,
316
self.old_tree.get_file(file_id).readlines(),
386
self.base_tree.get_file(file_id).readlines(),
320
390
diff_file(DEVNULL,
322
392
self.new_label + new_path,
323
self.new_tree.get_file(file_id).readlines(),
393
self.target_tree.get_file(file_id).readlines(),
325
395
elif text_modified:
326
396
diff_file(self.old_label + old_path,
327
self.old_tree.get_file(file_id).readlines(),
397
self.base_tree.get_file(file_id).readlines(),
328
398
self.new_label + new_path,
329
self.new_tree.get_file(file_id).readlines(),
399
self.target_tree.get_file(file_id).readlines(),
332
402
for path, file_id, kind in self.delta.modified:
334
404
encode(path), get_text_id_str(file_id))
335
405
if kind == 'file':
336
406
diff_file(self.old_label + path,
337
self.old_tree.get_file(file_id).readlines(),
407
self.base_tree.get_file(file_id).readlines(),
338
408
self.new_label + path,
339
self.new_tree.get_file(file_id).readlines(),
409
self.target_tree.get_file(file_id).readlines(),
342
def show_changeset(branch, revisions=None, to_file=None, include_full_diff=False):
412
def show_changeset(base_branch, base_rev_id,
413
target_branch, target_rev_id,
414
starting_rev_id = None,
415
to_file=None, include_full_diff=False):
343
416
from bzrlib.diff import compare_trees
345
418
if to_file is None:
347
420
to_file = sys.stdout
348
revisions = common.canonicalize_revision(branch, revisions)
350
old_tree, new_tree = _get_trees(branch, revisions)
352
delta = compare_trees(old_tree, new_tree, want_unchanged=False)
354
meta = MetaInfoHeader(branch, revisions, delta,
355
old_tree=old_tree, new_tree=new_tree)
421
base_tree = base_branch.get_revision_tree(base_rev_id)
422
target_tree = target_branch.get_revision_tree(target_rev_id)
424
delta = compare_trees(base_tree, target_tree, want_unchanged=False)
426
meta = MetaInfoHeader(base_branch, base_rev_id, base_tree,
427
target_branch, target_rev_id, target_tree,
429
starting_rev_id=starting_rev_id,
430
full_rename=include_full_diff, full_remove=include_full_diff)
356
431
meta.write_meta_info(to_file)