~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Martin Pool
  • Date: 2005-09-07 04:00:21 UTC
  • Revision ID: mbp@sourcefrog.net-20050907040020-2c7a5e0176888d95
- BROKEN: partial support for commit into weave

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
## XXX: Everything up to here can simply be orphaned if we abort
 
19
## the commit; it will leave junk files behind but that doesn't
 
20
## matter.
 
21
 
 
22
## TODO: Read back the just-generated changeset, and make sure it
 
23
## applies and recreates the right state.
 
24
 
 
25
 
 
26
import os
 
27
import sys
18
28
import time
19
29
import tempfile
20
30
from binascii import hexlify
22
32
from bzrlib.osutils import (local_time_offset, username,
23
33
                            rand_bytes, compact_date, user_email,
24
34
                            kind_marker, is_inside_any, quotefn,
25
 
                            sha_string, isdir, isfile)
 
35
                            sha_string, sha_file, isdir, isfile)
26
36
from bzrlib.branch import gen_file_id
27
37
from bzrlib.errors import BzrError, PointlessCommit
28
38
from bzrlib.revision import Revision, RevisionReference
29
39
from bzrlib.trace import mutter, note
30
 
from bzrlib.xml import serializer_v4
 
40
from bzrlib.xml5 import serializer_v5
31
41
from bzrlib.inventory import Inventory
32
 
 
33
 
 
34
 
def commit(branch, message,
35
 
           timestamp=None,
36
 
           timezone=None,
37
 
           committer=None,
38
 
           verbose=True,
39
 
           specific_files=None,
40
 
           rev_id=None,
41
 
           allow_pointless=True):
42
 
    """Commit working copy as a new revision.
43
 
 
44
 
    The basic approach is to add all the file texts into the
45
 
    store, then the inventory, then make a new revision pointing
46
 
    to that inventory and store that.
47
 
 
48
 
    This is not quite safe if the working copy changes during the
49
 
    commit; for the moment that is simply not allowed.  A better
50
 
    approach is to make a temporary copy of the files before
51
 
    computing their hashes, and then add those hashes in turn to
52
 
    the inventory.  This should mean at least that there are no
53
 
    broken hash pointers.  There is no way we can get a snapshot
54
 
    of the whole directory at an instant.  This would also have to
55
 
    be robust against files disappearing, moving, etc.  So the
56
 
    whole thing is a bit hard.
57
 
 
58
 
    This raises PointlessCommit if there are no changes, no new merges,
59
 
    and allow_pointless  is false.
60
 
 
61
 
    timestamp -- if not None, seconds-since-epoch for a
62
 
         postdated/predated commit.
63
 
 
64
 
    specific_files
65
 
        If true, commit only those files.
66
 
 
67
 
    rev_id
68
 
        If set, use this as the new revision id.
69
 
        Useful for test or import commands that need to tightly
70
 
        control what revisions are assigned.  If you duplicate
71
 
        a revision id that exists elsewhere it is your own fault.
72
 
        If null (default), a time/random revision id is generated.
 
42
from bzrlib.delta import compare_trees
 
43
from bzrlib.weave import Weave
 
44
from bzrlib.weavefile import read_weave, write_weave_v5
 
45
from bzrlib.atomicfile import AtomicFile
 
46
 
 
47
 
 
48
class NullCommitReporter(object):
 
49
    """I report on progress of a commit."""
 
50
    def added(self, path):
 
51
        pass
 
52
 
 
53
    def removed(self, path):
 
54
        pass
 
55
 
 
56
    def renamed(self, old_path, new_path):
 
57
        pass
 
58
 
 
59
 
 
60
class ReportCommitToLog(NullCommitReporter):
 
61
    def added(self, path):
 
62
        note('added %s', path)
 
63
 
 
64
    def removed(self, path):
 
65
        note('removed %s', path)
 
66
 
 
67
    def renamed(self, old_path, new_path):
 
68
        note('renamed %s => %s', old_path, new_path)
 
69
 
 
70
 
 
71
class Commit(object):
 
72
    """Task of committing a new revision.
 
73
 
 
74
    This is a MethodObject: it accumulates state as the commit is
 
75
    prepared, and then it is discarded.  It doesn't represent
 
76
    historical revisions, just the act of recording a new one.
 
77
 
 
78
            missing_ids
 
79
            Modified to hold a list of files that have been deleted from
 
