3
Just some work for generating a changeset.
11
from bzrlib.changeset.common import testament_sha1
12
from bzrlib.inventory import ROOT_ID
13
from bzrlib.errors import BzrCommandError, NotAncestor
14
from bzrlib.trace import warning, mutter
15
from collections import deque
16
from bzrlib.revision import (common_ancestor, MultipleRevisionSources,
17
get_intervening_revisions, NULL_REVISION)
18
from bzrlib.diff import internal_diff, compare_trees
20
class MetaInfoHeader(object):
21
"""Maintain all of the header information about this
26
base_repository, base_rev_id, base_tree,
27
target_branch, target_rev_id, target_tree,
30
full_remove=False, full_rename=False,
32
base_label = 'orig', target_label = 'mod'):
34
:param full_remove: Include the full-text for a delete
35
:param full_rename: Include an add+delete patch for a rename
38
self.base_repository = base_repository
39
self.base_rev_id = base_rev_id
40
self.base_tree = base_tree
41
if self.base_rev_id is not None:
42
self.base_revision = \
43
self.base_repository.get_revision(self.base_rev_id)
45
self.base_revision = None
47
self.target_branch = target_branch
48
self.target_rev_id = target_rev_id
49
self.target_tree = target_tree
53
self.starting_rev_id = starting_rev_id
55
self.full_remove=full_remove
56
self.full_rename=full_rename
58
self.base_label = base_label
59
self.target_label = target_label
63
#self.parent_revno = None
65
# These are entries in the header.
66
# They will be repeated in the footer,
67
# only if they have changed
70
self.message = message
72
self._get_revision_list()
74
def _get_revision_list(self):
75
"""This generates the list of all revisions from->to.
76
It fills out the internal self.revision_list with Revision
77
entries which should be in the changeset.
79
# Performance, without locking here, a new lock is taken and
80
# broken for every revision (6k+ total locks for the bzr.dev tree)
81
self.target_branch.lock_read()
82
self.base_repository.lock_read()
84
source = MultipleRevisionSources(self.target_branch.repository,
86
if self.starting_rev_id is None:
87
if self.base_rev_id is None:
88
self.starting_rev_id = NULL_REVISION
90
self.starting_rev_id = common_ancestor(self.target_rev_id,
91
self.base_rev_id, source)
93
rev_id_list = get_intervening_revisions(self.starting_rev_id,
94
self.target_rev_id, source,
95
self.target_branch.revision_history())
97
self.revision_list = [source.get_revision(rid) for rid in
100
self.base_repository.unlock()
101
self.target_branch.unlock()
103
def _write(self, txt, key=None, encode=True, indent=1):
104
from common import encode as _encode
107
self.to_file.write(_encode(txt))
110
self.to_file.write(txt)
112
write('#' + (' ' * indent))
115
write('%s: %s\n' % (key, txt))
119
write('%s\n' % (txt,))
121
def write_meta_info(self, to_file):
122
"""Write out the meta-info portion to the supplied file.
124
:param to_file: Write out the meta information to the supplied
127
self.to_file = to_file
133
def _write_header(self):
134
"""Write the stuff that comes before the patches."""
135
from common import format_highres_date, get_header
138
for line in get_header():
141
# Print out the basic information about the 'target' revision
142
rev = self.revision_list[-1]
143
write(rev.committer, key='committer')
144
self.committer = rev.committer
145
self.date = format_highres_date(rev.timestamp, offset=rev.timezone)
146
write(self.date, key='date')
147
if self.message is None:
148
if rev.message is not None:
149
self.message = rev.message
151
write('', key='message')
152
for line in self.message.split('\n'):
153
write(txt=line, indent=4)
155
write('') # line with just '#'
156
write('', indent=0) # Empty line
158
def _write_footer(self):
159
"""Write the stuff that comes after the patches.
161
This is meant to be more meta-information, which people probably don't want
162
to read, but which is required for proper bzr operation.
166
# What should we print out for an Empty base revision?
167
if len(self.revision_list[0].parent_ids) == 0:
170
assumed_base = self.revision_list[0].parent_ids[0]
172
if (self.base_revision is not None
173
and self.base_revision.revision_id != assumed_base):
174
base = self.base_revision.revision_id
175
write(base, key='base')
176
write(testament_sha1(base_repository, base),
179
self._write_revisions()
181
def _write_revisions(self):
182
"""Not used. Used for writing multiple revisions."""
183
from common import format_highres_date, encode
187
for rev in self.revision_list:
188
rev_id = rev.revision_id
189
write(rev_id, key='revision')
190
write(testament_sha1(self.target_branch.repository, rev_id),
191
key = 'sha1', indent=4)
192
if rev.committer != self.committer:
193
write(rev.committer, key='committer', indent=4)
194
date = format_highres_date(rev.timestamp, rev.timezone)
195
if date != self.date:
196
write(date, key='date', indent=4)
197
write(rev.inventory_sha1, key='inventory sha1', indent=4)
198
if len(rev.parent_ids) > 0:
199
write(txt='', key='parents', indent=4)
200
for p_id in rev.parent_ids:
201
p_sha1 = testament_sha1(self.target_branch.repository,
203
if p_sha1 is not None:
204
write(p_id + '\t' + p_sha1, indent=7)
206
warning('Rev id {%s} parent {%s} missing sha hash.'
208
write(p_id, indent=7)
209
if rev.message and rev.message != self.message:
210
write('', key='message', indent=4)
211
for line in rev.message.split('\n'):
212
write(line, indent=7)
214
def _write_diffs(self):
215
"""Write out the specific diffs"""
217
# Only forward slashes in changesets
218
return os.path.join(*args).replace('\\', '/')
220
def _maybe_diff(old_label, old_path, old_tree, file_id,
221
new_label, new_path, new_tree, text_modified,
222
kind, to_file, diff_file):
224
new_entry = new_tree.inventory[file_id]
225
old_tree.inventory[file_id].diff(diff_file,
226
pjoin(old_label, old_path), old_tree,
227
pjoin(new_label, new_path), new_entry,
229
DEVNULL = '/dev/null'
231
diff_file = internal_diff
232
# Get the target tree so that we can check for
233
# Appropriate text ids.
234
rev_id = self.target_rev_id
236
new_label = self.target_label
237
new_tree = self.target_tree
239
old_tree = self.base_tree
240
old_label = self.base_label
243
to_file = self.to_file
246
def get_rev_id_str(file_id, kind):
247
last_changed_rev_id = new_tree.inventory[file_id].revision
249
if rev_id != last_changed_rev_id:
250
return ' // last-changed:' + last_changed_rev_id
254
for path, file_id, kind in self.delta.removed:
255
write('=== removed %s %s' % (kind, path), indent=0)
257
old_tree.inventory[file_id].diff(diff_file, pjoin(old_label, path), old_tree,
258
DEVNULL, None, None, to_file)
259
for path, file_id, kind in self.delta.added:
260
write('=== added %s %s // file-id:%s%s' % (kind,
261
path, file_id, get_rev_id_str(file_id, kind)),
263
new_tree.inventory[file_id].diff(diff_file, pjoin(new_label, path), new_tree,
264
DEVNULL, None, None, to_file,
266
for (old_path, new_path, file_id, kind,
267
text_modified, meta_modified) in self.delta.renamed:
268
# TODO: Handle meta_modified
269
#prop_str = get_prop_change(meta_modified)
270
write('=== renamed %s %s // %s%s' % (kind,
272
get_rev_id_str(file_id, kind)),
275
# Looks like a delete + add
276
old_tree.inventory[file_id].diff(diff_file, pjoin(old_label, path), old_tree,
277
DEVNULL, None, None, to_file)
278
new_tree.inventory[file_id].diff(diff_file, pjoin(new_label, path), new_tree,
279
DEVNULL, None, None, to_file,
282
_maybe_diff(old_label, old_path, old_tree, file_id,
283
new_label, new_path, new_tree,
284
text_modified, kind, to_file, diff_file)
286
for (path, file_id, kind,
287
text_modified, meta_modified) in self.delta.modified:
288
# TODO: Handle meta_modified
289
#prop_str = get_prop_change(meta_modified)
290
write('=== modified %s %s%s' % (kind,
291
path, get_rev_id_str(file_id, kind)),
293
_maybe_diff(old_label, path, old_tree, file_id,
294
new_label, path, new_tree,
295
text_modified, kind, to_file, diff_file)
298
def show_changeset(base_repository, base_rev_id,
299
target_branch, target_rev_id,
300
starting_rev_id = None,
301
to_file=None, include_full_diff=False,
307
base_tree = base_repository.revision_tree(base_rev_id)
308
target_tree = target_branch.repository.revision_tree(target_rev_id)
310
delta = compare_trees(base_tree, target_tree, want_unchanged=False)
312
meta = MetaInfoHeader(base_repository, base_rev_id, base_tree,
313
target_branch, target_rev_id, target_tree,
315
starting_rev_id=starting_rev_id,
316
full_rename=include_full_diff, full_remove=include_full_diff,
318
meta.write_meta_info(to_file)