~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/changeset/old/gen_changeset.py

  • Committer: Aaron Bentley
  • Date: 2006-05-25 17:56:25 UTC
  • mto: This revision was merged to the branch mainline in revision 1738.
  • Revision ID: abentley@panoramicfeedback.com-20060525175625-c239c2e6526ffe6e
Cleanups to prepare for review

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
 
from sha import sha
7
 
 
8
 
import bzrlib
9
 
import os
10
 
 
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
19
 
 
20
 
class MetaInfoHeader(object):
21
 
    """Maintain all of the header information about this
22
 
    changeset.
23
 
    """
24
 
 
25
 
    def __init__(self,
26
 
            base_repository, base_rev_id, base_tree,
27
 
            target_branch, target_rev_id, target_tree,
28
 
            delta,
29
 
            starting_rev_id=None,
30
 
            full_remove=False, full_rename=False,
31
 
            message=None,
32
 
            base_label = 'orig', target_label = 'mod'):
33
 
        """
34
 
        :param full_remove: Include the full-text for a delete
35
 
        :param full_rename: Include an add+delete patch for a rename
36
 
 
37
 
        """
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)
44
 
        else:
45
 
            self.base_revision = None
46
 
 
47
 
        self.target_branch = target_branch
48
 
        self.target_rev_id = target_rev_id
49
 
        self.target_tree = target_tree
50
 
 
51
 
        self.delta = delta
52
 
 
53
 
        self.starting_rev_id = starting_rev_id
54
 
 
55
 
        self.full_remove=full_remove
56
 
        self.full_rename=full_rename
57
 
 
58
 
        self.base_label = base_label
59
 
        self.target_label = target_label
60
 
 
61
 
        self.to_file = None
62
 
        #self.revno = None
63
 
        #self.parent_revno = None
64
 
 
65
 
        # These are entries in the header.
66
 
        # They will be repeated in the footer,
67
 
        # only if they have changed
68
 
        self.date = None
69
 
        self.committer = None
70
 
        self.message = message
71
 
 
72
 
        self._get_revision_list()
73
 
 
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.
78
 
        """
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()
83
 
        try:
84
 
            source = MultipleRevisionSources(self.target_branch.repository,
85
 
                                             self.base_repository)
86
 
            if self.starting_rev_id is None:
87
 
                if self.base_rev_id is None:
88
 
                    self.starting_rev_id = NULL_REVISION
89
 
                else:
90
 
                    self.starting_rev_id = common_ancestor(self.target_rev_id, 
91
 
                        self.base_rev_id, source)
92
 
 
93
 
            rev_id_list = get_intervening_revisions(self.starting_rev_id,
94
 
                self.target_rev_id, source, 
95
 
                self.target_branch.revision_history())
96
 
 
97
 
            self.revision_list = [source.get_revision(rid) for rid in
98
 
                                  rev_id_list]
99
 
        finally:
100
 
            self.base_repository.unlock()
101
 
            self.target_branch.unlock()
102
 
 
103
 
    def _write(self, txt, key=None, encode=True, indent=1):
104
 
        from common import encode as _encode
105
 
        if encode:
106
 
            def write(txt):
107
 
                self.to_file.write(_encode(txt))
108
 
        else:
109
 
            def write(txt):
110
 
                self.to_file.write(txt)
111
 
        if indent > 0:
112
 
            write('#' + (' ' * indent))
113
 
        if key:
114
 
            if txt:
115
 
                write('%s: %s\n' % (key, txt))
116
 
            else:
117
 
                write('%s:\n' % key)
118
 
        else:
119
 
            write('%s\n' % (txt,))
120
 
 
121
 
    def write_meta_info(self, to_file):
122
 
        """Write out the meta-info portion to the supplied file.
123
 
 
124
 
        :param to_file: Write out the meta information to the supplied
125
 
                        file
