~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Martin Pool
  • Date: 2006-01-30 06:23:50 UTC
  • mfrom: (1534.1.17 integration)
  • Revision ID: mbp@sourcefrog.net-20060130062350-d6f25277ddcdfd79
[merge] robert's integration of much recent work

Show diffs side-by-side

added added

removed removed

Lines of Context:
56
56
# merges from, then it should still be reported as newly added
57
57
# relative to the basis revision.
58
58
 
 
59
# TODO: Do checks that the tree can be committed *before* running the 
 
60
# editor; this should include checks for a pointless commit and for 
 
61
# unknown or missing files.
 
62
 
 
63
# TODO: If commit fails, leave the message in a file somewhere.
 
64
 
59
65
 
60
66
import os
61
67
import re
66
72
from binascii import hexlify
67
73
from cStringIO import StringIO
68
74
 
 
75
from bzrlib.atomicfile import AtomicFile
69
76
from bzrlib.osutils import (local_time_offset,
70
77
                            rand_bytes, compact_date,
71
78
                            kind_marker, is_inside_any, quotefn,
72
79
                            sha_string, sha_strings, sha_file, isdir, isfile,
73
80
                            split_lines)
74
 
from bzrlib.branch import gen_file_id
75
81
import bzrlib.config
76
82
from bzrlib.errors import (BzrError, PointlessCommit,
77
83
                           HistoryMissing,
78
 
                           ConflictsInTree
 
84
                           ConflictsInTree,
 
85
                           StrictCommitFailed
79
86
                           )
 
87
import bzrlib.gpg as gpg
80
88
from bzrlib.revision import Revision
 
89
from bzrlib.testament import Testament
81
90
from bzrlib.trace import mutter, note, warning
82
91
from bzrlib.xml5 import serializer_v5
83
92
from bzrlib.inventory import Inventory, ROOT_ID
84
93
from bzrlib.weave import Weave
85
94
from bzrlib.weavefile import read_weave, write_weave_v5
86
 
from bzrlib.atomicfile import AtomicFile
 
95
from bzrlib.workingtree import WorkingTree
87
96
 
88
97
 
89
98
def commit(*args, **kwargs):
115
124
    def missing(self, path):
116
125
        pass
117
126
 
 
127
 
118
128
class ReportCommitToLog(NullCommitReporter):
119
129
 
120
130
    def snapshot_change(self, change, path):
132
142
    def missing(self, path):
133
143
        note('missing %s', path)
134
144
 
 
145
 
135
146
class Commit(object):
136
147
    """Task of committing a new revision.
137
148
 
145
156
            working inventory.
146
157
    """
147
158
    def __init__(self,
148
 
                 reporter=None):
 
159
                 reporter=None,
 
160
                 config=None):
149
161
        if reporter is not None:
150
162
            self.reporter = reporter
151
163
        else:
152
164
            self.reporter = NullCommitReporter()
153
 
 
 
165
        if config is not None:
 
166
            self.config = config
 
167
        else:
 
168
            self.config = None
154
169
        
155
170
    def commit(self,
156
171
               branch, message,
160
175
               specific_files=None,
161
176
               rev_id=None,
162
177
               allow_pointless=True,
 
178
               strict=False,
163
179
               verbose=False,
164
180
               revprops=None):
165
181
        """Commit working copy as a new revision.
178
194
        allow_pointless -- If true (default), commit even if nothing
179
195
            has changed and no merges are recorded.
180
196
 
 
197
        strict -- If true, don't allow a commit if the working tree
 
198
            contains unknown files.
 
199
 
181
200
        revprops -- Properties for new revision
182
201
        """
183
202
        mutter('preparing to commit')
184
203
 
185
204
        self.branch = branch
186
 
        self.weave_store = branch.weave_store
 
205
        self.weave_store = branch.repository.weave_store
187
206
        self.rev_id = rev_id
188
207
        self.specific_files = specific_files
189
208
        self.allow_pointless = allow_pointless
190
 
        self.revprops = revprops
 
209
        self.revprops = {'branch-nick': branch.nick}
 
210
        if revprops:
 
211
            self.revprops.update(revprops)
 
212
        self.work_tree = WorkingTree(branch.base, branch)
 
213
 
 
214
        if strict:
 
215
            # raise an exception as soon as we find a single unknown.
 
216
            for unknown in self.work_tree.unknowns():
 
217
                raise StrictCommitFailed()
191
218
 
192
219
        if timestamp is None:
193
220
            self.timestamp = time.time()
194
221
        else:
195
222
            self.timestamp = long(timestamp)
196
223
            
 
224
        if self.config is None:
 
225
            self.config = bzrlib.config.BranchConfig(self.branch)
 
226
 
197
227
        if rev_id is None:
198
 
            self.rev_id = _gen_revision_id(self.branch, self.timestamp)
 
228
            self.rev_id = _gen_revision_id(self.config, self.timestamp)
199
229
        else:
200
230
            self.rev_id = rev_id
201
231
 
202
232
        if committer is None:
203
 
            self.committer = bzrlib.config.username(self.branch)
 
233
            self.committer = self.config.username()
204
234
        else:
205
235
            assert isinstance(committer, basestring), type(committer)
206
236
            self.committer = committer
210
240
        else:
211
241
            self.timezone = int(timezone)
212
242
 
213
 
        assert isinstance(message, basestring), type(message)
 
243
        if isinstance(message, str):
 
244
            message = message.decode(bzrlib.user_encoding)
 
245
        assert isinstance(message, unicode), type(message)
214
246
        self.message = message
215
247
        self._escape_commit_message()
216
248
 
217
249
        self.branch.lock_write()
218
250
        try:
219
 
            self.work_tree = self.branch.working_tree()
