~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Martin Pool
  • Date: 2005-05-16 08:11:41 UTC
  • Revision ID: mbp@sourcefrog.net-20050516081141-09145035a895b5b1
- bzr selftest: return shell false (1) if any tests fail

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
 
18
18
 
19
 
# FIXME: "bzr commit doc/format" commits doc/format.txt!
20
 
 
21
 
def commit(branch, message,
22
 
           timestamp=None,
23
 
           timezone=None,
 
19
def commit(branch, message, timestamp=None, timezone=None,
24
20
           committer=None,
25
21
           verbose=True,
26
22
           specific_files=None,
27
 
           rev_id=None,
28
 
           allow_pointless=True):
 
23
           rev_id=None):
29
24
    """Commit working copy as a new revision.
30
25
 
31
26
    The basic approach is to add all the file texts into the
42
37
    be robust against files disappearing, moving, etc.  So the
43
38
    whole thing is a bit hard.
44
39
 
45
 
    This raises PointlessCommit if there are no changes, no new merges,
46
 
    and allow_pointless  is false.
47
 
 
48
40
    timestamp -- if not None, seconds-since-epoch for a
49
41
         postdated/predated commit.
50
42
 
59
51
        If null (default), a time/random revision id is generated.
60
52
    """
61
53
 
62
 
    import time, tempfile
63
 
 
64
 
    from bzrlib.osutils import local_time_offset, username
65
 
    from bzrlib.branch import gen_file_id
66
 
    from bzrlib.errors import BzrError, PointlessCommit
67
 
    from bzrlib.revision import Revision, RevisionReference
68
 
    from bzrlib.trace import mutter, note
69
 
    from bzrlib.xml import pack_xml
70
 
 
71
 
    branch.lock_write()
72
 
 
73
 
    try:
74
 
        # First walk over the working inventory; and both update that
75
 
        # and also build a new revision inventory.  The revision
76
 
        # inventory needs to hold the text-id, sha1 and size of the
77
 
        # actual file versions committed in the revision.  (These are
78
 
        # not present in the working inventory.)  We also need to
79
 
        # detect missing/deleted files, and remove them from the
80
 
        # working inventory.
81
 
 
82
 
        work_tree = branch.working_tree()
83
 
        work_inv = work_tree.inventory
84
 
        basis = branch.basis_tree()
85
 
        basis_inv = basis.inventory
86
 
 
87
 
        if verbose:
88
 
            # note('looking for changes...')
89
 
            # print 'looking for changes...'
90
 
            # disabled; should be done at a higher level
91
 
            pass
92
 
 
93
 
        pending_merges = branch.pending_merges()
94
 
 
95
 
        missing_ids, new_inv, any_changes = \
96
 
                     _gather_commit(branch,
97
 
                                    work_tree,
98
 
                                    work_inv,
99
 
                                    basis_inv,
100
 
                                    specific_files,
101
 
                                    verbose)
102
 
 
103
 
        if not (any_changes or allow_pointless or pending_merges):
104
 
            raise PointlessCommit()
105
 
 
106
 
        for file_id in missing_ids:
107
 
            # Any files that have been deleted are now removed from the
108
 
            # working inventory.  Files that were not selected for commit
109
 
            # are left as they were in the working inventory and ommitted
110
 
            # from the revision inventory.
111
 
 
112
 
            # have to do this later so we don't mess up the iterator.
113
 
            # since parents may be removed before their children we
114
 
            # have to test.
115
 
 
116
 
            # FIXME: There's probably a better way to do this; perhaps
117
 
            # the workingtree should know how to filter itbranch.
118
 
            if work_inv.has_id(file_id):
119
 
                del work_inv[file_id]
120
 
 
121
 
        if rev_id is None:
122
 
            rev_id = _gen_revision_id(branch, time.time())
123
 
        inv_id = rev_id
124
 
 
125
 
        inv_tmp = tempfile.TemporaryFile()
126
 
        pack_xml(new_inv, inv_tmp)
127
 
        inv_tmp.seek(0)