80
            the working directory; these should be removed from the
 
81
            working inventory.
73
82
    """
74
 
 
75
 
    branch.lock_write()
76
 
 
77
 
    try:
78
 
        # First walk over the working inventory; and both update that
79
 
        # and also build a new revision inventory.  The revision
80
 
        # inventory needs to hold the text-id, sha1 and size of the
81
 
        # actual file versions committed in the revision.  (These are
82
 
        # not present in the working inventory.)  We also need to
83
 
        # detect missing/deleted files, and remove them from the
84
 
        # working inventory.
85
 
 
86
 
        work_tree = branch.working_tree()
87
 
        work_inv = work_tree.inventory
88
 
        basis = branch.basis_tree()
89
 
        basis_inv = basis.inventory
90
 
 
91
 
        if verbose:
92
 
            # note('looking for changes...')
93
 
            # print 'looking for changes...'
94
 
            # disabled; should be done at a higher level
95
 
            pass
96
 
 
97
 
        pending_merges = branch.pending_merges()
98
 
 
99
 
        missing_ids, new_inv, any_changes = \
100
 
                     _gather_commit(branch,
101
 
                                    work_tree,
102
 
                                    work_inv,
103
 
                                    basis_inv,
104
 
                                    specific_files,
105
 
                                    verbose)
106
 
 
107
 
        if not (any_changes or allow_pointless or pending_merges):
108
 
            raise PointlessCommit()
109
 
 
110
 
        for file_id in missing_ids:
111
 
            # Any files that have been deleted are now removed from the
112
 
            # working inventory.  Files that were not selected for commit
113
 
            # are left as they were in the working inventory and ommitted
114
 
            # from the revision inventory.
115
 
 
116
 
            # have to do this later so we don't mess up the iterator.
117
 
            # since parents may be removed before their children we
118
 
            # have to test.
119
 
 
120
 
            # FIXME: There's probably a better way to do this; perhaps
121
 
            # the workingtree should know how to filter itbranch.
122
 
            if work_inv.has_id(file_id):
123
 
                del work_inv[file_id]
124
 
 
125
 
        if rev_id is None:
126
 
            rev_id = _gen_revision_id(branch, time.time())
127
 
        inv_id = rev_id
128
 
 
 
83
    def __init__(self,
 
84
                 reporter=None):
 
85
        if reporter is not None:
 
86
            self.reporter = reporter
 
87
        else:
 
88
            self.reporter = NullCommitReporter()
 
89
 
 
90
        
 
91
    def commit(self,
 
92
               branch, message,
 
93
               timestamp=None,
 
94
               timezone=None,
 
95
               committer=None,
 
96
               specific_files=None,
 
97
               rev_id=None,
 
98
               allow_pointless=True):
 
99
        """Commit working copy as a new revision.
 
100
 
 
101
        The basic approach is to add all the file texts into the
 
102
        store, then the inventory, then make a new revision pointing
 
103
        to that inventory and store that.
 
104
 
 
105
        This is not quite safe if the working copy changes during the
 
106
        commit; for the moment that is simply not allowed.  A better
 
107
        approach is to make a temporary copy of the files before
 
108
        computing their hashes, and then add those hashes in turn to
 
109
        the inventory.  This should mean at least that there are no
 
110
        broken hash pointers.  There is no way we can get a snapshot
 
111
        of the whole directory at an instant.  This would also have to
 
112
        be robust against files disappearing, moving, etc.  So the
 
113
        whole thing is a bit hard.
 
114
 
 
115
        This raises PointlessCommit if there are no changes, no new merges,
 
116
        and allow_pointless  is false.
 
117
 
 
118
        timestamp -- if not None, seconds-since-epoch for a
 
119
             postdated/predated commit.
 
120
 
 
121
        specific_files
 
122
            If true, commit only those files.
 
123
 
 
124
        rev_id
 
125
            If set, use this as the new revision id.
 
126
            Useful for test or import commands that need to tightly
 
127
            control what revisions are assigned.  If you duplicate
 
128
            a revision id that exists elsewhere it is your own fault.
 
129
            If null (default), a time/random revision id is generated.
 
