/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
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
29
30
1189 by Martin Pool
- BROKEN: partial support for commit into weave
31
import os
32
import sys
1188 by Martin Pool
- clean up imports in commit code
33
import time
34
import tempfile
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
35
import sha
36
1188 by Martin Pool
- clean up imports in commit code
37
from binascii import hexlify
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
38
from cStringIO import StringIO
1188 by Martin Pool
- clean up imports in commit code
39
40
from bzrlib.osutils import (local_time_offset, username,
41
                            rand_bytes, compact_date, user_email,
42
                            kind_marker, is_inside_any, quotefn,
1189 by Martin Pool
- BROKEN: partial support for commit into weave
43
                            sha_string, sha_file, isdir, isfile)
1188 by Martin Pool
- clean up imports in commit code
44
from bzrlib.branch import gen_file_id
45
from bzrlib.errors import BzrError, PointlessCommit
46
from bzrlib.revision import Revision, RevisionReference
47
from bzrlib.trace import mutter, note
1189 by Martin Pool
- BROKEN: partial support for commit into weave
48
from bzrlib.xml5 import serializer_v5
1188 by Martin Pool
- clean up imports in commit code
49
from bzrlib.inventory import Inventory
1189 by Martin Pool
- BROKEN: partial support for commit into weave
50
from bzrlib.delta import compare_trees
51
from bzrlib.weave import Weave
52
from bzrlib.weavefile import read_weave, write_weave_v5
53
from bzrlib.atomicfile import AtomicFile
54
55
56
class NullCommitReporter(object):
57
    """I report on progress of a commit."""
58
    def added(self, path):
59
        pass
60
61
    def removed(self, path):
62
        pass
63
64
    def renamed(self, old_path, new_path):
65
        pass
66
67
68
class ReportCommitToLog(NullCommitReporter):
69
    def added(self, path):
70
        note('added %s', path)
71
72
    def removed(self, path):
73
        note('removed %s', path)
74
75
    def renamed(self, old_path, new_path):
76
        note('renamed %s => %s', old_path, new_path)
77
78
79
class Commit(object):
80
    """Task of committing a new revision.
81
82
    This is a MethodObject: it accumulates state as the commit is
83
    prepared, and then it is discarded.  It doesn't represent
84
    historical revisions, just the act of recording a new one.
85
86
            missing_ids
87
            Modified to hold a list of files that have been deleted from
88
            the working directory; these should be removed from the
89
            working inventory.
485 by Martin Pool
- move commit code into its own module
90
    """
1189 by Martin Pool
- BROKEN: partial support for commit into weave
91
    def __init__(self,
92
                 reporter=None):
93
        if reporter is not None:
94
            self.reporter = reporter
95
        else:
96
            self.reporter = NullCommitReporter()
97
98
        
99
    def commit(self,
100
               branch, message,
101
               timestamp=None,
102
               timezone=None,
103
               committer=None,
104
               specific_files=None,
105
               rev_id=None,
106
               allow_pointless=True):
107
        """Commit working copy as a new revision.
108
109
        The basic approach is to add all the file texts into the
110
        store, then the inventory, then make a new revision pointing
111
        to that inventory and store that.
112
113
        This is not quite safe if the working copy changes during the
114
        commit; for the moment that is simply not allowed.  A better
115
        approach is to make a temporary copy of the files before
116
        computing their hashes, and then add those hashes in turn to
117
        the inventory.  This should mean at least that there are no
118
        broken hash pointers.  There is no way we can get a snapshot
119
        of the whole directory at an instant.  This would also have to
120
        be robust against files disappearing, moving, etc.  So the
121
        whole thing is a bit hard.
122
123
        This raises PointlessCommit if there are no changes, no new merges,
124
        and allow_pointless  is false.
125
126
        timestamp -- if not None, seconds-since-epoch for a
127
             postdated/predated commit.
128
129
        specific_files
130
            If true, commit only those files.
131
132
        rev_id
133
            If set, use this as the new revision id.
134
            Useful for test or import commands that need to tightly
135
            control what revisions are assigned.  If you duplicate
136
            a revision id that exists elsewhere it is your own fault.
137
            If null (default), a time/random revision id is generated.
138
        """
139
140
        self.branch = branch
141
        self.branch.lock_write()
142
        self.rev_id = rev_id
143
        self.specific_files = specific_files
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
144
        self.allow_pointless = allow_pointless
