3
Just some work for generating a changeset.
9
from bzrlib.inventory import ROOT_ID
10
from bzrlib.errors import BzrCommandError, NotAncestor
11
from bzrlib.trace import warning, mutter
12
from collections import deque
13
from bzrlib.revision import (common_ancestor, MultipleRevisionSources,
14
get_intervening_revisions, NULL_REVISION)
15
from bzrlib.diff import internal_diff, compare_trees
17
class MetaInfoHeader(object):
18
"""Maintain all of the header information about this
23
base_branch, base_rev_id, base_tree,
24
target_branch, target_rev_id, target_tree,
27
full_remove=False, full_rename=False,
29
base_label = 'orig', target_label = 'mod'):
31
:param full_remove: Include the full-text for a delete
32
:param full_rename: Include an add+delete patch for a rename
35
self.base_branch = base_branch
36
self.base_rev_id = base_rev_id
37
self.base_tree = base_tree
38
if self.base_rev_id is not None:
39
self.base_revision = self.base_branch.get_revision(self.base_rev_id)
41
self.base_revision = None
43
self.target_branch = target_branch
44
self.target_rev_id = target_rev_id
45
self.target_tree = target_tree
49
self.starting_rev_id = starting_rev_id
51
self.full_remove=full_remove
52
self.full_rename=full_rename
54
self.base_label = base_label
55
self.target_label = target_label
59
#self.parent_revno = None
61
# These are entries in the header.
62
# They will be repeated in the footer,
63
# only if they have changed
66
self.message = message
68
self._get_revision_list()
70
def _get_revision_list(self):
71
"""This generates the list of all revisions from->to.
72
It fills out the internal self.revision_list with Revision
73
entries which should be in the changeset.
75
# Performance, without locking here, a new lock is taken and
76
# broken for every revision (6k+ total locks for the bzr.dev tree)
77
self.target_branch.lock_read()
78
self.base_branch.lock_read()
80
source = MultipleRevisionSources(self.target_branch, self.base_branch)
81
if self.starting_rev_id is None:
82
if self.base_rev_id is None:
83
self.starting_rev_id = NULL_REVISION
85
self.starting_rev_id = common_ancestor(self.target_rev_id,
86
self.base_rev_id, source)
88
rev_id_list = get_intervening_revisions(self.starting_rev_id,
89
self.target_rev_id, source, self.target_branch.revision_history())
91
self.revision_list = [source.get_revision(rid) for rid in rev_id_list]
93
self.base_branch.unlock()
94
self.target_branch.unlock()
96
def _write(self, txt, key=None, encode=True, indent=1):
97
from common import encode as _encode
100
self.to_file.write(_encode(txt))
103
self.to_file.write(txt)
105
write('#' + (' ' * indent))
108
write('%s: %s\n' % (key, txt))
112
write('%s\n' % (txt,))
114
def write_meta_info(self, to_file):
115
"""Write out the meta-info portion to the supplied file.
117
:param to_file: Write out the meta information to the supplied
120
self.to_file = to_file
126
def _write_header(self):
127
"""Write the stuff that comes before the patches."""
128
from common import format_highres_date, get_header
131
for line in get_header():
134
# Print out the basic information about the 'target' revision
135
rev = self.revision_list[-1]
136
write(rev.committer, key='committer')
137
self.committer = rev.committer
138
self.date = format_highres_date(rev.timestamp, offset=rev.timezone)
139
write(self.date, key='date')
140
if self.message is None:
141
if rev.message is not None:
142
self.message = rev.message
144
write('', key='message')
145
for line in self.message.split('\n'):
146
write(txt=line, indent=4)
148
write('') # line with just '#'
149
write('', indent=0) # Empty line
151
def _write_footer(self):
152
"""Write the stuff that comes after the patches.
154
This is meant to be more meta-information, which people probably don't want
155
to read, but which is required for proper bzr operation.
159
# What should we print out for an Empty base revision?
160
if len(self.revision_list[0].parent_ids) == 0:
163
assumed_base = self.revision_list[0].parent_ids[0]
165
if (self.base_revision is not None
166
and self.base_revision.revision_id != assumed_base):
167
base = self.base_revision.revision_id
168
write(base, key='base')
169
write(self.base_branch.get_revision_sha1(base), key='base sha1')
171
self._write_revisions()
173
def _write_revisions(self):
174
"""Not used. Used for writing multiple revisions."""
175
from common import format_highres_date, encode
179
for rev in self.revision_list:
180
rev_id = rev.revision_id
181
write(rev_id, key='revision')
182
write(self.target_branch.get_revision_sha1(rev_id),
183
key = 'sha1', indent=4)
184
if rev.committer != self.committer:
185
write(rev.committer, key='committer', indent=4)
186
date = format_highres_date(rev.timestamp, rev.timezone)
187
if date != self.date:
188
write(date, key='date', indent=4)
189
write(rev.inventory_sha1, key='inventory sha1', indent=4)
190
if len(rev.parent_ids) > 0:
191
write(txt='', key='parents', indent=4)
192
for p_id in rev.parent_ids:
193
p_sha1 = self.target_branch.get_revision_sha1(p_id)
194
if p_sha1 is not None:
195
write(p_id + '\t' + p_sha1, indent=7)
197
warning('Rev id {%s} parent {%s} missing sha hash.'
199
write(p_id, indent=7)
200
if rev.message and rev.message != self.message:
201
write('', key='message', indent=4)
202
for line in rev.message.split('\n'):
203
write(line, indent=7)
205
def _write_diffs(self):
206
"""Write out the specific diffs"""
208
# Only forward slashes in changesets
209
return os.path.join(*args).replace('\\', '/')
211
def _maybe_diff(old_label, old_path, old_tree, file_id,
212
new_label, new_path, new_tree, text_modified,
213
kind, to_file, diff_file):
215
new_entry = new_tree.inventory[file_id]
216
old_tree.inventory[file_id].diff(diff_file,
217
pjoin(old_label, old_path), old_tree,
218
pjoin(new_label, new_path), new_entry,
220
DEVNULL = '/dev/null'
222
diff_file = internal_diff
223
# Get the target tree so that we can check for
224
# Appropriate text ids.
225
rev_id = self.target_rev_id
227
new_label = self.target_label
228
new_tree = self.target_tree
230
old_tree = self.base_tree
231
old_label = self.base_label
234
to_file = self.to_file
237
def get_rev_id_str(file_id, kind):
238
last_changed_rev_id = new_tree.inventory[file_id].revision
240
if rev_id != last_changed_rev_id:
241
return ' // last-changed:' + last_changed_rev_id
245
for path, file_id, kind in self.delta.removed:
246
write('=== removed %s %s' % (kind, path), indent=0)
248
old_tree.inventory[file_id].diff(diff_file, pjoin(old_label, path), old_tree,
249
DEVNULL, None, None, to_file)
250
for path, file_id, kind in self.delta.added:
251
write('=== added %s %s // file-id:%s%s' % (kind,
252
path, file_id, get_rev_id_str(file_id, kind)),
254
new_tree.inventory[file_id].diff(diff_file, pjoin(new_label, path), new_tree,
255
DEVNULL, None, None, to_file,
257
for (old_path, new_path, file_id, kind,
258
text_modified, meta_modified) in self.delta.renamed:
259
# TODO: Handle meta_modified
260
#prop_str = get_prop_change(meta_modified)
261
write('=== renamed %s %s // %s%s' % (kind,
263
get_rev_id_str(file_id, kind)),
266
# Looks like a delete + add
267
old_tree.inventory[file_id].diff(diff_file, pjoin(old_label, path), old_tree,
268
DEVNULL, None, None, to_file)
269
new_tree.inventory[file_id].diff(diff_file, pjoin(new_label, path), new_tree,
270
DEVNULL, None, None, to_file,
273
_maybe_diff(old_label, old_path, old_tree, file_id,
274
new_label, new_path, new_tree,
275
text_modified, kind, to_file, diff_file)
277
for (path, file_id, kind,
278
text_modified, meta_modified) in self.delta.modified:
279
# TODO: Handle meta_modified
280
#prop_str = get_prop_change(meta_modified)
281
write('=== modified %s %s%s' % (kind,
282
path, get_rev_id_str(file_id, kind)),
284
_maybe_diff(old_label, path, old_tree, file_id,
285
new_label, path, new_tree,
286
text_modified, kind, to_file, diff_file)
289
def show_changeset(base_branch, base_rev_id,
290
target_branch, target_rev_id,
291
starting_rev_id = None,
292
to_file=None, include_full_diff=False,
298
base_tree = base_branch.revision_tree(base_rev_id)
299
target_tree = target_branch.revision_tree(target_rev_id)
301
delta = compare_trees(base_tree, target_tree, want_unchanged=False)
303
meta = MetaInfoHeader(base_branch, base_rev_id, base_tree,
304
target_branch, target_rev_id, target_tree,
306
starting_rev_id=starting_rev_id,
307
full_rename=include_full_diff, full_remove=include_full_diff,
309
meta.write_meta_info(to_file)