128
 
        branch.inventory_store.add(inv_tmp, inv_id)
129
 
        mutter('new inventory_id is {%s}' % inv_id)
130
 
 
131
 
        # We could also just sha hash the inv_tmp file
132
 
        # however, in the case that branch.inventory_store.add()
133
 
        # ever actually does anything special
134
 
        inv_sha1 = branch.get_inventory_sha1(inv_id)
135
 
 
136
 
        branch._write_inventory(work_inv)
137
 
 
138
 
        if timestamp == None:
139
 
            timestamp = time.time()
140
 
 
141
 
        if committer == None:
142
 
            committer = username(branch)
143
 
 
144
 
        if timezone == None:
145
 
            timezone = local_time_offset()
146
 
 
147
 
        mutter("building commit log message")
148
 
        rev = Revision(timestamp=timestamp,
149
 
                       timezone=timezone,
150
 
                       committer=committer,
151
 
                       message = message,
152
 
                       inventory_id=inv_id,
153
 
                       inventory_sha1=inv_sha1,
154
 
                       revision_id=rev_id)
155
 
 
156
 
        rev.parents = []
157
 
        precursor_id = branch.last_patch()
158
 
        if precursor_id:
159
 
            precursor_sha1 = branch.get_revision_sha1(precursor_id)
160
 
            rev.parents.append(RevisionReference(precursor_id, precursor_sha1))
161
 
        for merge_rev in pending_merges:
162
 
            rev.parents.append(RevisionReference(merge_rev))            
163
 
 
164
 
        rev_tmp = tempfile.TemporaryFile()
165
 
        pack_xml(rev, rev_tmp)
166
 
        rev_tmp.seek(0)
167
 
        branch.revision_store.add(rev_tmp, rev_id)
168
 
        mutter("new revision_id is {%s}" % rev_id)
169
 
 
170
 
        ## XXX: Everything up to here can simply be orphaned if we abort
171
 
        ## the commit; it will leave junk files behind but that doesn't
172
 
        ## matter.
173
 
 
174
 
        ## TODO: Read back the just-generated changeset, and make sure it
175
 
        ## applies and recreates the right state.
176
 
 
177
 
        ## TODO: Also calculate and store the inventory SHA1
178
 
        mutter("committing patch r%d" % (branch.revno() + 1))
179
 
 
180
 
        branch.append_revision(rev_id)
181
 
 
182
 
        branch.set_pending_merges([])
183
 
 
184
 
        if verbose:
185
 
            # disabled; should go through logging
186
 
            # note("commited r%d" % branch.revno())
187
 
            # print ("commited r%d" % branch.revno())
188
 
            pass
189
 
    finally:
190
 
        branch.unlock()
191
 
 
192
 
 
193
 
 
194
 
def _gen_revision_id(branch, when):
195
 
    """Return new revision-id."""
196
 
    from binascii import hexlify
197
 
    from bzrlib.osutils import rand_bytes, compact_date, user_email
198
 
 
199
 
    s = '%s-%s-' % (user_email(branch), compact_date(when))
200
 
    s += hexlify(rand_bytes(8))
201
 
    return s
202
 
 
203
 
 
204
 
def _gather_commit(branch, work_tree, work_inv, basis_inv, specific_files,
205
 
                   verbose):
206
 
    """Build inventory preparatory to commit.
207
 
 
208
 
    Returns missing_ids, new_inv, any_changes.
209
 
 
210
 
    This adds any changed files into the text store, and sets their
211
 
    test-id, sha and size in the returned inventory appropriately.
212
 
 
213
 
    missing_ids
214
 
        Modified to hold a list of files that have been deleted from
215
 
        the working directory; these should be removed from the
216
 
        working inventory.
217
 
    """
218
 
    from bzrlib.inventory import Inventory
219
 
    from bzrlib.osutils import isdir, isfile, sha_string, quotefn, \
 
54
    import os, time, tempfile
 
55
 
 
56
    from inventory import Inventory
 
57
    from osutils import isdir, isfile, sha_string, quotefn, \