1189 by Martin Pool
- BROKEN: partial support for commit into weave
145
146
        if timestamp is None:
147
            self.timestamp = time.time()
148
        else:
149
            self.timestamp = long(timestamp)
150
            
151
        if committer is None:
152
            self.committer = username(self.branch)
153
        else:
154
            assert isinstance(committer, basestring), type(committer)
155
            self.committer = committer
156
157
        if timezone is None:
158
            self.timezone = local_time_offset()
159
        else:
160
            self.timezone = int(timezone)
161
162
        assert isinstance(message, basestring), type(message)
163
        self.message = message
164
165
        try:
166
            # First walk over the working inventory; and both update that
167
            # and also build a new revision inventory.  The revision
168
            # inventory needs to hold the text-id, sha1 and size of the
169
            # actual file versions committed in the revision.  (These are
170
            # not present in the working inventory.)  We also need to
171
            # detect missing/deleted files, and remove them from the
172
            # working inventory.
173
174
            self.work_tree = self.branch.working_tree()
175
            self.work_inv = self.work_tree.inventory
176
            self.basis_tree = self.branch.basis_tree()
177
            self.basis_inv = self.basis_tree.inventory
178
179
            self.pending_merges = self.branch.pending_merges()
1199 by Martin Pool
- weave commit records per-file ancestors
180
            if self.pending_merges:
181
                raise NotImplementedError("sorry, can't commit merges to the weave format yet")
182
            
1189 by Martin Pool
- BROKEN: partial support for commit into weave
183
            if self.rev_id is None:
184
                self.rev_id = _gen_revision_id(self.branch, time.time())
185
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
186
            # todo: update hashcache
1189 by Martin Pool
- BROKEN: partial support for commit into weave
187
            self.delta = compare_trees(self.basis_tree, self.work_tree,
188
                                       specific_files=self.specific_files)
189
190
            if not (self.delta.has_changed()
191
                    or self.allow_pointless
192
                    or self.pending_merges):
193
                raise PointlessCommit()
194
195
            self.new_inv = self.basis_inv.copy()
196
197
            self.delta.show(sys.stdout)
198
199
            self._remove_deleted()
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
200
            self._store_files()
1189 by Martin Pool
- BROKEN: partial support for commit into weave
201
202
            self.branch._write_inventory(self.work_inv)
203
            self._record_inventory()
204
205
            self._make_revision()
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
206
            note('committted r%d {%s}', (self.branch.revno() + 1),
207
                 self.rev_id)
208
            self.branch.append_revision(self.rev_id)
1189 by Martin Pool
- BROKEN: partial support for commit into weave
209
            self.branch.set_pending_merges([])
210
        finally:
211
            self.branch.unlock()
212
213
214
    def _record_inventory(self):
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
215
        inv_tmp = StringIO()
1189 by Martin Pool
- BROKEN: partial support for commit into weave
216
        serializer_v5.write_inventory(self.new_inv, inv_tmp)
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
217
        self.inv_sha1 = sha_string(inv_tmp.getvalue())
1189 by Martin Pool
- BROKEN: partial support for commit into weave
218
        inv_tmp.seek(0)
219
        self.branch.inventory_store.add(inv_tmp, self.rev_id)
220
221
222
    def _make_revision(self):
223
        """Record a new revision object for this commit."""
224
        self.rev = Revision(timestamp=self.timestamp,
225
                            timezone=self.timezone,
226
                            committer=self.committer,
227
                            message=self.message,
228
                            inventory_sha1=self.inv_sha1,
229
                            revision_id=self.rev_id)
230
231
        self.rev.parents = []
232
        precursor_id = self.branch.last_patch()
717 by Martin Pool
- correctly set parent list when committing first
233
        if precursor_id:
1189 by Martin Pool
- BROKEN: partial support for commit into weave
234
            self.rev.parents.append(RevisionReference(precursor_id))
235
        for merge_rev in self.pending_merges:
236
            rev.parents.append(RevisionReference(merge_rev))
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
237
238
        rev_tmp = tempfile.TemporaryFile()
1189 by Martin Pool
- BROKEN: partial support for commit into weave
239
        serializer_v5.write_revision(self.rev, rev_tmp)
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
240
        rev_tmp.seek(0)
1189 by Martin Pool
- BROKEN: partial support for commit into weave
241
        self.branch.revision_store.add(rev_tmp, self.rev_id)
