~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to contrib/plugins/changeset/gen_changeset.py

  • Committer: Martin Pool
  • Date: 2005-06-22 09:08:43 UTC
  • Revision ID: mbp@sourcefrog.net-20050622090843-78fe9c62da9ed167
- add john's changeset plugin

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
"""\
 
3
Just some work for generating a changeset.
 
4
"""
 
5
 
 
6
import bzrlib, bzrlib.errors
 
7
 
 
8
import common
 
9
 
 
10
from bzrlib.inventory import ROOT_ID
 
11
 
 
12
try:
 
13
    set
 
14
except NameError:
 
15
    from sets import Set as set
 
16
 
 
17
def _canonicalize_revision(branch, revno):
 
18
    """Turn some sort of revision information into a single
 
19
    set of from-to revision ids.
 
20
 
 
21
    A revision id can be None if there is no associated revison.
 
22
 
 
23
    :return: (old, new)
 
24
    """
 
25
    # This is a little clumsy because revision parsing may return
 
26
    # a single entry, or a list
 
27
    if revno is None:
 
28
        new = branch.last_patch()
 
29
    else:
 
30
        new = branch.lookup_revision(revno)
 
31
 
 
32
    if new is None:
 
33
        raise BzrCommandError('Cannot generate a changset with no commits in tree.')
 
34
 
 
35
    old = branch.get_revision(new).precursor
 
36
 
 
37
    return old, new
 
38
 
 
39
def _get_trees(branch, revisions):
 
40
    """Get the old and new trees based on revision.
 
41
    """
 
42
    from bzrlib.tree import EmptyTree
 
43
    if revisions[0] is None:
 
44
        if hasattr(branch, 'get_root_id'): # Watch out for trees with labeled ROOT ids
 
45
            old_tree = EmptyTree(branch.get_root_id) 
 
46
        else:
 
47
            old_tree = EmptyTree()
 
48
    else:
 
49
        old_tree = branch.revision_tree(revisions[0])
 
50
 
 
51
    if revisions[1] is None:
 
52
        # This is for the future, once we support rollup revisions
 
53
        # Or working tree revisions
 
54
        new_tree = branch.working_tree()
 
55
    else:
 
56
        new_tree = branch.revision_tree(revisions[1])
 
57
    return old_tree, new_tree
 
58
 
 
59
def _fake_working_revision(branch):
 
60
    """Fake a Revision object for the working tree.
 
61
    
 
62
    This is for the future, to support changesets against the working tree.
 
63
    """
 
64
    from bzrlib.revision import Revision
 
65
    import time
 
66
    from bzrlib.osutils import local_time_offset, \
 
67
            username
 
68
 
 
69
    precursor = branch.last_patch()
 
70
    precursor_sha1 = branch.get_revision_sha1(precursor)
 
71
 
 
72
    return Revision(timestamp=time.time(),
 
73
            timezone=local_time_offset(),
 
74
            committer=username(),
 
75
            precursor=precursor,
 
76
            precursor_sha1=precursor_sha1)
 
77
 
 
78
 
 
79
class MetaInfoHeader(object):
 
80
    """Maintain all of the header information about this
 
81
    changeset.
 
82
    """
 
83
 
 
84
    def __init__(self, branch, revisions, delta,
 
85
            full_remove=True, full_rename=False,
 
86
            external_diff_options = None,
 
87
            new_tree=None, old_tree=None,
 
88
            old_label = '', new_label = ''):
 
89
        """
 
90
        :param full_remove: Include the full-text for a delete
 
91
        :param full_rename: Include an add+delete patch for a rename
 
92
        """
 
93
        self.branch = branch
 
94
        self.delta = delta
 
95
        self.full_remove=full_remove
 
96
        self.full_rename=full_rename
 
97
        self.external_diff_options = external_diff_options
 
98
        self.old_label = old_label
 
99
        self.new_label = new_label
 
100
        self.old_tree = old_tree
 
101
        self.new_tree = new_tree
 
102
        self.to_file = None
 
103
        self.revno = None
 
104
        self.precursor_revno = None
 
105
 
 
106
        self._get_revision_list(revisions)
 
107
 
 
108
    def _get_revision_list(self, revisions):
 
109
        """This generates the list of all revisions from->to.
 
110
 
 
111
        This is for the future, when we support having a rollup changeset.
 
112
        For now, the list should only be one long.
 
113
        """
 
114
        old_revno = None
 
115
        new_revno = None
 
116
        rh = self.branch.revision_history()
 
117
        for revno, rev in enumerate(rh):
 
118
            if rev == revisions[0]:
 
119
                old_revno = revno
 
120
            if rev == revisions[1]:
 
121
                new_revno = revno
 
122
 
 
123
        self.revision_list = []
 
124
        if old_revno is None:
 
125
            self.base_revision = None # Effectively the EmptyTree()
 