220
58
         local_time_offset, username, kind_marker, is_inside_any
221
59
    
222
 
    from bzrlib.branch import gen_file_id
223
 
    from bzrlib.errors import BzrError
224
 
    from bzrlib.revision import Revision
225
 
    from bzrlib.trace import mutter, note
226
 
 
227
 
    any_changes = False
228
 
    inv = Inventory(work_inv.root.file_id)
 
60
    from branch import gen_file_id
 
61
    from errors import BzrError
 
62
    from revision import Revision
 
63
    from trace import mutter, note
 
64
 
 
65
    branch._need_writelock()
 
66
 
 
67
    # First walk over the working inventory; and both update that
 
68
    # and also build a new revision inventory.  The revision
 
69
    # inventory needs to hold the text-id, sha1 and size of the
 
70
    # actual file versions committed in the revision.  (These are
 
71
    # not present in the working inventory.)  We also need to
 
72
    # detect missing/deleted files, and remove them from the
 
73
    # working inventory.
 
74
 
 
75
    work_tree = branch.working_tree()
 
76
    work_inv = work_tree.inventory
 
77
    inv = Inventory()
 
78
    basis = branch.basis_tree()
 
79
    basis_inv = basis.inventory
229
80
    missing_ids = []
230
 
    
 
81
 
 
82
    if verbose:
 
83
        note('looking for changes...')
 
84
        
231
85
    for path, entry in work_inv.iter_entries():
232
86
        ## TODO: Check that the file kind has not changed from the previous
233
87
        ## revision of this file (if any).
234
88
 
 
89
        entry = entry.copy()
 
90
 
235
91
        p = branch.abspath(path)
236
92
        file_id = entry.file_id
237
93
        mutter('commit prep file %s, id %r ' % (p, file_id))
238
94
 
239
95
        if specific_files and not is_inside_any(specific_files, path):
240
 
            mutter('  skipping file excluded from commit')
241
96
            if basis_inv.has_id(file_id):
242
97
                # carry over with previous state
243
98
                inv.add(basis_inv[file_id].copy())
249
104
        if not work_tree.has_id(file_id):
250
105
            if verbose:
251
106
                print('deleted %s%s' % (path, kind_marker(entry.kind)))
252
 
                any_changes = True
253
107
            mutter("    file is missing, removing from inventory")
254
108
            missing_ids.append(file_id)
255
109
            continue
256
110
 
257
 
        # this is present in the new inventory; may be new, modified or
258
 
        # unchanged.
259
 
        old_ie = basis_inv.has_id(file_id) and basis_inv[file_id]
260
 
        
261
 
        entry = entry.copy()
262
111
        inv.add(entry)
263
112
 
264
 
        if old_ie:
265
 
            old_kind = old_ie.kind
 
113
        if basis_inv.has_id(file_id):
 
114
            old_kind = basis_inv[file_id].kind
266
115
            if old_kind != entry.kind:
267
116
                raise BzrError("entry %r changed kind from %r to %r"
268
117
                        % (file_id, old_kind, entry.kind))
277
126
 
278
127
            new_sha1 = work_tree.get_file_sha1(file_id)
279
128
 
 
129
            old_ie = basis_inv.has_id(file_id) and basis_inv[file_id]
280
130
            if (old_ie
281
131
                and old_ie.text_sha1 == new_sha1):
282
132
                ## assert content == basis.get_file(file_id).read()
288
138
            else:
289
139
                content = file(p, 'rb').read()
290
140
 
291
 
                # calculate the sha again, just in case the file contents
292
 
                # changed since we updated the cache
293
141
                entry.text_sha1 = sha_string(content)
294
142
                entry.text_size = len(content)
295
143
 
296
144
                entry.text_id = gen_file_id(entry.name)
297
145
                branch.text_store.add(content, entry.text_id)
298
146
                mutter('    stored with text_id {%s}' % entry.text_id)
299
 
 
300
 
        if verbose:
301
 
            marked = path + kind_marker(entry.kind)
302
 
            if not old_ie:
