/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
485 by Martin Pool
- move commit code into its own module
1
# Copyright (C) 2005 Canonical Ltd
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
18
## XXX: Can we do any better about making interrupted commits change
19
## nothing?
20
21
## XXX: If we merged two versions of a file then we still need to
22
## create a new version representing that merge, even if it didn't
23
## change from the parent.
1189 by Martin Pool
- BROKEN: partial support for commit into weave
24
25
## TODO: Read back the just-generated changeset, and make sure it
26
## applies and recreates the right state.
27
28
1245 by Martin Pool
doc
29
## This is not quite safe if the working copy changes during the
30
## commit; for the moment that is simply not allowed.  A better
31
## approach is to make a temporary copy of the files before
32
## computing their hashes, and then add those hashes in turn to
33
## the inventory.  This should mean at least that there are no
34
## broken hash pointers.  There is no way we can get a snapshot
35
## of the whole directory at an instant.  This would also have to
36
## be robust against files disappearing, moving, etc.  So the
37
## whole thing is a bit hard.
38
39
## The newly committed revision is going to have a shape corresponding
40
## to that of the working inventory.  Files that are not in the
41
## working tree and that were in the predecessor are reported as
42
## removed -- this can include files that were either removed from the
43
## inventory or deleted in the working tree.  If they were only
44
## deleted from disk, they are removed from the working inventory.
45
46
## We then consider the remaining entries, which will be in the new
47
## version.  Directory entries are simply copied across.  File entries
48
## must be checked to see if a new version of the file should be
49
## recorded.  For each parent revision inventory, we check to see what
50
## version of the file was present.  If the file was present in at
51
## least one tree, and if it was the same version in all the trees,
52
## then we can just refer to that version.  Otherwise, a new version
53
## representing the merger of the file versions must be added.
54
55
56
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
57
58
1189 by Martin Pool
- BROKEN: partial support for commit into weave
59
import os
60
import sys
1188 by Martin Pool
- clean up imports in commit code
61
import time
62
import tempfile
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
63
import sha
64
1188 by Martin Pool
- clean up imports in commit code
65
from binascii import hexlify
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
66
from cStringIO import StringIO
1188 by Martin Pool
- clean up imports in commit code
67
68
from bzrlib.osutils import (local_time_offset, username,
69
                            rand_bytes, compact_date, user_email,
70
                            kind_marker, is_inside_any, quotefn,
1235 by Martin Pool
- split sha_strings into osutils
71
                            sha_string, sha_strings, sha_file, isdir, isfile)
1225 by Martin Pool
- branch now tracks ancestry - all merged revisions
72
from bzrlib.branch import gen_file_id, INVENTORY_FILEID, ANCESTRY_FILEID
1188 by Martin Pool
- clean up imports in commit code
73
from bzrlib.errors import BzrError, PointlessCommit
74
from bzrlib.revision import Revision, RevisionReference
75
from bzrlib.trace import mutter, note
1189 by Martin Pool
- BROKEN: partial support for commit into weave
76
from bzrlib.xml5 import serializer_v5
1188 by Martin Pool
- clean up imports in commit code
77
from bzrlib.inventory import Inventory
1189 by Martin Pool
- BROKEN: partial support for commit into weave
78
from bzrlib.delta import compare_trees
79
from bzrlib.weave import Weave
80
from bzrlib.weavefile import read_weave, write_weave_v5
81
from bzrlib.atomicfile import AtomicFile
82
83
1205 by Martin Pool
- add bzrlib.commit.commit compatability interface
84
def commit(*args, **kwargs):
85
    """Commit a new revision to a branch.
86
87
    Function-style interface for convenience of old callers.
88
89
    New code should use the Commit class instead.
90
    """
91
    Commit().commit(*args, **kwargs)
92
93
1189 by Martin Pool
- BROKEN: partial support for commit into weave
94
class NullCommitReporter(object):
95
    """I report on progress of a commit."""
96
    def added(self, path):
97
        pass
98
99
    def removed(self, path):
100
        pass
101
102
    def renamed(self, old_path, new_path):
103
        pass
104
105
106
class ReportCommitToLog(NullCommitReporter):
107
    def added(self, path):
108
        note('added %s', path)