220
251
            self.work_inv = self.work_tree.inventory
221
252
            self.basis_tree = self.branch.basis_tree()
222
253
            self.basis_inv = self.basis_tree.inventory
241
272
 
242
273
            self._record_inventory()
243
274
            self._make_revision()
 
275
            self.work_tree.set_pending_merges([])
 
276
            self.branch.append_revision(self.rev_id)
244
277
            self.reporter.completed(self.branch.revno()+1, self.rev_id)
245
 
            self.branch.append_revision(self.rev_id)
246
 
            self.branch.set_pending_merges([])
 
278
            if self.config.post_commit() is not None:
 
279
                hooks = self.config.post_commit().split(' ')
 
280
                # this would be nicer with twisted.python.reflect.namedAny
 
281
                for hook in hooks:
 
282
                    result = eval(hook + '(branch, rev_id)',
 
283
                                  {'branch':self.branch,
 
284
                                   'bzrlib':bzrlib,
 
285
                                   'rev_id':self.rev_id})
247
286
        finally:
248
287
            self.branch.unlock()
249
288
 
251
290
        """Store the inventory for the new revision."""
252
291
        inv_text = serializer_v5.write_inventory_to_string(self.new_inv)
253
292
        self.inv_sha1 = sha_string(inv_text)
254
 
        s = self.branch.control_weaves
 
293
        s = self.branch.repository.control_weaves
255
294
        s.add_text('inventory', self.rev_id,
256
295
                   split_lines(inv_text), self.present_parents,
257
296
                   self.branch.get_transaction())
262
301
        # represented in well-formed XML; escape characters that
263
302
        # aren't listed in the XML specification
264
303
        # (http://www.w3.org/TR/REC-xml/#NT-Char).
265
 
        if isinstance(self.message, unicode):
266
 
            char_pattern = u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]'
267
 
        else:
268
 
            # Use a regular 'str' as pattern to avoid having re.subn
269
 
            # return 'unicode' results.
270
 
            char_pattern = '[^x09\x0A\x0D\x20-\xFF]'
271
304
        self.message, escape_count = re.subn(
272
 
            char_pattern,
 
305
            u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
273
306
            lambda match: match.group(0).encode('unicode_escape'),
274
307
            self.message)
275
308
        if escape_count:
277
310
 
278
311
    def _gather_parents(self):
279
312
        """Record the parents of a merge for merge detection."""
280
 
        pending_merges = self.branch.pending_merges()
 
313
        pending_merges = self.work_tree.pending_merges()
281
314
        self.parents = []
282
315
        self.parent_invs = []
283
316
        self.present_parents = []
286
319
            self.parents.append(precursor_id)
287
320
        self.parents += pending_merges
288
321
        for revision in self.parents:
289
 
            if self.branch.has_revision(revision):
290
 
                self.parent_invs.append(self.branch.get_inventory(revision))
 
322
            if self.branch.repository.has_revision(revision):
 
323
                inventory = self.branch.repository.get_inventory(revision)
 
324
                self.parent_invs.append(inventory)
291
325
                self.present_parents.append(revision)
292
326
 
293
327
    def _check_parents_present(self):
294
328
        for parent_id in self.parents:
295
329
            mutter('commit parent revision {%s}', parent_id)
296
 
            if not self.branch.has_revision(parent_id):
 
330
            if not self.branch.repository.has_revision(parent_id):
297
331
                if parent_id == self.branch.last_revision():
298
332
                    warning("parent is missing %r", parent_id)
299
333
                    raise HistoryMissing(self.branch, 'revision', parent_id)
313
347
        rev_tmp = StringIO()
314
348
        serializer_v5.write_revision(self.rev, rev_tmp)
315
349
        rev_tmp.seek(0)
316
 
        self.branch.revision_store.add(rev_tmp, self.rev_id)
 
350
        if self.config.signature_needed():
 
351
            plaintext = Testament(self.rev, self.new_inv).as_short_text()
 
352
            self.branch.repository.store_revision_signature(
 
353
                gpg.GPGStrategy(self.config), plaintext, self.rev_id)
 
354
        self.branch.repository.revision_store.add(rev_tmp, self.rev_id)
317
355
        mutter('new revision_id is {%s}', self.rev_id)
318
356
 
319
357
    def _remove_deleted(self):
339
377
            deleted_ids.sort(reverse=True)
340
378
            for path, file_id in deleted_ids:
341
379
                del self.work_inv[file_id]
342
 
            self.branch._write_inventory(self.work_inv)
 
380
            self.work_tree._write_inventory(self.work_inv)
343
381
 
344
382
    def _store_snapshot(self):
345
383
        """Pass over inventory and record a snapshot.
354
392
        for path, ie in self.new_inv.iter_entries():
355
393
            previous_entries = ie.find_previous_heads(
356
394
                self.parent_invs, 
357
 
                self.weave_store.get_weave_or_empty(ie.file_id,
 
395
                self.weave_store.get_weave_prelude_or_empty(ie.file_id,
358
396
                    self.branch.get_transaction()))
359
397
            if ie.revision is None:
360
398
                change = ie.snapshot(self.rev_id, path, previous_entries,
419
457
            if file_id not in self.new_inv:
420
458
                self.reporter.deleted(self.basis_inv.id2path(file_id))
421
459
 
422
 
def _gen_revision_id(branch, when):
 
460
def _gen_revision_id(config, when):
423
461
    """Return new revision-id."""
424
 
    s = '%s-%s-' % (bzrlib.config.user_email(branch), compact_date(when))
 
462
    s = '%s-%s-' % (config.user_email(), compact_date(when))
425
463
    s += hexlify(rand_bytes(8))
426
464
    return s