130
        """
 
131
 
 
132
        self.branch = branch
 
133
        self.branch.lock_write()
 
134
        self.rev_id = rev_id
 
135
        self.specific_files = specific_files
 
136
 
 
137
        if timestamp is None:
 
138
            self.timestamp = time.time()
 
139
        else:
 
140
            self.timestamp = long(timestamp)
 
141
            
 
142
        if committer is None:
 
143
            self.committer = username(self.branch)
 
144
        else:
 
145
            assert isinstance(committer, basestring), type(committer)
 
146
            self.committer = committer
 
147
 
 
148
        if timezone is None:
 
149
            self.timezone = local_time_offset()
 
150
        else:
 
151
            self.timezone = int(timezone)
 
152
 
 
153
        assert isinstance(message, basestring), type(message)
 
154
        self.message = message
 
155
 
 
156
        try:
 
157
            # First walk over the working inventory; and both update that
 
158
            # and also build a new revision inventory.  The revision
 
159
            # inventory needs to hold the text-id, sha1 and size of the
 
160
            # actual file versions committed in the revision.  (These are
 
161
            # not present in the working inventory.)  We also need to
 
162
            # detect missing/deleted files, and remove them from the
 
163
            # working inventory.
 
164
 
 
165
            self.work_tree = self.branch.working_tree()
 
166
            self.work_inv = self.work_tree.inventory
 
167
            self.basis_tree = self.branch.basis_tree()
 
168
            self.basis_inv = self.basis_tree.inventory
 
169
 
 
170
            self.pending_merges = self.branch.pending_merges()
 
171
 
 
172
            if self.rev_id is None:
 
173
                self.rev_id = _gen_revision_id(self.branch, time.time())
 
174
 
 
175
            self.delta = compare_trees(self.basis_tree, self.work_tree,
 
176
                                       specific_files=self.specific_files)
 
177
 
 
178
            if not (self.delta.has_changed()
 
179
                    or self.allow_pointless
 
180
                    or self.pending_merges):
 
181
                raise PointlessCommit()
 
182
 
 
183
            self.new_inv = self.basis_inv.copy()
 
184
 
 
185
            self.delta.show(sys.stdout)
 
186
 
 
187
            self._remove_deleted()
 
188
            self._store_texts()
 
189
 
 
190
            self.branch._write_inventory(self.work_inv)
 
191
            self._record_inventory()
 
192
 
 
193
            self._make_revision()
 
194
            note('committted r%d', (self.branch.revno() + 1))
 
195
            self.branch.append_revision(rev_id)
 
196
            self.branch.set_pending_merges([])
 
197
        finally:
 
198
            self.branch.unlock()
 
199
 
 
200
 
 
201
    def _record_inventory(self):
129
202
        inv_tmp = tempfile.TemporaryFile()
130
 
        
131
 
        serializer_v4.write_inventory(new_inv, inv_tmp)
132
 
        inv_tmp.seek(0)
133
 
        branch.inventory_store.add(inv_tmp, inv_id)
134
 
        mutter('new inventory_id is {%s}' % inv_id)
135
 
 
136
 
        # We could also just sha hash the inv_tmp file
137
 
        # however, in the case that branch.inventory_store.add()
138
 
        # ever actually does anything special
139
 
        inv_sha1 = branch.get_inventory_sha1(inv_id)
140
 
 
141
 
        branch._write_inventory(work_inv)
142
 
 
143
 
        if timestamp == None:
144
 
            timestamp = time.time()
145
 
 
146
 
        if committer == None:
147
 
            committer = username(branch)
148
 
 
149
 
        if timezone == None:
150
 
            timezone = local_time_offset()
151
 
 
152
 
        mutter("building commit log message")
153
 
        rev = Revision(timestamp=timestamp,
154
 
                       timezone=timezone,
155
 
                       committer=committer,
156
 
                       message = message,
157
 
                       inventory_id=inv_id,
158
 
                       inventory_sha1=inv_sha1,
159
 
                       revision_id=rev_id)
160
 
 
161
 
        rev.parents = []
162
 
        precursor_id = branch.last_patch()
 
203
        serializer_v5.write_inventory(self.new_inv, inv_tmp)
 
204
        inv_tmp.seek(0)
 
205
        self.inv_sha1 = sha_file(inv_tmp)
 
206
        inv_tmp.seek(0)
 
207
        self.branch.inventory_store.add(inv_tmp, self.rev_id)
 
208
 
 
209
 
 
210
    def _make_revision(self):
 
211
        """Record a new revision object for this commit."""
 
212
        self.rev = Revision(timestamp=self.timestamp,
 
213
                            timezone=self.timezone,
 
214
                            committer=self.committer,
 
215
                            message=self.message,
 
216
                            inventory_sha1=self.inv_sha1,
 
217
                            revision_id=self.rev_id)
 
218
 
 
219
        self.rev.parents = []
 
220
        precursor_id = self.branch.last_patch()
163
221
        if precursor_id:
164
 
            precursor_sha1 = branch.get_revision_sha1(precursor_id)
165
 
            rev.parents.append(RevisionReference(precursor_id, precursor_sha1))
166
 
        for merge_rev in pending_merges:
167
 
            rev.parents.append(RevisionReference(merge_rev))            
 
222
            self.rev.parents.append(RevisionReference(precursor_id))
 
223
        for merge_rev in self.pending_merges:
 
224
            rev.parents.append(RevisionReference(merge_rev))
168
225
 
169
226
        rev_tmp = tempfile.TemporaryFile()
170
 
        serializer_v4.write_revision(rev, rev_tmp)
 
227
        serializer_v5.write_revision(self.rev, rev_tmp)
171
228
        rev_tmp.seek(0)
172
 
        branch.revision_store.add(rev_tmp, rev_id)
173
 
        mutter("new revision_id is {%s}" % rev_id)
174
 
 
175
 
        ## XXX: Everything up to here can simply be orphaned if we abort
176
 
        ## the commit; it will leave junk files behind but that doesn't
177
 
        ## matter.
178
 
 
179
 
        ## TODO: Read back the just-generated changeset, and make sure it
180
 
        ## applies and recreates the right state.
181
 
 
182
 
        ## TODO: Also calculate and store the inventory SHA1
183
 
        mutter("committing patch r%d" % (branch.revno() + 1))
184
 
 
185
 
        branch.append_revision(rev_id)
186
 
 
187
 
        branch.set_pending_merges([])
188
 
 
189
 
        if verbose:
190
 
            # disabled; should go through logging
191
 
            # note("commited r%d" % branch.revno())
192
 
            # print ("commited r%d" % branch.revno())
193
 
            pass
194
 
    finally:
195
 
        branch.unlock()
196
 
 
197
 
 
198
 
 
199
 
def _gen_revision_id(branch, when):
200
 
    """Return new revision-id."""
201
 
    s = '%s-%s-' % (user_email(branch), compact_date(when))
202
 
    s += hexlify(rand_bytes(8))
203
 
    return s
204
 
 
205
 
 
206
 
def _gather_commit(branch, work_tree, work_inv, basis_inv, specific_files,
207
 
                   verbose):
208
 
    """Build inventory preparatory to commit.