109
110
    def removed(self, path):
111
        note('removed %s', path)
112
113
    def renamed(self, old_path, new_path):
114
        note('renamed %s => %s', old_path, new_path)
115
116
117
class Commit(object):
118
    """Task of committing a new revision.
119
120
    This is a MethodObject: it accumulates state as the commit is
121
    prepared, and then it is discarded.  It doesn't represent
122
    historical revisions, just the act of recording a new one.
123
124
            missing_ids
125
            Modified to hold a list of files that have been deleted from
126
            the working directory; these should be removed from the
127
            working inventory.
485 by Martin Pool
- move commit code into its own module
128
    """
1189 by Martin Pool
- BROKEN: partial support for commit into weave
129
    def __init__(self,
130
                 reporter=None):
131
        if reporter is not None:
132
            self.reporter = reporter
133
        else:
134
            self.reporter = NullCommitReporter()
135
136
        
137
    def commit(self,
138
               branch, message,
139
               timestamp=None,
140
               timezone=None,
141
               committer=None,
142
               specific_files=None,
143
               rev_id=None,
144
               allow_pointless=True):
145
        """Commit working copy as a new revision.
146
147
        The basic approach is to add all the file texts into the
148
        store, then the inventory, then make a new revision pointing
149
        to that inventory and store that.
150
151
        This raises PointlessCommit if there are no changes, no new merges,
152
        and allow_pointless  is false.
153
154
        timestamp -- if not None, seconds-since-epoch for a
155
             postdated/predated commit.
156
157
        specific_files
158
            If true, commit only those files.
159
160
        rev_id
161
            If set, use this as the new revision id.
162
            Useful for test or import commands that need to tightly
163
            control what revisions are assigned.  If you duplicate
164
            a revision id that exists elsewhere it is your own fault.
165
            If null (default), a time/random revision id is generated.
166
        """
167
168
        self.branch = branch
169
        self.rev_id = rev_id
170
        self.specific_files = specific_files
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
171
        self.allow_pointless = allow_pointless
1189 by Martin Pool
- BROKEN: partial support for commit into weave
172
173
        if timestamp is None:
174
            self.timestamp = time.time()
175
        else:
176
            self.timestamp = long(timestamp)
177
            
178
        if committer is None:
179
            self.committer = username(self.branch)
180
        else:
181
            assert isinstance(committer, basestring), type(committer)
182
            self.committer = committer
183
184
        if timezone is None:
185
            self.timezone = local_time_offset()
186
        else:
187
            self.timezone = int(timezone)
188
189
        assert isinstance(message, basestring), type(message)
190
        self.message = message
191
1245 by Martin Pool
doc
192
        self.branch.lock_write()
1189 by Martin Pool
- BROKEN: partial support for commit into weave
193
        try:
194
            # First walk over the working inventory; and both update that
195
            # and also build a new revision inventory.  The revision
196
            # inventory needs to hold the text-id, sha1 and size of the
197
            # actual file versions committed in the revision.  (These are
198
            # not present in the working inventory.)  We also need to
199
            # detect missing/deleted files, and remove them from the
200
            # working inventory.
201
202
            self.work_tree = self.branch.working_tree()
203
            self.work_inv = self.work_tree.inventory
204
            self.basis_tree = self.branch.basis_tree()
205
            self.basis_inv = self.basis_tree.inventory
206
1223 by Martin Pool
- store inventories in weave
207
            self._gather_parents()
208
1189 by Martin Pool
- BROKEN: partial support for commit into weave
209
            if self.rev_id is None:
210
                self.rev_id = _gen_revision_id(self.branch, time.time())
211
1245 by Martin Pool
doc
212
            self._remove_deletions()
213
1225 by Martin Pool
- branch now tracks ancestry - all merged revisions
214
            # TODO: update hashcache
1189 by Martin Pool
- BROKEN: partial support for commit into weave
215
            self.delta = compare_trees(self.basis_tree, self.work_tree,
216
                                       specific_files=self.specific_files)
217
218
            if not (self.delta.has_changed()
219
                    or self.allow_pointless
1223 by Martin Pool
- store inventories in weave
220
                    or len(self.parents) != 1):