126
            old_revno = -1
 
127
        else:
 
128
            self.base_revision = self.branch.get_revision(rh[old_revno])
 
129
        if new_revno is None:
 
130
            # For the future, when we support working tree changesets.
 
131
            for rev_id in rh[old_revno+1:]:
 
132
                self.revision_list.append(self.branch.get_revision(rev_id))
 
133
            self.revision_list.append(_fake_working_revision(self.branch))
 
134
        else:
 
135
            for rev_id in rh[old_revno+1:new_revno+1]:
 
136
                self.revision_list.append(self.branch.get_revision(rev_id))
 
137
        self.precursor_revno = old_revno
 
138
        self.revno = new_revno
 
139
 
 
140
    def _write(self, txt, key=None):
 
141
        if key:
 
142
            self.to_file.write('# %s: %s\n' % (key, txt))
 
143
        else:
 
144
            self.to_file.write('# %s\n' % (txt,))
 
145
 
 
146
    def write_meta_info(self, to_file):
 
147
        """Write out the meta-info portion to the supplied file.
 
148
 
 
149
        :param to_file: Write out the meta information to the supplied
 
150
                        file
 
151
        """
 
152
        self.to_file = to_file
 
153
 
 
154
        self._write_header()
 
155
        self._write_diffs()
 
156
        self._write_footer()
 
157
 
 
158
    def _write_header(self):
 
159
        """Write the stuff that comes before the patches."""
 
160
        from bzrlib.osutils import username, format_date
 
161
        write = self._write
 
162
 
 
163
        for line in common.get_header():
 
164
            write(line)
 
165
 
 
166
        # This grabs the current username, what we really want is the
 
167
        # username from the actual patches.
 
168
        #write(username(), key='committer')
 
169
        assert len(self.revision_list) == 1
 
170
        rev = self.revision_list[0]
 
171
        write(rev.committer, key='committer')
 
172
        write(format_date(rev.timestamp, offset=rev.timezone), key='date')
 
173
        write(str(self.revno), key='revno')
 
174
        if rev.message:
 
175
            self.to_file.write('# message:\n')
 
176
            for line in rev.message.split('\n'):
 
177
                self.to_file.write('#    %s\n' % line)
 
178
        write(rev.revision_id, key='revision')
 
179
 
 
180
        if self.base_revision:
 
181
            write(self.base_revision.revision_id, key='precursor')
 
182
            write(str(self.precursor_revno), key='precursor revno')
 
183
 
 
184
 
 
185
        write('')
 
186
        self.to_file.write('\n')
 
187
 
 
188
    def _write_footer(self):
 
189
        """Write the stuff that comes after the patches.
 
190
 
 
191
        This is meant to be more meta-information, which people probably don't want
 
192
        to read, but which is required for proper bzr operation.
 
193
        """
 
194
        write = self._write
 
195
 
 
196
        write('BEGIN BZR FOOTER')
 
197
 
 
198
        assert len(self.revision_list) == 1 # We only handle single revision entries
 
199
        rev = self.revision_list[0]
 
200
        write(self.branch.get_revision_sha1(rev.revision_id),
 
201
                key='revision sha1')
 
202
        if self.base_revision:
 
203
            rev_id = self.base_revision.revision_id
 
204
            write(self.branch.get_revision_sha1(rev_id),
 
205
                    key='precursor sha1')
 
206
 
 
207
        write('%.9f' % rev.timestamp, key='timestamp')
 
208
        write(str(rev.timezone), key='timezone')
 
209
 
 
210
        self._write_ids()
 
211
 
 
212
        write('END BZR FOOTER')
 
213
 
 
214
    def _write_revisions(self):
 
215
        """Not used. Used for writing multiple revisions."""
 
216
        first = True
 
217
        for rev in self.revision_list:
 
218
            if rev.revision_id is not None:
 
219
                if first:
 
220
                    self._write('revisions:')
 
221
                    first = False
 
222
                self._write(' '*4 + rev.revision_id + '\t' + self.branch.get_revision_sha1(rev.revision_id))
 
223
 
 
224
 
 
225
    def _write_ids(self):
 
226
        if hasattr(self.branch, 'get_root_id'):
 
227
            root_id = self.branch.get_root_id()
 
228
        else:
 
229
            root_id = ROOT_ID
 
230
 
 
231
        old_ids = set()
 
232
        new_ids = set()
 
233
 
 
234
        for path, file_id, kind in self.delta.removed:
 
235
            old_ids.add(file_id)
 
236
        for path, file_id, kind in self.delta.added:
 
237
            new_ids.add(file_id)
 
238
        for old_path, new_path, file_id, kind, text_modified in self.delta.renamed:
 
239
            old_ids.add(file_id)
 
240
            new_ids.add(file_id)
 