303
 
                print 'added', marked
304
 
                any_changes = True
305
 
            elif old_ie == entry:
306
 
                pass                    # unchanged
307
 
            elif (old_ie.name == entry.name
308
 
                  and old_ie.parent_id == entry.parent_id):
309
 
                print 'modified', marked
310
 
                any_changes = True
311
 
            else:
312
 
                print 'renamed', marked
313
 
                any_changes = True
314
 
                        
315
 
    return missing_ids, inv, any_changes
 
147
                if verbose:
 
148
                    if not old_ie:
 
149
                        print('added %s' % path)
 
150
                    elif (old_ie.name == entry.name
 
151
                          and old_ie.parent_id == entry.parent_id):
 
152
                        print('modified %s' % path)
 
153
                    else:
 
154
                        print('renamed %s' % path)
 
155
 
 
156
 
 
157
    for file_id in missing_ids:
 
158
        # Any files that have been deleted are now removed from the
 
159
        # working inventory.  Files that were not selected for commit
 
160
        # are left as they were in the working inventory and ommitted
 
161
        # from the revision inventory.
 
162
        
 
163
        # have to do this later so we don't mess up the iterator.
 
164
        # since parents may be removed before their children we
 
165
        # have to test.
 
166
 
 
167
        # FIXME: There's probably a better way to do this; perhaps
 
168
        # the workingtree should know how to filter itbranch.
 
169
        if work_inv.has_id(file_id):
 
170
            del work_inv[file_id]
 
171
 
 
172
 
 
173
    if rev_id is None:
 
174
        rev_id = _gen_revision_id(time.time())
 
175
    inv_id = rev_id
 
176
 
 
177
    inv_tmp = tempfile.TemporaryFile()
 
178
    inv.write_xml(inv_tmp)
 
179
    inv_tmp.seek(0)
 
180
    branch.inventory_store.add(inv_tmp, inv_id)
 
181
    mutter('new inventory_id is {%s}' % inv_id)
 
182
 
 
183
    branch._write_inventory(work_inv)
 
184
 
 
185
    if timestamp == None:
 
186
        timestamp = time.time()
 
187
 
 
188
    if committer == None:
 
189
        committer = username()
 
190
 
 
191
    if timezone == None:
 
192
        timezone = local_time_offset()
 
193
 
 
194
    mutter("building commit log message")
 
195
    rev = Revision(timestamp=timestamp,
 
196
                   timezone=timezone,
 
197
                   committer=committer,
 
198
                   precursor = branch.last_patch(),
 
199
                   message = message,
 
200
                   inventory_id=inv_id,
 
201
                   revision_id=rev_id)
 
202
 
 
203
    rev_tmp = tempfile.TemporaryFile()
 
204
    rev.write_xml(rev_tmp)
 
205
    rev_tmp.seek(0)
 
206
    branch.revision_store.add(rev_tmp, rev_id)
 
207
    mutter("new revision_id is {%s}" % rev_id)
 
208
 
 
209
    ## XXX: Everything up to here can simply be orphaned if we abort
 
210
    ## the commit; it will leave junk files behind but that doesn't
 
211
    ## matter.
 
212
 
 
213
    ## TODO: Read back the just-generated changeset, and make sure it
 
214
    ## applies and recreates the right state.
 
215
 
 
216
    ## TODO: Also calculate and store the inventory SHA1
 
217
    mutter("committing patch r%d" % (branch.revno() + 1))
 
218
 
 
219
    branch.append_revision(rev_id)
 
220
 
 
221
    if verbose:
 
222
        note("commited r%d" % branch.revno())
 
223
 
 
224
 
 
225
 
 
226
def _gen_revision_id(when):
 
227
    """Return new revision-id."""
 
228
    from binascii import hexlify
 
229
    from osutils import rand_bytes, compact_date, user_email
 
230
 
 
231
    s = '%s-%s-' % (user_email(), compact_date(when))
 
232
    s += hexlify(rand_bytes(8))
 
233
    return s
316
234
 
317
235