1189 by Martin Pool
- BROKEN: partial support for commit into weave
221
                raise PointlessCommit()
222
223
            self.new_inv = self.basis_inv.copy()
224
1245 by Martin Pool
doc
225
            ## FIXME: Don't write to stdout!
1189 by Martin Pool
- BROKEN: partial support for commit into weave
226
            self.delta.show(sys.stdout)
227
228
            self._remove_deleted()
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
229
            self._store_files()
1189 by Martin Pool
- BROKEN: partial support for commit into weave
230
231
            self.branch._write_inventory(self.work_inv)
232
            self._record_inventory()
1225 by Martin Pool
- branch now tracks ancestry - all merged revisions
233
            self._record_ancestry()
1189 by Martin Pool
- BROKEN: partial support for commit into weave
234
235
            self._make_revision()
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
236
            note('committted r%d {%s}', (self.branch.revno() + 1),
237
                 self.rev_id)
238
            self.branch.append_revision(self.rev_id)
1189 by Martin Pool
- BROKEN: partial support for commit into weave
239
            self.branch.set_pending_merges([])
240
        finally:
241
            self.branch.unlock()
242
243
1245 by Martin Pool
doc
244
245
    def _remove_deletions(self):
246
        """Remove deleted files from the working inventory."""
247
        pass
248
249
250
1189 by Martin Pool
- BROKEN: partial support for commit into weave
251
    def _record_inventory(self):
1223 by Martin Pool
- store inventories in weave
252
        """Store the inventory for the new revision."""
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
253
        inv_tmp = StringIO()
1189 by Martin Pool
- BROKEN: partial support for commit into weave
254
        serializer_v5.write_inventory(self.new_inv, inv_tmp)
1223 by Martin Pool
- store inventories in weave
255
        inv_tmp.seek(0)
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
256
        self.inv_sha1 = sha_string(inv_tmp.getvalue())
1223 by Martin Pool
- store inventories in weave
257
        inv_lines = inv_tmp.readlines()
258
        self.branch.weave_store.add_text(INVENTORY_FILEID, self.rev_id,
259
                                         inv_lines, self.parents)
260
261
1225 by Martin Pool
- branch now tracks ancestry - all merged revisions
262
    def _record_ancestry(self):
263
        """Append merged revision ancestry to the ancestry file."""
264
        if len(self.parents) > 1:
265
            raise NotImplementedError("sorry, can't commit merges yet")
266
        w = self.branch.weave_store.get_weave_or_empty(ANCESTRY_FILEID)
267
        if self.parents:
268
            lines = w.get(w.lookup(self.parents[0]))
269
        else:
270
            lines = []
271
        lines.append(self.rev_id + '\n')
272
        parent_idxs = map(w.lookup, self.parents)
273
        w.add(self.rev_id, parent_idxs, lines)
274
        self.branch.weave_store.put_weave(ANCESTRY_FILEID, w)
275
276
1223 by Martin Pool
- store inventories in weave
277
    def _gather_parents(self):
278
        pending_merges = self.branch.pending_merges()
279
        if pending_merges:
280
            raise NotImplementedError("sorry, can't commit merges to the weave format yet")
281
        self.parents = []
1241 by Martin Pool
- rename last_patch to last_revision
282
        precursor_id = self.branch.last_revision()
1223 by Martin Pool
- store inventories in weave
283
        if precursor_id:
284
            self.parents.append(precursor_id)
285
        self.parents += pending_merges
1189 by Martin Pool
- BROKEN: partial support for commit into weave
286
287
288
    def _make_revision(self):
289
        """Record a new revision object for this commit."""
290
        self.rev = Revision(timestamp=self.timestamp,
291
                            timezone=self.timezone,
292
                            committer=self.committer,
293
                            message=self.message,
294
                            inventory_sha1=self.inv_sha1,
295
                            revision_id=self.rev_id)
1223 by Martin Pool
- store inventories in weave
296
        self.rev.parents = map(RevisionReference, self.parents)
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
297
        rev_tmp = tempfile.TemporaryFile()
1189 by Martin Pool
- BROKEN: partial support for commit into weave
298
        serializer_v5.write_revision(self.rev, rev_tmp)
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
299
        rev_tmp.seek(0)