241
        for path, file_id, kind in self.delta.modified:
 
242
            new_ids.add(file_id)
 
243
 
 
244
        self._write(root_id, key='tree root id')
 
245
 
 
246
        def write_ids(tree, id_set, name):
 
247
            if len(id_set) > 0:
 
248
                self.to_file.write('# %s ids:\n' % name)
 
249
            seen_ids = set([root_id])
 
250
            while len(id_set) > 0:
 
251
                file_id = id_set.pop()
 
252
                if file_id in seen_ids:
 
253
                    continue
 
254
                seen_ids.add(file_id)
 
255
                ie = tree.inventory[file_id]
 
256
                if ie.parent_id not in seen_ids:
 
257
                    id_set.add(ie.parent_id)
 
258
                path = tree.inventory.id2path(file_id)
 
259
                self.to_file.write('#    %s\t%s\t%s\n'
 
260
                        % (path.encode('utf8'), file_id.encode('utf8'),
 
261
                            ie.parent_id.encode('utf8')))
 
262
        write_ids(self.new_tree, new_ids, 'file')
 
263
        write_ids(self.old_tree, old_ids, 'old file')
 
264
 
 
265
    def _write_diffs(self):
 
266
        """Write out the specific diffs"""
 
267
        from bzrlib.diff import internal_diff, external_diff
 
268
        DEVNULL = '/dev/null'
 
269
 
 
270
        if self.external_diff_options:
 
271
            assert isinstance(self.external_diff_options, basestring)
 
272
            opts = self.external_diff_options.split()
 
273
            def diff_file(olab, olines, nlab, nlines, to_file):
 
274
                external_diff(olab, olines, nlab, nlines, to_file, opts)
 
275
        else:
 
276
            diff_file = internal_diff
 
277
 
 
278
        for path, file_id, kind in self.delta.removed:
 
279
            print >>self.to_file, '*** removed %s %r' % (kind, path)
 
280
            if kind == 'file' and self.full_remove:
 
281
                diff_file(self.old_label + path,
 
282
                          self.old_tree.get_file(file_id).readlines(),
 
283
                          DEVNULL, 
 
284
                          [],
 
285
                          self.to_file)
 
286
    
 
287
        for path, file_id, kind in self.delta.added:
 
288
            print >>self.to_file, '*** added %s %r' % (kind, path)
 
289
            if kind == 'file':
 
290
                diff_file(DEVNULL,
 
291
                          [],
 
292
                          self.new_label + path,
 
293
                          self.new_tree.get_file(file_id).readlines(),
 
294
                          self.to_file)
 
295
    
 
296
        for old_path, new_path, file_id, kind, text_modified in self.delta.renamed:
 
297
            print >>self.to_file, '*** renamed %s %r => %r' % (kind, old_path, new_path)
 
298
            if self.full_rename and kind == 'file':
 
299
                diff_file(self.old_label + old_path,
 
300
                          self.old_tree.get_file(file_id).readlines(),
 
301
                          DEVNULL, 
 
302
                          [],
 
303
                          self.to_file)
 
304
                diff_file(DEVNULL,
 
305
                          [],
 
306
                          self.new_label + new_path,
 
307
                          self.new_tree.get_file(file_id).readlines(),
 
308
                          self.to_file)
 
309
            elif text_modified:
 
310
                    diff_file(self.old_label + old_path,
 
311
                              self.old_tree.get_file(file_id).readlines(),
 
312
                              self.new_label + new_path,
 
313
                              self.new_tree.get_file(file_id).readlines(),
 
314
                              self.to_file)
 
315
    
 
316
        for path, file_id, kind in self.delta.modified:
 
317
            print >>self.to_file, '*** modified %s %r' % (kind, path)
 
318
            if kind == 'file':
 
319
                diff_file(self.old_label + path,
 
320
                          self.old_tree.get_file(file_id).readlines(),
 
321
                          self.new_label + path,
 
322
                          self.new_tree.get_file(file_id).readlines(),
 
323
                          self.to_file)
 
324
 
 
325
def show_changeset(branch, revision=None, specific_files=None,
 
326
        external_diff_options=None, to_file=None,
 
327
        include_full_diff=False):
 
328
    from bzrlib.diff import compare_trees
 
329
 
 
330
    if to_file is None:
 
331
        import sys
 
332
        to_file = sys.stdout
 
333
    revisions = _canonicalize_revision(branch, revision)
 
334
 
 
335
    old_tree, new_tree = _get_trees(branch, revisions)
 
336
 
 
337
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
 
338
                          specific_files=specific_files)
 
339
 
 
340
    meta = MetaInfoHeader(branch, revisions, delta,
 
341
            external_diff_options=external_diff_options,
 
342
            old_tree=old_tree, new_tree=new_tree)
 
343
    meta.write_meta_info(to_file)
 
344
 
 
345