209
 
 
210
 
    Returns missing_ids, new_inv, any_changes.
211
 
 
212
 
    This adds any changed files into the text store, and sets their
213
 
    test-id, sha and size in the returned inventory appropriately.
214
 
 
215
 
    missing_ids
216
 
        Modified to hold a list of files that have been deleted from
217
 
        the working directory; these should be removed from the
218
 
        working inventory.
219
 
    """
220
 
    any_changes = False
221
 
    inv = Inventory(work_inv.root.file_id)
222
 
    missing_ids = []
223
 
    
224
 
    for path, entry in work_inv.iter_entries():
225
 
        ## TODO: Check that the file kind has not changed from the previous
226
 
        ## revision of this file (if any).
227
 
 
228
 
        p = branch.abspath(path)
229
 
        file_id = entry.file_id
230
 
        mutter('commit prep file %s, id %r ' % (p, file_id))
231
 
 
232
 
        if specific_files and not is_inside_any(specific_files, path):
233
 
            mutter('  skipping file excluded from commit')
234
 
            if basis_inv.has_id(file_id):
235
 
                # carry over with previous state
236
 
                inv.add(basis_inv[file_id].copy())
237
 
            else:
238
 
                # omit this from committed inventory
239
 
                pass
240
 
            continue
241
 
 
242
 
        if not work_tree.has_id(file_id):
243
 
            if verbose:
244
 
                print('deleted %s%s' % (path, kind_marker(entry.kind)))
245
 
                any_changes = True
246
 
            mutter("    file is missing, removing from inventory")
247
 
            missing_ids.append(file_id)
248
 
            continue
249
 
 
250
 
        # this is present in the new inventory; may be new, modified or
251
 
        # unchanged.
252
 
        old_ie = basis_inv.has_id(file_id) and basis_inv[file_id]
253
 
        
254
 
        entry = entry.copy()
255
 
        inv.add(entry)
256
 
 
257
 
        if old_ie:
258
 
            old_kind = old_ie.kind
259
 
            if old_kind != entry.kind:
260
 
                raise BzrError("entry %r changed kind from %r to %r"
261
 
                        % (file_id, old_kind, entry.kind))
262
 
 
263
 
        if entry.kind == 'directory':
264
 
            if not isdir(p):
265
 
                raise BzrError("%s is entered as directory but not a directory"
266
 
                               % quotefn(p))
267
 
        elif entry.kind == 'file':
268
 
            if not isfile(p):
269
 
                raise BzrError("%s is entered as file but is not a file" % quotefn(p))
270
 
 
271
 
            new_sha1 = work_tree.get_file_sha1(file_id)
272
 
 
273
 
            if (old_ie
274
 
                and old_ie.text_sha1 == new_sha1):
275
 
                ## assert content == basis.get_file(file_id).read()
276
 
                entry.text_id = old_ie.text_id
277
 
                entry.text_sha1 = new_sha1
278
 
                entry.text_size = old_ie.text_size
279
 
                mutter('    unchanged from previous text_id {%s}' %
280
 
                       entry.text_id)
281
 
            else:
282
 
                content = file(p, 'rb').read()
283
 
 
284
 
                # calculate the sha again, just in case the file contents
285
 
                # changed since we updated the cache
286
 
                entry.text_sha1 = sha_string(content)
287
 
                entry.text_size = len(content)
288
 
 
289
 
                entry.text_id = gen_file_id(entry.name)
290
 
                branch.text_store.add(content, entry.text_id)
291
 
                mutter('    stored with text_id {%s}' % entry.text_id)
292
 
 
293
 
        if verbose:
 
229
        self.branch.revision_store.add(rev_tmp, self.rev_id)
 
230
        mutter('new revision_id is {%s}', self.rev_id)
 
231
 
 
232
 
 
233
    def _remove_deleted(self):
 
234
        """Remove deleted files from the working and stored inventories."""
 
235
        for path, id, kind in self.delta.removed:
 
236
            if self.work_inv.has_id(id):
 
237
                del self.work_inv[id]
 
238
            if self.new_inv.has_id(id):
 
239
                del self.new_inv[id]
 
240
 
 
241
 
 
242
    def _store_texts(self):
 
243
        """Store new texts of modified/added files."""
 
244
        for path, id, kind in self.delta.modified:
 
245
            if kind != 'file':
 
246
                continue
 
247
            self._store_file_text(path, id)
 
248
 
 
249
        for path, id, kind in self.delta.added:
 
250
            if kind != 'file':
 
251
                continue
 
252
            self._store_file_text(path, id)
 
253
 
 
254
        for old_path, new_path, id, kind, text_modified in self.delta.renamed:
 
255
            if kind != 'file':
 
256
                continue
 
257
            if not text_modified:
 
258
                continue
 
259
            self._store_file_text(path, id)
 
260
 
 
261
 
 
262
    def _store_file_text(self, path, id):
 
263
        """Store updated text for one modified or added file."""
 
264
        # TODO: Add or update the inventory entry for this file;
 
265
        # put in the new text version
 
266
        note('store new text for {%s} in revision {%s}', id, self.rev_id)
 
267
        new_lines = self.work_tree.get_file(id).readlines()
 
268
        weave_fn = self.branch.controlfilename(['weaves', id+'.weave'])
 
269
        if os.path.exists(weave_fn):
 
270
            w = read_weave(file(weave_fn, 'rb'))
 
271
        else:
 
272
            w = Weave()
 
273
        w.add(self.rev_id, [], new_lines)
 
274
        af = AtomicFile(weave_fn)
 
275
        try:
 
276
            write_weave_v5(w, af)
 
277
            af.commit()
 
278
        finally:
 
279
            af.close()
 
280
 
 
281
 
 
282
    def _gather(self):
 
283
        """Build inventory preparatory to commit.
 