242
        mutter('new revision_id is {%s}', self.rev_id)
243
244
245
    def _remove_deleted(self):
246
        """Remove deleted files from the working and stored inventories."""
247
        for path, id, kind in self.delta.removed:
248
            if self.work_inv.has_id(id):
249
                del self.work_inv[id]
250
            if self.new_inv.has_id(id):
251
                del self.new_inv[id]
252
253
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
254
255
    def _store_files(self):
1189 by Martin Pool
- BROKEN: partial support for commit into weave
256
        """Store new texts of modified/added files."""
257
        for path, id, kind in self.delta.modified:
258
            if kind != 'file':
259
                continue
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
260
            self._store_file_text(id)
1189 by Martin Pool
- BROKEN: partial support for commit into weave
261
262
        for path, id, kind in self.delta.added:
263
            if kind != 'file':
264
                continue
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
265
            self._store_file_text(id)
1189 by Martin Pool
- BROKEN: partial support for commit into weave
266
267
        for old_path, new_path, id, kind, text_modified in self.delta.renamed:
268
            if kind != 'file':
269
                continue
270
            if not text_modified:
271
                continue
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
272
            self._store_file_text(id)
273
274
275
    def _store_file_text(self, file_id):
1189 by Martin Pool
- BROKEN: partial support for commit into weave
276
        """Store updated text for one modified or added file."""
1199 by Martin Pool
- weave commit records per-file ancestors
277
        note('store new text for {%s} in revision {%s}',
278
             file_id, self.rev_id)
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
279
        new_lines = self.work_tree.get_file(file_id).readlines()
1199 by Martin Pool
- weave commit records per-file ancestors
280
        if file_id in self.new_inv:     # was in basis inventory
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
281
            ie = self.new_inv[file_id]
282
            assert ie.file_id == file_id
1199 by Martin Pool
- weave commit records per-file ancestors
283
            assert file_id in self.basis_inv
284
            assert self.basis_inv[file_id].kind == 'file'
285
            old_version = self.basis_inv[file_id].text_version
286
            file_parents = [old_version]
287
        else:                           # new in this revision
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
288
            ie = self.work_inv[file_id].copy()
289
            self.new_inv.add(ie)
1199 by Martin Pool
- weave commit records per-file ancestors
290
            assert file_id not in self.basis_inv
291
            file_parents = []
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
292
        assert ie.kind == 'file'
1199 by Martin Pool
- weave commit records per-file ancestors
293
        self._add_text_to_weave(file_id, new_lines, file_parents)
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
294
        # make a new inventory entry for this file, using whatever
295
        # it had in the working copy, plus details on the new text
296
        ie.text_sha1 = _sha_strings(new_lines)
297
        ie.text_size = sum(map(len, new_lines))
298
        ie.text_version = self.rev_id
299
        ie.entry_version = self.rev_id
300
301
1199 by Martin Pool
- weave commit records per-file ancestors
302
    def _add_text_to_weave(self, file_id, new_lines, parents):
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
303
        weave_fn = self.branch.controlfilename(['weaves', file_id+'.weave'])
1189 by Martin Pool
- BROKEN: partial support for commit into weave
304
        if os.path.exists(weave_fn):
305
            w = read_weave(file(weave_fn, 'rb'))
306
        else:
307
            w = Weave()
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
308
        # XXX: Should set the appropriate parents by looking for this file_id
309
        # in all revision parents
1199 by Martin Pool
- weave commit records per-file ancestors
310
        parent_idxs = map(w.lookup, parents)
311
        w.add(self.rev_id, parent_idxs, new_lines)
1189 by Martin Pool
- BROKEN: partial support for commit into weave
312
        af = AtomicFile(weave_fn)
313
        try:
314
            write_weave_v5(w, af)
315
            af.commit()
316
        finally:
317
            af.close()
318
319
320
def _gen_revision_id(branch, when):
321
    """Return new revision-id."""
322
    s = '%s-%s-' % (user_email(branch), compact_date(when))
323
    s += hexlify(rand_bytes(8))
324
    return s
633 by Martin Pool
- Show added/renamed/modified messages from commit for non-file
325
326
1194 by Martin Pool
- [BROKEN] more progress of commit into weaves
327
def _sha_strings(strings):
328
    """Return the sha-1 of concatenation of strings"""
329
    s = sha.new()
330
    map(s.update, strings)
331
    return s.hexdigest()