~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 16:50:49 UTC
  • mto: This revision was merged to the branch mainline in revision 1738.
  • Revision ID: abentley@panoramicfeedback.com-20060525165049-7c042d3345ac4dbe
Default execute bit to no for new files, directories, symlinks

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)