1189 by Martin Pool
- BROKEN: partial support for commit into weave
300
        self.branch.revision_store.add(rev_tmp, self.rev_id)
301
        mutter('new revision_id is {%s}', self.rev_id)
302
303
304
    def _remove_deleted(self):
305
        """Remove deleted files from the working and stored inventories."""
306
        for path, id, kind in self.delta.removed:
307
            if self.work_inv.has_id(id):
308
                del self.work_inv[id]
309
            if self.new_inv.has_id(id):
310
                del self.new_inv[id]
311
312
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
313
314
    def _store_files(self):
1189 by Martin Pool
- BROKEN: partial support for commit into weave
315
        """Store new texts of modified/added files."""
1207 by Martin Pool
- fix bugs in adding files in subdirectories
316
        # We must make sure that directories are added before anything
317
        # inside them is added.  the files within the delta report are
318
        # sorted by path so we know the directory will come before its
319
        # contents. 
320
        for path, file_id, kind in self.delta.added:
321
            if kind != 'file':
322
                ie = self.work_inv[file_id].copy()
323
                self.new_inv.add(ie)
324
            else:
325
                self._store_file_text(file_id)
326
327
        for path, file_id, kind in self.delta.modified:
328
            if kind != 'file':
329
                continue
330
            self._store_file_text(file_id)
331
332
        for old_path, new_path, file_id, kind, text_modified in self.delta.renamed:
1189 by Martin Pool
- BROKEN: partial support for commit into weave
333
            if kind != 'file':
334
                continue
335
            if not text_modified:
336
                continue
1207 by Martin Pool
- fix bugs in adding files in subdirectories
337
            self._store_file_text(file_id)
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
338
339
340
    def _store_file_text(self, file_id):
1189 by Martin Pool
- BROKEN: partial support for commit into weave
341
        """Store updated text for one modified or added file."""
1199 by Martin Pool
- weave commit records per-file ancestors
342
        note('store new text for {%s} in revision {%s}',
343
             file_id, self.rev_id)
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
344
        new_lines = self.work_tree.get_file(file_id).readlines()
1199 by Martin Pool
- weave commit records per-file ancestors
345
        if file_id in self.new_inv:     # was in basis inventory
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
346
            ie = self.new_inv[file_id]
347
            assert ie.file_id == file_id
1199 by Martin Pool
- weave commit records per-file ancestors
348
            assert file_id in self.basis_inv
349
            assert self.basis_inv[file_id].kind == 'file'
350
            old_version = self.basis_inv[file_id].text_version
351
            file_parents = [old_version]
352
        else:                           # new in this revision
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
353
            ie = self.work_inv[file_id].copy()
354
            self.new_inv.add(ie)
1199 by Martin Pool
- weave commit records per-file ancestors
355
            assert file_id not in self.basis_inv
356
            file_parents = []
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
357
        assert ie.kind == 'file'
1199 by Martin Pool
- weave commit records per-file ancestors
358
        self._add_text_to_weave(file_id, new_lines, file_parents)
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
359
        # make a new inventory entry for this file, using whatever
360
        # it had in the working copy, plus details on the new text
1235 by Martin Pool
- split sha_strings into osutils
361
        ie.text_sha1 = sha_strings(new_lines)
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
362
        ie.text_size = sum(map(len, new_lines))
363
        ie.text_version = self.rev_id
364
        ie.entry_version = self.rev_id
365
366
1199 by Martin Pool
- weave commit records per-file ancestors
367
    def _add_text_to_weave(self, file_id, new_lines, parents):
1223 by Martin Pool
- store inventories in weave
368
        if file_id.startswith('__'):
369
            raise ValueError('illegal file-id %r for text file' % file_id)
370
        self.branch.weave_store.add_text(file_id, self.rev_id, new_lines, parents)
1189 by Martin Pool
- BROKEN: partial support for commit into weave
371
372
373
def _gen_revision_id(branch, when):
374
    """Return new revision-id."""
375
    s = '%s-%s-' % (user_email(branch), compact_date(when))
376
    s += hexlify(rand_bytes(8))
377
    return s
633 by Martin Pool
- Show added/renamed/modified messages from commit for non-file
378