~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to gen_changeset.py

  • Committer: John Arbash Meinel
  • Date: 2005-06-20 05:06:06 UTC
  • mto: (0.5.85) (1185.82.1 bzr-w-changeset)
  • mto: This revision was merged to the branch mainline in revision 1738.
  • Revision ID: john@arbash-meinel.com-20050620050606-75e09678cc5ea0aa
adding apply-changset, plus more meta information.

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