284
 
 
285
        This adds any changed files into the text store, and sets their
 
286
        test-id, sha and size in the returned inventory appropriately.
 
287
 
 
288
        """
 
289
        self.any_changes = False
 
290
        self.new_inv = Inventory(self.work_inv.root.file_id)
 
291
        self.missing_ids = []
 
292
 
 
293
        for path, entry in self.work_inv.iter_entries():
 
294
            ## TODO: Check that the file kind has not changed from the previous
 
295
            ## revision of this file (if any).
 
296
 
 
297
            p = self.branch.abspath(path)
 
298
            file_id = entry.file_id
 
299
            mutter('commit prep file %s, id %r ' % (p, file_id))
 
300
 
 
301
            if (self.specific_files
 
302
            and not is_inside_any(self.specific_files, path)):
 
303
                mutter('  skipping file excluded from commit')
 
304
                if self.basis_inv.has_id(file_id):
 
305
                    # carry over with previous state
 
306
                    self.new_inv.add(self.basis_inv[file_id].copy())
 
307
                else:
 
308
                    # omit this from committed inventory
 
309
                    pass
 
310
                continue
 
311
 
 
312
            if not self.work_tree.has_id(file_id):
 
313
                mutter("    file is missing, removing from inventory")
 
314
                self.missing_ids.append(file_id)
 
315
                continue
 
316
 
 
317
            # this is present in the new inventory; may be new, modified or
 
318
            # unchanged.
 
319
            old_ie = self.basis_inv.has_id(file_id) and self.basis_inv[file_id]
 
320
 
 
321
            entry = entry.copy()
 
322
            self.new_inv.add(entry)
 
323
 
 
324
            if old_ie:
 
325
                old_kind = old_ie.kind
 
326
                if old_kind != entry.kind:
 
327
                    raise BzrError("entry %r changed kind from %r to %r"
 
328
                            % (file_id, old_kind, entry.kind))
 
329
 
 
330
            if entry.kind == 'directory':
 
331
                if not isdir(p):
 
332
                    raise BzrError("%s is entered as directory but not a directory"
 
333
                                   % quotefn(p))
 
334
            elif entry.kind == 'file':
 
335
                if not isfile(p):
 
336
                    raise BzrError("%s is entered as file but is not a file" % quotefn(p))
 
337
 
 
338
                new_sha1 = self.work_tree.get_file_sha1(file_id)
 
339
 
 
340
                if (old_ie
 
341
                    and old_ie.text_sha1 == new_sha1):
 
342
                    ## assert content == basis.get_file(file_id).read()
 
343
                    entry.text_id = old_ie.text_id
 
344
                    entry.text_sha1 = new_sha1
 
345
                    entry.text_size = old_ie.text_size
 
346
                    mutter('    unchanged from previous text_id {%s}' %
 
347
                           entry.text_id)
 
348
                else:
 
349
                    content = file(p, 'rb').read()
 
350
 
 
351
                    # calculate the sha again, just in case the file contents
 
352
                    # changed since we updated the cache
 
353
                    entry.text_sha1 = sha_string(content)
 
354
                    entry.text_size = len(content)
 
355
 
 
356
                    entry.text_id = gen_file_id(entry.name)
 
357
                    self.branch.text_store.add(content, entry.text_id)
 
358
                    mutter('    stored with text_id {%s}' % entry.text_id)
 
359
 
294
360
            marked = path + kind_marker(entry.kind)
295
361
            if not old_ie:
296
 
                print 'added', marked
297
 
                any_changes = True
 
362
                self.reporter.added(marked)
 
363
                self.any_changes = True
298
364
            elif old_ie == entry:
299
365
                pass                    # unchanged
300
366
            elif (old_ie.name == entry.name
301
367
                  and old_ie.parent_id == entry.parent_id):
302
 
                print 'modified', marked
303
 
                any_changes = True
 
368
                self.reporter.modified(marked)
 
369
                self.any_changes = True
304
370
            else:
305
 
                print 'renamed', marked
306
 
                any_changes = True
307
 
                        
308
 
    return missing_ids, inv, any_changes
 
371
                old_path = old_inv.id2path(file_id) + kind_marker(entry.kind)
 
372
                self.reporter.renamed(old_path, marked)
 
373
                self.any_changes = True
 
374
 
 
375
 
 
376
 
 
377
def _gen_revision_id(branch, when):
 
378
    """Return new revision-id."""
 
379
    s = '%s-%s-' % (user_email(branch), compact_date(when))
 
380
    s += hexlify(rand_bytes(8))
 
381
    return s
309
382
 
310
383