126
 
        """
127
 
        self.to_file = to_file
128
 
 
129
 
        self._write_header()
130
 
        self._write_diffs()
131
 
        self._write_footer()
132
 
 
133
 
    def _write_header(self):
134
 
        """Write the stuff that comes before the patches."""
135
 
        from common import format_highres_date, get_header
136
 
        write = self._write
137
 
 
138
 
        for line in get_header():
139
 
            write(line)
140
 
 
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
150
 
        if self.message:
151
 
            write('', key='message')
152
 
            for line in self.message.split('\n'):
153
 
                write(txt=line, indent=4)
154
 
 
155
 
        write('') # line with just '#'
156
 
        write('', indent=0) # Empty line
157
 
 
158
 
    def _write_footer(self):
159
 
        """Write the stuff that comes after the patches.
160
 
 
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.
163
 
        """
164
 
        write = self._write
165
 
 
166
 
        # What should we print out for an Empty base revision?
167
 
        if len(self.revision_list[0].parent_ids) == 0:
168
 
            assumed_base = None
169
 
        else:
170
 
            assumed_base = self.revision_list[0].parent_ids[0]
171
 
 
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), 
177
 
                  key='base sha1')
178
 
 
179
 
        self._write_revisions()
180
 
 
181
 
    def _write_revisions(self):
182
 
        """Not used. Used for writing multiple revisions."""
183
 
        from common import format_highres_date, encode
184
 
 
185
 
        write = self._write
186
 
 
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, 
202
 
                                            p_id)
203
 
                    if p_sha1 is not None:
204
 
                        write(p_id + '\t' + p_sha1, indent=7)
205
 
                    else:
206
 
                        warning('Rev id {%s} parent {%s} missing sha hash.'
207
 
                                % (rev_id, p_id))
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)
213
 
 
214
 
    def _write_diffs(self):
215
 
        """Write out the specific diffs"""
216
 
        def pjoin(*args):
217
 
            # Only forward slashes in changesets
218
 
            return os.path.join(*args).replace('\\', '/')
219
 
 
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):
223
 
            if text_modified:
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, 
228
 
                                                 new_tree, to_file)
229
 
        DEVNULL = '/dev/null'
230
 
 
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
235
 
 
236
 
        new_label = self.target_label
237
 
        new_tree = self.target_tree
238
 
 
239
 
        old_tree = self.base_tree
240
 
        old_label = self.base_label
241
 
 
242
 
        write = self._write
243
 
        to_file = self.to_file
244
 
 
245
 
 
246
 
        def get_rev_id_str(file_id, kind):
247
 
            last_changed_rev_id = new_tree.inventory[file_id].revision
248
 
 
249
 
            if rev_id != last_changed_rev_id:
250
 
                return ' // last-changed:' + last_changed_rev_id
251
 
            else:
252
 
                return ''
253
 
 
254
 
        for path, file_id, kind in self.delta.removed:
255
 
            write('=== removed %s %s' % (kind, path), indent=0)
256
 
            if self.full_remove:
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)),
262
 
                    indent=0)
263
 
            new_tree.inventory[file_id].diff(diff_file, pjoin(new_label, path), new_tree,
264
 
                                             DEVNULL, None, None, to_file, 
265
 
                                             reverse=True)
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,
271
 
                    old_path, new_path,
272
 
                    get_rev_id_str(file_id, kind)),
273
 
                    indent=0)
274
 
            if self.full_rename:
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, 
280
 
                                                 reverse=True)
281
 
            else:
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)
285
 
 
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)),
292
 
                    indent=0)
293
 
            _maybe_diff(old_label, path, old_tree, file_id,
294
 
                        new_label, path, new_tree,
295
 
                        text_modified, kind, to_file, diff_file)
296
 
 
297
 
 
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,
302
 
        message=None):
303
 
 
304
 
    if to_file is None:
305
 
        import sys
306
 
        to_file = sys.stdout
307
 
    base_tree = base_repository.revision_tree(base_rev_id)
308
 
    target_tree = target_branch.repository.revision_tree(target_rev_id)
309
 
 
310
 
    delta = compare_trees(base_tree, target_tree, want_unchanged=False)
311
 
 
312
 
    meta = MetaInfoHeader(base_repository, base_rev_id, base_tree,
313
 
            target_branch, target_rev_id, target_tree,
314
 
            delta,
315
 
            starting_rev_id=starting_rev_id,
316
 
            full_rename=include_full_diff, full_remove=include_full_diff,
317
 
            message=message)
318
 
    meta.write_meta_info(to_file)