~bzr-pqm/bzr/bzr.dev

70 by mbp at sourcefrog
Prepare for smart recursive add.
1
# Copyright (C) 2005 Canonical Ltd
2
1 by mbp at sourcefrog
import from baz patch-364
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
800 by Martin Pool
Merge John's import-speedup branch:
18
import sys, os
1 by mbp at sourcefrog
import from baz patch-364
19
20
import bzrlib
800 by Martin Pool
Merge John's import-speedup branch:
21
from bzrlib.trace import mutter, note
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
23
     sha_file, appendpath, file_kind
24
from bzrlib.errors import BzrError
1 by mbp at sourcefrog
import from baz patch-364
25
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
27
## TODO: Maybe include checks for common corruption of newlines, etc?
28
29
416 by Martin Pool
- bzr log and bzr root now accept an http URL
30
def find_branch(f, **args):
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
31
    from transport import transport
907.1.10 by John Arbash Meinel
Updated find_branch so that it can search for the branch root.
32
    from local_transport import LocalTransport
33
    t = transport(f)
34
    # FIXME: This is a hack around transport so that
35
    #        We can search the local directories for
36
    #        a branch root.
907.1.13 by John Arbash Meinel
Fixed bzr root.
37
    if args.has_key('init') and args['init']:
38
        # Don't search if we are init-ing
39
        return Branch(t, **args)
907.1.10 by John Arbash Meinel
Updated find_branch so that it can search for the branch root.
40
    if isinstance(t, LocalTransport):
41
        root = find_branch_root(f)
42
        if root != f:
43
            t = transport(root)
44
    return Branch(t, **args)
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
45
600 by Martin Pool
- Better Branch.relpath that doesn't match on
46
def _relpath(base, path):
47
    """Return path relative to base, or raise exception.
48
49
    The path may be either an absolute path or a path relative to the
50
    current working directory.
51
52
    Lifted out of Branch.relpath for ease of testing.
53
54
    os.path.commonprefix (python2.4) has a bad bug that it works just
55
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
56
    avoids that problem."""
57
    rp = os.path.abspath(path)
58
59
    s = []
60
    head = rp
61
    while len(head) >= len(base):
62
        if head == base:
63
            break
64
        head, tail = os.path.split(head)
65
        if tail:
66
            s.insert(0, tail)
67
    else:
68
        from errors import NotBranchError
69
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
70
71
    return os.sep.join(s)
416 by Martin Pool
- bzr log and bzr root now accept an http URL
72
        
73
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
74
def find_branch_root(f=None):
75
    """Find the branch root enclosing f, or pwd.
76
416 by Martin Pool
- bzr log and bzr root now accept an http URL
77
    f may be a filename or a URL.
78
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
79
    It is not necessary that f exists.
80
81
    Basically we keep looking up until we find the control directory or
82
    run into the root."""
184 by mbp at sourcefrog
pychecker fixups
83
    if f == None:
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
84
        f = os.getcwd()
907.1.10 by John Arbash Meinel
Updated find_branch so that it can search for the branch root.
85
    else:
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
86
        f = os.path.realpath(f)
425 by Martin Pool
- check from aaron for existence of a branch
87
    if not os.path.exists(f):
88
        raise BzrError('%r does not exist' % f)
89
        
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
90
91
    orig_f = f
92
93
    while True:
94
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
95
            return f
96
        head, tail = os.path.split(f)
97
        if head == f:
98
            # reached the root, whatever that may be
184 by mbp at sourcefrog
pychecker fixups
99
            raise BzrError('%r is not in a branch' % orig_f)
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
100
        f = head
101
    
628 by Martin Pool
- merge aaron's updated merge/pull code
102
class DivergedBranches(Exception):
103
    def __init__(self, branch1, branch2):
104
        self.branch1 = branch1
105
        self.branch2 = branch2
106
        Exception.__init__(self, "These branches have diverged.")
1 by mbp at sourcefrog
import from baz patch-364
107
685 by Martin Pool
- add -r option to the branch command
108
109
class NoSuchRevision(BzrError):
110
    def __init__(self, branch, revision):
111
        self.branch = branch
112
        self.revision = revision
113
        msg = "Branch %s has no revision %d" % (branch, revision)
114
        BzrError.__init__(self, msg)
115
116
1 by mbp at sourcefrog
import from baz patch-364
117
######################################################################
118
# branch objects
119
558 by Martin Pool
- All top-level classes inherit from object
120
class Branch(object):
1 by mbp at sourcefrog
import from baz patch-364
121
    """Branch holding a history of revisions.
122
343 by Martin Pool
doc
123
    base
124
        Base directory of the branch.
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
125
126
    _lock_mode
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
127
        None, or 'r' or 'w'
128
129
    _lock_count
130
        If _lock_mode is true, a positive count of the number of times the
131
        lock has been taken.
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
132
614 by Martin Pool
- unify two defintions of LockError
133
    _lock
134
        Lock object from bzrlib.lock.
1 by mbp at sourcefrog
import from baz patch-364
135
    """
564 by Martin Pool
- Set Branch.base in class def to avoid it being undefined
136
    base = None
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
137
    _lock_mode = None
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
138
    _lock_count = None
615 by Martin Pool
Major rework of locking code:
139
    _lock = None
907.1.24 by John Arbash Meinel
Remote functionality work.
140
    cache_root = None
353 by Martin Pool
- Per-branch locks in read and write modes.
141
    
897 by Martin Pool
- merge john's revision-naming code
142
    # Map some sort of prefix into a namespace
143
    # stuff like "revno:10", "revid:", etc.
144
    # This should match a prefix with a function which accepts
145
    REVISION_NAMESPACES = {}
146
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
147
    def __init__(self, transport, init=False):
1 by mbp at sourcefrog
import from baz patch-364
148
        """Create new branch object at a particular location.
149
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
150
        transport -- A Transport object, defining how to access files.
151
                (If a string, transport.transport() will be used to
152
                create a Transport object)
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
153
        
254 by Martin Pool
- Doc cleanups from Magnus Therning
154
        init -- If True, create new control files in a previously
1 by mbp at sourcefrog
import from baz patch-364
155
             unversioned directory.  If False, the branch must already
156
             be versioned.
157
158
        In the test suite, creation of new trees is tested using the
159
        `ScratchBranch` class.
160
        """
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
161
        if isinstance(transport, basestring):
162
            from transport import transport as get_transport
163
            transport = get_transport(transport)
164
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
165
        self._transport = transport
1 by mbp at sourcefrog
import from baz patch-364
166
        if init:
167
            self._make_control()
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
168
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
169
        self._check_format()
1 by mbp at sourcefrog
import from baz patch-364
170
171
172
    def __str__(self):
907.1.5 by John Arbash Meinel
Some more work, including ScratchBranch changes.
173
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
1 by mbp at sourcefrog
import from baz patch-364
174
175
176
    __repr__ = __str__
177
178
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
179
    def __del__(self):
615 by Martin Pool
Major rework of locking code:
180
        if self._lock_mode or self._lock:
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
181
            from warnings import warn
182
            warn("branch %r was not explicitly unlocked" % self)
615 by Martin Pool
Major rework of locking code:
183
            self._lock.unlock()
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
184
907.1.23 by John Arbash Meinel
Branch objects now automatically create Cached stores if the protocol is_remote.
185
        # TODO: It might be best to do this somewhere else,
186
        # but it is nice for a Branch object to automatically
187
        # cache it's information.
188
        # Alternatively, we could have the Transport objects cache requests
189
        # See the earlier discussion about how major objects (like Branch)
190
        # should never expect their __del__ function to run.
191
        if self.cache_root is not None:
907.1.24 by John Arbash Meinel
Remote functionality work.
192
            #from warnings import warn
193
            #warn("branch %r auto-cleanup of cache files" % self)
907.1.23 by John Arbash Meinel
Branch objects now automatically create Cached stores if the protocol is_remote.
194
            try:
195
                import shutil
196
                shutil.rmtree(self.cache_root)
197
            except:
198
                pass
199
            self.cache_root = None
200
907.1.17 by John Arbash Meinel
Adding a Branch.base property, removing pull_loc()
201
    def _get_base(self):
907.1.19 by John Arbash Meinel
Updated ScratchBranch and Branch.base, All Tests PASS !!!
202
        if self._transport:
203
            return self._transport.base
204
        return None
907.1.17 by John Arbash Meinel
Adding a Branch.base property, removing pull_loc()
205
206
    base = property(_get_base)
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
207
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
208
209
    def lock_write(self):
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
210
        # TODO: Upgrade locking to support using a Transport,
211
        # and potentially a remote locking protocol
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
212
        if self._lock_mode:
213
            if self._lock_mode != 'w':
214
                from errors import LockError
215
                raise LockError("can't upgrade to a write lock from %r" %
216
                                self._lock_mode)
217
            self._lock_count += 1
218
        else:
907.1.24 by John Arbash Meinel
Remote functionality work.
219
            self._lock = self._transport.lock_write(
220
                    self._rel_controlfilename('branch-lock'))
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
221
            self._lock_mode = 'w'
222
            self._lock_count = 1
223
224
225
226
    def lock_read(self):
227
        if self._lock_mode:
228
            assert self._lock_mode in ('r', 'w'), \
229
                   "invalid lock mode %r" % self._lock_mode
230
            self._lock_count += 1
231
        else:
907.1.24 by John Arbash Meinel
Remote functionality work.
232
            self._lock = self._transport.lock_read(
233
                    self._rel_controlfilename('branch-lock'))
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
234
            self._lock_mode = 'r'
235
            self._lock_count = 1
236
                        
237
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
238
            
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
239
    def unlock(self):
240
        if not self._lock_mode:
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
241
            from errors import LockError
242
            raise LockError('branch %r is not locked' % (self))
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
243
244
        if self._lock_count > 1:
245
            self._lock_count -= 1
246
        else:
615 by Martin Pool
Major rework of locking code:
247
            self._lock.unlock()
248
            self._lock = None
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
249
            self._lock_mode = self._lock_count = None
353 by Martin Pool
- Per-branch locks in read and write modes.
250
251
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
252
    def abspath(self, name):
253
        """Return absolute filename for something in the branch"""
907.1.5 by John Arbash Meinel
Some more work, including ScratchBranch changes.
254
        return self._transport.abspath(name)
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
255
1 by mbp at sourcefrog
import from baz patch-364
256
68 by mbp at sourcefrog
- new relpath command and function
257
    def relpath(self, path):
258
        """Return path relative to this branch of something inside it.
259
260
        Raises an error if path is not in this branch."""
907.1.24 by John Arbash Meinel
Remote functionality work.
261
        return self._transport.relpath(path)
68 by mbp at sourcefrog
- new relpath command and function
262
263
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
264
    def _rel_controlfilename(self, file_or_path):
265
        if isinstance(file_or_path, basestring):
266
            file_or_path = [file_or_path]
267
        return [bzrlib.BZRDIR] + file_or_path
268
1 by mbp at sourcefrog
import from baz patch-364
269
    def controlfilename(self, file_or_path):
270
        """Return location relative to branch."""
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
271
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
1 by mbp at sourcefrog
import from baz patch-364
272
273
274
    def controlfile(self, file_or_path, mode='r'):
245 by mbp at sourcefrog
- control files always in utf-8-unix format
275
        """Open a control file for this branch.
276
277
        There are two classes of file in the control directory: text
278
        and binary.  binary files are untranslated byte streams.  Text
279
        control files are stored with Unix newlines and in UTF-8, even
280
        if the platform or locale defaults are different.
430 by Martin Pool
doc
281
282
        Controlfiles should almost never be opened in write mode but
283
        rather should be atomically copied and replaced using atomicfile.
245 by mbp at sourcefrog
- control files always in utf-8-unix format
284
        """
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
285
        import codecs
286
287
        relpath = self._rel_controlfilename(file_or_path)
288
        #TODO: codecs.open() buffers linewise, so it was overloaded with
289
        # a much larger buffer, do we need to do the same for getreader/getwriter?
290
291
        # TODO: Try to use transport.put() rather than branch.controlfile(mode='w')
292
        if mode == 'rb': 
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
293
            return self._transport.get(relpath)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
294
        elif mode == 'wb':
907.1.20 by John Arbash Meinel
Removed Transport.open(), making get + put encode/decode to utf-8
295
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfile")
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
296
        elif mode == 'r':
907.1.20 by John Arbash Meinel
Removed Transport.open(), making get + put encode/decode to utf-8
297
            return self._transport.get(relpath, decode=True)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
298
        elif mode == 'w':
907.1.20 by John Arbash Meinel
Removed Transport.open(), making get + put encode/decode to utf-8
299
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfile")
300
            #return codecs.getwriter('utf-8')(
301
            #        self._transport.open(relpath), errors='replace')
245 by mbp at sourcefrog
- control files always in utf-8-unix format
302
        else:
303
            raise BzrError("invalid controlfile mode %r" % mode)
304
907.1.20 by John Arbash Meinel
Removed Transport.open(), making get + put encode/decode to utf-8
305
    def put_controlfile(self, file_or_path, f, encode=True):
306
        """Write an entry as a controlfile.
307
308
        :param file_or_path: This is the sub-path underneath the bzr control directory
309
        :param f: A file-like or string object whose contents should be placed
310
                  in the appropriate location.
311
        :param encode:  If true, encode the contents as utf-8
312
        """
313
        self._transport.put(self._rel_controlfilename(file_or_path), f, encode=encode)
1 by mbp at sourcefrog
import from baz patch-364
314
315
    def _make_control(self):
800 by Martin Pool
Merge John's import-speedup branch:
316
        from bzrlib.inventory import Inventory
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
317
        from bzrlib.xml import pack_xml
907.1.20 by John Arbash Meinel
Removed Transport.open(), making get + put encode/decode to utf-8
318
        from cStringIO import StringIO
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
319
        
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
320
        self._transport.mkdir(self.controlfilename([]))
321
        self._transport.put(self._rel_controlfilename('README'),
1 by mbp at sourcefrog
import from baz patch-364
322
            "This is a Bazaar-NG control directory.\n"
679 by Martin Pool
- put trailing newline on newly-created .bzr/README
323
            "Do not change any files in this directory.\n")
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
324
        self._transport.put(self._rel_controlfilename('branch-format'),
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
325
            BZR_BRANCH_FORMAT)
1 by mbp at sourcefrog
import from baz patch-364
326
        for d in ('text-store', 'inventory-store', 'revision-store'):
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
327
            self._transport.mkdir(self._rel_controlfilename(d))
1 by mbp at sourcefrog
import from baz patch-364
328
        for f in ('revision-history', 'merged-patches',
353 by Martin Pool
- Per-branch locks in read and write modes.
329
                  'pending-merged-patches', 'branch-name',
815 by Martin Pool
- track pending-merges
330
                  'branch-lock',
331
                  'pending-merges'):
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
332
            self._transport.put(self._rel_controlfilename(f), '')
907.1.5 by John Arbash Meinel
Some more work, including ScratchBranch changes.
333
        mutter('created control directory in ' + self._transport.base)
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
334
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
335
        # TODO: Try and do this with self._transport.put() instead
907.1.20 by John Arbash Meinel
Removed Transport.open(), making get + put encode/decode to utf-8
336
        sio = StringIO()
337
        pack_xml(Inventory(), sio)
907.1.22 by John Arbash Meinel
Fixed some encoding issues, added is_remote function for Transport objects.
338
        sio.seek(0)
339
        self.put_controlfile('inventory', sio, encode=False)
1 by mbp at sourcefrog
import from baz patch-364
340
341
342
    def _check_format(self):
343
        """Check this branch format is supported.
344
345
        The current tool only supports the current unstable format.
346
347
        In the future, we might need different in-memory Branch
348
        classes to support downlevel branches.  But not yet.
163 by mbp at sourcefrog
merge win32 portability fixes
349
        """
350
        # This ignores newlines so that we can open branches created
351
        # on Windows from Linux and so on.  I think it might be better
352
        # to always make all internal files in unix format.
245 by mbp at sourcefrog
- control files always in utf-8-unix format
353
        fmt = self.controlfile('branch-format', 'r').read()
163 by mbp at sourcefrog
merge win32 portability fixes
354
        fmt.replace('\r\n', '')
1 by mbp at sourcefrog
import from baz patch-364
355
        if fmt != BZR_BRANCH_FORMAT:
576 by Martin Pool
- raise exceptions rather than using bailout()
356
            raise BzrError('sorry, branch format %r not supported' % fmt,
357
                           ['use a different bzr version',
358
                            'or remove the .bzr directory and "bzr init" again'])
1 by mbp at sourcefrog
import from baz patch-364
359
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
360
        # We know that the format is the currently supported one.
361
        # So create the rest of the entries.
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
362
        from bzrlib.store import CompressedTextStore
363
907.1.23 by John Arbash Meinel
Branch objects now automatically create Cached stores if the protocol is_remote.
364
        if self._transport.is_remote():
365
            import tempfile
366
            self.cache_root = tempfile.mkdtemp()
907.1.24 by John Arbash Meinel
Remote functionality work.
367
            mutter('Branch %r using caching in %r' % (self, self.cache_root))
907.1.23 by John Arbash Meinel
Branch objects now automatically create Cached stores if the protocol is_remote.
368
        else:
369
            self.cache_root = None
370
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
371
        def get_store(name):
372
            relpath = self._rel_controlfilename(name)
907.1.23 by John Arbash Meinel
Branch objects now automatically create Cached stores if the protocol is_remote.
373
            store = CompressedTextStore(self._transport.clone(relpath))
374
            if self._transport.is_remote():
375
                from meta_store import CachedStore
907.1.24 by John Arbash Meinel
Remote functionality work.
376
                cache_path = os.path.join(self.cache_root, name)
907.1.23 by John Arbash Meinel
Branch objects now automatically create Cached stores if the protocol is_remote.
377
                os.mkdir(cache_path)
378
                store = CachedStore(store, cache_path)
379
            return store
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
380
381
        self.text_store = get_store('text-store')
382
        self.revision_store = get_store('revision-store')
383
        self.inventory_store = get_store('inventory-store')
384
1 by mbp at sourcefrog
import from baz patch-364
385
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
386
1 by mbp at sourcefrog
import from baz patch-364
387
    def read_working_inventory(self):
388
        """Read the working inventory."""
800 by Martin Pool
Merge John's import-speedup branch:
389
        from bzrlib.inventory import Inventory
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
390
        from bzrlib.xml import unpack_xml
800 by Martin Pool
Merge John's import-speedup branch:
391
        from time import time
392
        before = time()
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
393
        self.lock_read()
394
        try:
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
395
            # ElementTree does its own conversion from UTF-8, so open in
396
            # binary.
397
            inv = unpack_xml(Inventory,
398
                                  self.controlfile('inventory', 'rb'))
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
399
            mutter("loaded inventory of %d items in %f"
800 by Martin Pool
Merge John's import-speedup branch:
400
                   % (len(inv), time() - before))
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
401
            return inv
402
        finally:
403
            self.unlock()
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
404
            
1 by mbp at sourcefrog
import from baz patch-364
405
406
    def _write_inventory(self, inv):
407
        """Update the working inventory.
408
409
        That is to say, the inventory describing changes underway, that
410
        will be committed to the next revision.
411
        """
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
412
        from bzrlib.xml import pack_xml
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
413
        from cStringIO import StringIO
770 by Martin Pool
- write new working inventory using AtomicFile
414
        self.lock_write()
415
        try:
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
416
            # Transport handles atomicity
417
418
            sio = StringIO()
419
            pack_xml(inv, sio)
420
            sio.seek(0)
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
421
            self._transport.put(self._rel_controlfilename('inventory'), sio)
770 by Martin Pool
- write new working inventory using AtomicFile
422
        finally:
423
            self.unlock()
424
        
14 by mbp at sourcefrog
write inventory to temporary file and atomically replace
425
        mutter('wrote working inventory')
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
426
            
1 by mbp at sourcefrog
import from baz patch-364
427
428
    inventory = property(read_working_inventory, _write_inventory, None,
429
                         """Inventory for the working copy.""")
430
431
493 by Martin Pool
- Merge aaron's merge command
432
    def add(self, files, verbose=False, ids=None):
1 by mbp at sourcefrog
import from baz patch-364
433
        """Make files versioned.
434
247 by mbp at sourcefrog
doc
435
        Note that the command line normally calls smart_add instead.
436
1 by mbp at sourcefrog
import from baz patch-364
437
        This puts the files in the Added state, so that they will be
438
        recorded by the next commit.
439
596 by Martin Pool
doc
440
        files
441
            List of paths to add, relative to the base of the tree.
442
443
        ids
444
            If set, use these instead of automatically generated ids.
445
            Must be the same length as the list of files, but may
446
            contain None for ids that are to be autogenerated.
447
254 by Martin Pool
- Doc cleanups from Magnus Therning
448
        TODO: Perhaps have an option to add the ids even if the files do
596 by Martin Pool
doc
449
              not (yet) exist.
1 by mbp at sourcefrog
import from baz patch-364
450
254 by Martin Pool
- Doc cleanups from Magnus Therning
451
        TODO: Perhaps return the ids of the files?  But then again it
596 by Martin Pool
doc
452
              is easy to retrieve them if they're needed.
1 by mbp at sourcefrog
import from baz patch-364
453
254 by Martin Pool
- Doc cleanups from Magnus Therning
454
        TODO: Adding a directory should optionally recurse down and
596 by Martin Pool
doc
455
              add all non-ignored children.  Perhaps do that in a
456
              higher-level method.
1 by mbp at sourcefrog
import from baz patch-364
457
        """
800 by Martin Pool
Merge John's import-speedup branch:
458
        from bzrlib.textui import show_status
1 by mbp at sourcefrog
import from baz patch-364
459
        # TODO: Re-adding a file that is removed in the working copy
460
        # should probably put it back with the previous ID.
800 by Martin Pool
Merge John's import-speedup branch:
461
        if isinstance(files, basestring):
462
            assert(ids is None or isinstance(ids, basestring))
1 by mbp at sourcefrog
import from baz patch-364
463
            files = [files]
493 by Martin Pool
- Merge aaron's merge command
464
            if ids is not None:
465
                ids = [ids]
466
467
        if ids is None:
468
            ids = [None] * len(files)
469
        else:
470
            assert(len(ids) == len(files))
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
471
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
472
        self.lock_write()
473
        try:
474
            inv = self.read_working_inventory()
475
            for f,file_id in zip(files, ids):
476
                if is_control_file(f):
477
                    raise BzrError("cannot add control file %s" % quotefn(f))
478
479
                fp = splitpath(f)
480
481
                if len(fp) == 0:
482
                    raise BzrError("cannot add top-level %r" % f)
483
484
                fullpath = os.path.normpath(self.abspath(f))
485
486
                try:
487
                    kind = file_kind(fullpath)
488
                except OSError:
489
                    # maybe something better?
490
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
491
492
                if kind != 'file' and kind != 'directory':
493
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
494
495
                if file_id is None:
496
                    file_id = gen_file_id(f)
497
                inv.add_path(f, kind=kind, file_id=file_id)
498
499
                if verbose:
772 by Martin Pool
- fix verbose output from Branch.add
500
                    print 'added', quotefn(f)
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
501
502
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
503
504
            self._write_inventory(inv)
505
        finally:
506
            self.unlock()
70 by mbp at sourcefrog
Prepare for smart recursive add.
507
            
1 by mbp at sourcefrog
import from baz patch-364
508
176 by mbp at sourcefrog
New cat command contributed by janmar.
509
    def print_file(self, file, revno):
510
        """Print `file` to stdout."""
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
511
        self.lock_read()
512
        try:
513
            tree = self.revision_tree(self.lookup_revision(revno))
514
            # use inventory as it was in that revision
515
            file_id = tree.inventory.path2id(file)
516
            if not file_id:
897 by Martin Pool
- merge john's revision-naming code
517
                raise BzrError("%r is not present in revision %s" % (file, revno))
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
518
            tree.print_file(file_id)
519
        finally:
520
            self.unlock()
521
522
1 by mbp at sourcefrog
import from baz patch-364
523
    def remove(self, files, verbose=False):
524
        """Mark nominated files for removal from the inventory.
525
526
        This does not remove their text.  This does not run on 
527
254 by Martin Pool
- Doc cleanups from Magnus Therning
528
        TODO: Refuse to remove modified files unless --force is given?
1 by mbp at sourcefrog
import from baz patch-364
529
254 by Martin Pool
- Doc cleanups from Magnus Therning
530
        TODO: Do something useful with directories.
1 by mbp at sourcefrog
import from baz patch-364
531
254 by Martin Pool
- Doc cleanups from Magnus Therning
532
        TODO: Should this remove the text or not?  Tough call; not
1 by mbp at sourcefrog
import from baz patch-364
533
        removing may be useful and the user can just use use rm, and
534
        is the opposite of add.  Removing it is consistent with most
535
        other tools.  Maybe an option.
536
        """
800 by Martin Pool
Merge John's import-speedup branch:
537
        from bzrlib.textui import show_status
1 by mbp at sourcefrog
import from baz patch-364
538
        ## TODO: Normalize names
539
        ## TODO: Remove nested loops; better scalability
800 by Martin Pool
Merge John's import-speedup branch:
540
        if isinstance(files, basestring):
1 by mbp at sourcefrog
import from baz patch-364
541
            files = [files]
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
542
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
543
        self.lock_write()
544
545
        try:
546
            tree = self.working_tree()
547
            inv = tree.inventory
548
549
            # do this before any modifications
550
            for f in files:
551
                fid = inv.path2id(f)
552
                if not fid:
553
                    raise BzrError("cannot remove unversioned file %s" % quotefn(f))
554
                mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
555
                if verbose:
556
                    # having remove it, it must be either ignored or unknown
557
                    if tree.is_ignored(f):
558
                        new_status = 'I'
559
                    else:
560
                        new_status = '?'
561
                    show_status(new_status, inv[fid].kind, quotefn(f))
562
                del inv[fid]
563
564
            self._write_inventory(inv)
565
        finally:
566
            self.unlock()
567
568
612 by Martin Pool
doc
569
    # FIXME: this doesn't need to be a branch method
493 by Martin Pool
- Merge aaron's merge command
570
    def set_inventory(self, new_inventory_list):
800 by Martin Pool
Merge John's import-speedup branch:
571
        from bzrlib.inventory import Inventory, InventoryEntry
493 by Martin Pool
- Merge aaron's merge command
572
        inv = Inventory()
573
        for path, file_id, parent, kind in new_inventory_list:
574
            name = os.path.basename(path)
575
            if name == "":
576
                continue
577
            inv.add(InventoryEntry(file_id, name, kind, parent))
578
        self._write_inventory(inv)
579
1 by mbp at sourcefrog
import from baz patch-364
580
581
    def unknowns(self):
582
        """Return all unknown files.
583
584
        These are files in the working directory that are not versioned or
585
        control files or ignored.
586
        
587
        >>> b = ScratchBranch(files=['foo', 'foo~'])
588
        >>> list(b.unknowns())
589
        ['foo']
590
        >>> b.add('foo')
591
        >>> list(b.unknowns())
592
        []
593
        >>> b.remove('foo')
594
        >>> list(b.unknowns())
595
        ['foo']
596
        """
597
        return self.working_tree().unknowns()
598
599
905 by Martin Pool
- merge aaron's append_multiple.patch
600
    def append_revision(self, *revision_ids):
601
        for revision_id in revision_ids:
602
            mutter("add {%s} to revision-history" % revision_id)
603
604
        rev_history = self.revision_history()
605
        rev_history.extend(revision_ids)
769 by Martin Pool
- append to branch revision history using AtomicFile
606
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
607
        self.lock_write()
769 by Martin Pool
- append to branch revision history using AtomicFile
608
        try:
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
609
            self._transport.put(self._rel_controlfilename('revision-history'),
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
610
                    '\n'.join(rev_history))
769 by Martin Pool
- append to branch revision history using AtomicFile
611
        finally:
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
612
            self.unlock()
233 by mbp at sourcefrog
- more output from test.sh
613
614
1 by mbp at sourcefrog
import from baz patch-364
615
    def get_revision(self, revision_id):
616
        """Return the Revision object for a named revision"""
800 by Martin Pool
Merge John's import-speedup branch:
617
        from bzrlib.revision import Revision
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
618
        from bzrlib.xml import unpack_xml
619
620
        self.lock_read()
621
        try:
622
            if not revision_id or not isinstance(revision_id, basestring):
623
                raise ValueError('invalid revision-id: %r' % revision_id)
624
            r = unpack_xml(Revision, self.revision_store[revision_id])
625
        finally:
626
            self.unlock()
627
            
1 by mbp at sourcefrog
import from baz patch-364
628
        assert r.revision_id == revision_id
629
        return r
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
630
        
1 by mbp at sourcefrog
import from baz patch-364
631
672 by Martin Pool
- revision records include the hash of their inventory and
632
    def get_revision_sha1(self, revision_id):
633
        """Hash the stored value of a revision, and return it."""
634
        # In the future, revision entries will be signed. At that
635
        # point, it is probably best *not* to include the signature
636
        # in the revision hash. Because that lets you re-sign
637
        # the revision, (add signatures/remove signatures) and still
638
        # have all hash pointers stay consistent.
639
        # But for now, just hash the contents.
640
        return sha_file(self.revision_store[revision_id])
641
1 by mbp at sourcefrog
import from baz patch-364
642
643
    def get_inventory(self, inventory_id):
644
        """Get Inventory object by hash.
645
254 by Martin Pool
- Doc cleanups from Magnus Therning
646
        TODO: Perhaps for this and similar methods, take a revision
1 by mbp at sourcefrog
import from baz patch-364
647
               parameter which can be either an integer revno or a
648
               string hash."""
800 by Martin Pool
Merge John's import-speedup branch:
649
        from bzrlib.inventory import Inventory
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
650
        from bzrlib.xml import unpack_xml
651
652
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
653
            
1 by mbp at sourcefrog
import from baz patch-364
654
672 by Martin Pool
- revision records include the hash of their inventory and
655
    def get_inventory_sha1(self, inventory_id):
656
        """Return the sha1 hash of the inventory entry
657
        """
658
        return sha_file(self.inventory_store[inventory_id])
659
1 by mbp at sourcefrog
import from baz patch-364
660
661
    def get_revision_inventory(self, revision_id):
662
        """Return inventory of a past revision."""
820 by Martin Pool
- faster Branch.get_revision_inventory now we know the ids are the same
663
        # bzr 0.0.6 imposes the constraint that the inventory_id
664
        # must be the same as its revision, so this is trivial.
1 by mbp at sourcefrog
import from baz patch-364
665
        if revision_id == None:
800 by Martin Pool
Merge John's import-speedup branch:
666
            from bzrlib.inventory import Inventory
1 by mbp at sourcefrog
import from baz patch-364
667
            return Inventory()
668
        else:
820 by Martin Pool
- faster Branch.get_revision_inventory now we know the ids are the same
669
            return self.get_inventory(revision_id)
1 by mbp at sourcefrog
import from baz patch-364
670
671
672
    def revision_history(self):
673
        """Return sequence of revision hashes on to this branch.
674
675
        >>> ScratchBranch().revision_history()
676
        []
677
        """
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
678
        self.lock_read()
679
        try:
680
            return [l.rstrip('\r\n') for l in
681
                    self.controlfile('revision-history', 'r').readlines()]
682
        finally:
683
            self.unlock()
1 by mbp at sourcefrog
import from baz patch-364
684
685
622 by Martin Pool
Updated merge patch from Aaron
686
    def common_ancestor(self, other, self_revno=None, other_revno=None):
687
        """
688
        >>> import commit
689
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
690
        >>> sb.common_ancestor(sb) == (None, None)
691
        True
692
        >>> commit.commit(sb, "Committing first revision", verbose=False)
693
        >>> sb.common_ancestor(sb)[0]
694
        1
695
        >>> clone = sb.clone()
696
        >>> commit.commit(sb, "Committing second revision", verbose=False)
697
        >>> sb.common_ancestor(sb)[0]
698
        2
699
        >>> sb.common_ancestor(clone)[0]
700
        1
701
        >>> commit.commit(clone, "Committing divergent second revision", 
702
        ...               verbose=False)
703
        >>> sb.common_ancestor(clone)[0]
704
        1
705
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
706
        True
707
        >>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
708
        True
709
        >>> clone2 = sb.clone()
710
        >>> sb.common_ancestor(clone2)[0]
711
        2
712
        >>> sb.common_ancestor(clone2, self_revno=1)[0]
713
        1
714
        >>> sb.common_ancestor(clone2, other_revno=1)[0]
715
        1
716
        """
717
        my_history = self.revision_history()
718
        other_history = other.revision_history()
719
        if self_revno is None:
720
            self_revno = len(my_history)
721
        if other_revno is None:
722
            other_revno = len(other_history)
723
        indices = range(min((self_revno, other_revno)))
724
        indices.reverse()
725
        for r in indices:
726
            if my_history[r] == other_history[r]:
727
                return r+1, my_history[r]
728
        return None, None
729
385 by Martin Pool
- New Branch.enum_history method
730
    def enum_history(self, direction):
731
        """Return (revno, revision_id) for history of branch.
732
733
        direction
734
            'forward' is from earliest to latest
735
            'reverse' is from latest to earliest
736
        """
737
        rh = self.revision_history()
738
        if direction == 'forward':
739
            i = 1
740
            for rid in rh:
741
                yield i, rid
742
                i += 1
743
        elif direction == 'reverse':
744
            i = len(rh)
745
            while i > 0:
746
                yield i, rh[i-1]
747
                i -= 1
748
        else:
526 by Martin Pool
- use ValueError for bad internal parameters
749
            raise ValueError('invalid history direction', direction)
385 by Martin Pool
- New Branch.enum_history method
750
751
1 by mbp at sourcefrog
import from baz patch-364
752
    def revno(self):
753
        """Return current revision number for this branch.
754
755
        That is equivalent to the number of revisions committed to
756
        this branch.
757
        """
758
        return len(self.revision_history())
759
760
761
    def last_patch(self):
762
        """Return last patch hash, or None if no history.
763
        """
764
        ph = self.revision_history()
765
        if ph:
766
            return ph[-1]
184 by mbp at sourcefrog
pychecker fixups
767
        else:
768
            return None
485 by Martin Pool
- move commit code into its own module
769
770
685 by Martin Pool
- add -r option to the branch command
771
    def missing_revisions(self, other, stop_revision=None):
628 by Martin Pool
- merge aaron's updated merge/pull code
772
        """
773
        If self and other have not diverged, return a list of the revisions
774
        present in other, but missing from self.
775
776
        >>> from bzrlib.commit import commit
777
        >>> bzrlib.trace.silent = True
778
        >>> br1 = ScratchBranch()
779
        >>> br2 = ScratchBranch()
780
        >>> br1.missing_revisions(br2)
781
        []
782
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
783
        >>> br1.missing_revisions(br2)
784
        [u'REVISION-ID-1']
785
        >>> br2.missing_revisions(br1)
786
        []
787
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
788
        >>> br1.missing_revisions(br2)
789
        []
790
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
791
        >>> br1.missing_revisions(br2)
792
        [u'REVISION-ID-2A']
793
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
794
        >>> br1.missing_revisions(br2)
795
        Traceback (most recent call last):
796
        DivergedBranches: These branches have diverged.
797
        """
798
        self_history = self.revision_history()
799
        self_len = len(self_history)
800
        other_history = other.revision_history()
801
        other_len = len(other_history)
802
        common_index = min(self_len, other_len) -1
803
        if common_index >= 0 and \
804
            self_history[common_index] != other_history[common_index]:
805
            raise DivergedBranches(self, other)
685 by Martin Pool
- add -r option to the branch command
806
807
        if stop_revision is None:
808
            stop_revision = other_len
809
        elif stop_revision > other_len:
810
            raise NoSuchRevision(self, stop_revision)
811
        
812
        return other_history[self_len:stop_revision]
813
814
815
    def update_revisions(self, other, stop_revision=None):
663 by Martin Pool
doc
816
        """Pull in all new revisions from other branch.
817
        
628 by Martin Pool
- merge aaron's updated merge/pull code
818
        >>> from bzrlib.commit import commit
819
        >>> bzrlib.trace.silent = True
820
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
821
        >>> br1.add('foo')
822
        >>> br1.add('bar')
823
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
824
        >>> br2 = ScratchBranch()
825
        >>> br2.update_revisions(br1)
826
        Added 2 texts.
827
        Added 1 inventories.
828
        Added 1 revisions.
829
        >>> br2.revision_history()
830
        [u'REVISION-ID-1']
831
        >>> br2.update_revisions(br1)
832
        Added 0 texts.
833
        Added 0 inventories.
834
        Added 0 revisions.
835
        >>> br1.text_store.total_size() == br2.text_store.total_size()
836
        True
837
        """
670 by Martin Pool
- Show progress while branching
838
        from bzrlib.progress import ProgressBar
800 by Martin Pool
Merge John's import-speedup branch:
839
        try:
840
            set
841
        except NameError:
842
            from sets import Set as set
670 by Martin Pool
- Show progress while branching
843
844
        pb = ProgressBar()
845
846
        pb.update('comparing histories')
685 by Martin Pool
- add -r option to the branch command
847
        revision_ids = self.missing_revisions(other, stop_revision)
790 by Martin Pool
Merge from aaron:
848
849
        if hasattr(other.revision_store, "prefetch"):
850
            other.revision_store.prefetch(revision_ids)
851
        if hasattr(other.inventory_store, "prefetch"):
852
            inventory_ids = [other.get_revision(r).inventory_id
853
                             for r in revision_ids]
854
            other.inventory_store.prefetch(inventory_ids)
855
                
670 by Martin Pool
- Show progress while branching
856
        revisions = []
800 by Martin Pool
Merge John's import-speedup branch:
857
        needed_texts = set()
670 by Martin Pool
- Show progress while branching
858
        i = 0
859
        for rev_id in revision_ids:
860
            i += 1
861
            pb.update('fetching revision', i, len(revision_ids))
862
            rev = other.get_revision(rev_id)
863
            revisions.append(rev)
628 by Martin Pool
- merge aaron's updated merge/pull code
864
            inv = other.get_inventory(str(rev.inventory_id))
865
            for key, entry in inv.iter_entries():
866
                if entry.text_id is None:
867
                    continue
868
                if entry.text_id not in self.text_store:
869
                    needed_texts.add(entry.text_id)
670 by Martin Pool
- Show progress while branching
870
871
        pb.clear()
872
                    
628 by Martin Pool
- merge aaron's updated merge/pull code
873
        count = self.text_store.copy_multi(other.text_store, needed_texts)
874
        print "Added %d texts." % count 
875
        inventory_ids = [ f.inventory_id for f in revisions ]
876
        count = self.inventory_store.copy_multi(other.inventory_store, 
877
                                                inventory_ids)
878
        print "Added %d inventories." % count 
879
        revision_ids = [ f.revision_id for f in revisions]
880
        count = self.revision_store.copy_multi(other.revision_store, 
881
                                               revision_ids)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
882
        self.append_revision(*revision_ids)
628 by Martin Pool
- merge aaron's updated merge/pull code
883
        print "Added %d revisions." % count
884
                    
885
        
485 by Martin Pool
- move commit code into its own module
886
    def commit(self, *args, **kw):
887
        from bzrlib.commit import commit
888
        commit(self, *args, **kw)
184 by mbp at sourcefrog
pychecker fixups
889
        
1 by mbp at sourcefrog
import from baz patch-364
890
897 by Martin Pool
- merge john's revision-naming code
891
    def lookup_revision(self, revision):
892
        """Return the revision identifier for a given revision information."""
893
        revno, info = self.get_revision_info(revision)
894
        return info
895
896
    def get_revision_info(self, revision):
897
        """Return (revno, revision id) for revision identifier.
898
899
        revision can be an integer, in which case it is assumed to be revno (though
900
            this will translate negative values into positive ones)
901
        revision can also be a string, in which case it is parsed for something like
902
            'date:' or 'revid:' etc.
903
        """
904
        if revision is None:
905
            return 0, None
906
        revno = None
907
        try:# Convert to int if possible
908
            revision = int(revision)
909
        except ValueError:
910
            pass
911
        revs = self.revision_history()
912
        if isinstance(revision, int):
913
            if revision == 0:
914
                return 0, None
915
            # Mabye we should do this first, but we don't need it if revision == 0
916
            if revision < 0:
917
                revno = len(revs) + revision + 1
918
            else:
919
                revno = revision
920
        elif isinstance(revision, basestring):
921
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
922
                if revision.startswith(prefix):
923
                    revno = func(self, revs, revision)
924
                    break
925
            else:
926
                raise BzrError('No namespace registered for string: %r' % revision)
927
928
        if revno is None or revno <= 0 or revno > len(revs):
929
            raise BzrError("no such revision %s" % revision)
930
        return revno, revs[revno-1]
931
932
    def _namespace_revno(self, revs, revision):
933
        """Lookup a revision by revision number"""
934
        assert revision.startswith('revno:')
935
        try:
936
            return int(revision[6:])
937
        except ValueError:
938
            return None
939
    REVISION_NAMESPACES['revno:'] = _namespace_revno
940
941
    def _namespace_revid(self, revs, revision):
942
        assert revision.startswith('revid:')
943
        try:
944
            return revs.index(revision[6:]) + 1
945
        except ValueError:
946
            return None
947
    REVISION_NAMESPACES['revid:'] = _namespace_revid
948
949
    def _namespace_last(self, revs, revision):
950
        assert revision.startswith('last:')
951
        try:
952
            offset = int(revision[5:])
953
        except ValueError:
954
            return None
955
        else:
956
            if offset <= 0:
957
                raise BzrError('You must supply a positive value for --revision last:XXX')
958
            return len(revs) - offset + 1
959
    REVISION_NAMESPACES['last:'] = _namespace_last
960
961
    def _namespace_tag(self, revs, revision):
962
        assert revision.startswith('tag:')
963
        raise BzrError('tag: namespace registered, but not implemented.')
964
    REVISION_NAMESPACES['tag:'] = _namespace_tag
965
966
    def _namespace_date(self, revs, revision):
967
        assert revision.startswith('date:')
968
        import datetime
969
        # Spec for date revisions:
970
        #   date:value
971
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
972
        #   it can also start with a '+/-/='. '+' says match the first
973
        #   entry after the given date. '-' is match the first entry before the date
974
        #   '=' is match the first entry after, but still on the given date.
975
        #
976
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
977
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
978
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
979
        #       May 13th, 2005 at 0:00
980
        #
981
        #   So the proper way of saying 'give me all entries for today' is:
982
        #       -r {date:+today}:{date:-tomorrow}
983
        #   The default is '=' when not supplied
984
        val = revision[5:]
985
        match_style = '='
986
        if val[:1] in ('+', '-', '='):
987
            match_style = val[:1]
988
            val = val[1:]
989
990
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
991
        if val.lower() == 'yesterday':
992
            dt = today - datetime.timedelta(days=1)
993
        elif val.lower() == 'today':
994
            dt = today
995
        elif val.lower() == 'tomorrow':
996
            dt = today + datetime.timedelta(days=1)
997
        else:
901 by Martin Pool
- fix missing import
998
            import re
897 by Martin Pool
- merge john's revision-naming code
999
            # This should be done outside the function to avoid recompiling it.
1000
            _date_re = re.compile(
1001
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1002
                    r'(,|T)?\s*'
1003
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1004
                )
1005
            m = _date_re.match(val)
1006
            if not m or (not m.group('date') and not m.group('time')):
1007
                raise BzrError('Invalid revision date %r' % revision)
1008
1009
            if m.group('date'):
1010
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1011
            else:
1012
                year, month, day = today.year, today.month, today.day
1013
            if m.group('time'):
1014
                hour = int(m.group('hour'))
1015
                minute = int(m.group('minute'))
1016
                if m.group('second'):
1017
                    second = int(m.group('second'))
1018
                else:
1019
                    second = 0
1020
            else:
1021
                hour, minute, second = 0,0,0
1022
1023
            dt = datetime.datetime(year=year, month=month, day=day,
1024
                    hour=hour, minute=minute, second=second)
1025
        first = dt
1026
        last = None
1027
        reversed = False
1028
        if match_style == '-':
1029
            reversed = True
1030
        elif match_style == '=':
1031
            last = dt + datetime.timedelta(days=1)
1032
1033
        if reversed:
1034
            for i in range(len(revs)-1, -1, -1):
1035
                r = self.get_revision(revs[i])
1036
                # TODO: Handle timezone.
1037
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1038
                if first >= dt and (last is None or dt >= last):
1039
                    return i+1
1040
        else:
1041
            for i in range(len(revs)):
1042
                r = self.get_revision(revs[i])
1043
                # TODO: Handle timezone.
1044
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1045
                if first <= dt and (last is None or dt <= last):
1046
                    return i+1
1047
    REVISION_NAMESPACES['date:'] = _namespace_date
1 by mbp at sourcefrog
import from baz patch-364
1048
1049
    def revision_tree(self, revision_id):
1050
        """Return Tree for a revision on this branch.
1051
1052
        `revision_id` may be None for the null revision, in which case
1053
        an `EmptyTree` is returned."""
800 by Martin Pool
Merge John's import-speedup branch:
1054
        from bzrlib.tree import EmptyTree, RevisionTree
529 by Martin Pool
todo
1055
        # TODO: refactor this to use an existing revision object
1056
        # so we don't need to read it in twice.
1 by mbp at sourcefrog
import from baz patch-364
1057
        if revision_id == None:
1058
            return EmptyTree()
1059
        else:
1060
            inv = self.get_revision_inventory(revision_id)
1061
            return RevisionTree(self.text_store, inv)
1062
1063
1064
    def working_tree(self):
1065
        """Return a `Tree` for the working copy."""
453 by Martin Pool
- Split WorkingTree into its own file
1066
        from workingtree import WorkingTree
907.1.5 by John Arbash Meinel
Some more work, including ScratchBranch changes.
1067
        # TODO: In the future, WorkingTree should utilize Transport
1068
        return WorkingTree(self._transport.base, self.read_working_inventory())
1 by mbp at sourcefrog
import from baz patch-364
1069
1070
1071
    def basis_tree(self):
1072
        """Return `Tree` object for last revision.
1073
1074
        If there are no revisions yet, return an `EmptyTree`.
1075
        """
800 by Martin Pool
Merge John's import-speedup branch:
1076
        from bzrlib.tree import EmptyTree, RevisionTree
1 by mbp at sourcefrog
import from baz patch-364
1077
        r = self.last_patch()
1078
        if r == None:
1079
            return EmptyTree()
1080
        else:
1081
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
1082
1083
1084
168 by mbp at sourcefrog
new "rename" command
1085
    def rename_one(self, from_rel, to_rel):
309 by Martin Pool
doc
1086
        """Rename one file.
1087
1088
        This can change the directory or the filename or both.
353 by Martin Pool
- Per-branch locks in read and write modes.
1089
        """
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
1090
        self.lock_write()
171 by mbp at sourcefrog
better error message when working file rename fails
1091
        try:
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
1092
            tree = self.working_tree()
1093
            inv = tree.inventory
1094
            if not tree.has_filename(from_rel):
1095
                raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1096
            if tree.has_filename(to_rel):
1097
                raise BzrError("can't rename: new working file %r already exists" % to_rel)
1098
1099
            file_id = inv.path2id(from_rel)
1100
            if file_id == None:
1101
                raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1102
1103
            if inv.path2id(to_rel):
1104
                raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1105
1106
            to_dir, to_tail = os.path.split(to_rel)
1107
            to_dir_id = inv.path2id(to_dir)
1108
            if to_dir_id == None and to_dir != '':
1109
                raise BzrError("can't determine destination directory id for %r" % to_dir)
1110
1111
            mutter("rename_one:")
1112
            mutter("  file_id    {%s}" % file_id)
1113
            mutter("  from_rel   %r" % from_rel)
1114
            mutter("  to_rel     %r" % to_rel)
1115
            mutter("  to_dir     %r" % to_dir)
1116
            mutter("  to_dir_id  {%s}" % to_dir_id)
1117
1118
            inv.rename(file_id, to_dir_id, to_tail)
1119
1120
            print "%s => %s" % (from_rel, to_rel)
1121
1122
            from_abs = self.abspath(from_rel)
1123
            to_abs = self.abspath(to_rel)
1124
            try:
1125
                os.rename(from_abs, to_abs)
1126
            except OSError, e:
1127
                raise BzrError("failed to rename %r to %r: %s"
1128
                        % (from_abs, to_abs, e[1]),
1129
                        ["rename rolled back"])
1130
1131
            self._write_inventory(inv)
1132
        finally:
1133
            self.unlock()
1134
1135
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
1136
    def move(self, from_paths, to_name):
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
1137
        """Rename files.
1138
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
1139
        to_name must exist as a versioned directory.
1140
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
1141
        If to_name exists and is a directory, the files are moved into
1142
        it, keeping their old names.  If it is a directory, 
1143
1144
        Note that to_name is only the last component of the new name;
1145
        this doesn't change the directory.
1146
        """
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
1147
        self.lock_write()
1148
        try:
1149
            ## TODO: Option to move IDs only
1150
            assert not isinstance(from_paths, basestring)
1151
            tree = self.working_tree()
1152
            inv = tree.inventory
1153
            to_abs = self.abspath(to_name)
1154
            if not isdir(to_abs):
1155
                raise BzrError("destination %r is not a directory" % to_abs)
1156
            if not tree.has_filename(to_name):
1157
                raise BzrError("destination %r not in working directory" % to_abs)
1158
            to_dir_id = inv.path2id(to_name)
1159
            if to_dir_id == None and to_name != '':
1160
                raise BzrError("destination %r is not a versioned directory" % to_name)
1161
            to_dir_ie = inv[to_dir_id]
1162
            if to_dir_ie.kind not in ('directory', 'root_directory'):
1163
                raise BzrError("destination %r is not a directory" % to_abs)
1164
1165
            to_idpath = inv.get_idpath(to_dir_id)
1166
1167
            for f in from_paths:
1168
                if not tree.has_filename(f):
1169
                    raise BzrError("%r does not exist in working tree" % f)
1170
                f_id = inv.path2id(f)
1171
                if f_id == None:
1172
                    raise BzrError("%r is not versioned" % f)
1173
                name_tail = splitpath(f)[-1]
1174
                dest_path = appendpath(to_name, name_tail)
1175
                if tree.has_filename(dest_path):
1176
                    raise BzrError("destination %r already exists" % dest_path)
1177
                if f_id in to_idpath:
1178
                    raise BzrError("can't move %r to a subdirectory of itself" % f)
1179
1180
            # OK, so there's a race here, it's possible that someone will
1181
            # create a file in this interval and then the rename might be
1182
            # left half-done.  But we should have caught most problems.
1183
1184
            for f in from_paths:
1185
                name_tail = splitpath(f)[-1]
1186
                dest_path = appendpath(to_name, name_tail)
1187
                print "%s => %s" % (f, dest_path)
1188
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1189
                try:
1190
                    os.rename(self.abspath(f), self.abspath(dest_path))
1191
                except OSError, e:
1192
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1193
                            ["rename rolled back"])
1194
1195
            self._write_inventory(inv)
1196
        finally:
1197
            self.unlock()
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
1198
1199
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
1200
    def revert(self, filenames, old_tree=None, backups=True):
778 by Martin Pool
- simple revert of text files
1201
        """Restore selected files to the versions from a previous tree.
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
1202
1203
        backups
1204
            If true (default) backups are made of files before
1205
            they're renamed.
778 by Martin Pool
- simple revert of text files
1206
        """
1207
        from bzrlib.errors import NotVersionedError, BzrError
1208
        from bzrlib.atomicfile import AtomicFile
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
1209
        from bzrlib.osutils import backup_file
778 by Martin Pool
- simple revert of text files
1210
        
1211
        inv = self.read_working_inventory()
1212
        if old_tree is None:
1213
            old_tree = self.basis_tree()
1214
        old_inv = old_tree.inventory
1215
1216
        nids = []
1217
        for fn in filenames:
1218
            file_id = inv.path2id(fn)
1219
            if not file_id:
1220
                raise NotVersionedError("not a versioned file", fn)
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
1221
            if not old_inv.has_id(file_id):
1222
                raise BzrError("file not present in old tree", fn, file_id)
778 by Martin Pool
- simple revert of text files
1223
            nids.append((fn, file_id))
1224
            
1225
        # TODO: Rename back if it was previously at a different location
1226
1227
        # TODO: If given a directory, restore the entire contents from
1228
        # the previous version.
1229
1230
        # TODO: Make a backup to a temporary file.
1231
1232
        # TODO: If the file previously didn't exist, delete it?
1233
        for fn, file_id in nids:
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
1234
            backup_file(fn)
1235
            
778 by Martin Pool
- simple revert of text files
1236
            f = AtomicFile(fn, 'wb')
1237
            try:
1238
                f.write(old_tree.get_file(file_id).read())
1239
                f.commit()
1240
            finally:
1241
                f.close()
1242
1243
815 by Martin Pool
- track pending-merges
1244
    def pending_merges(self):
1245
        """Return a list of pending merges.
1246
1247
        These are revisions that have been merged into the working
1248
        directory but not yet committed.
1249
        """
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
1250
        cfn = self._rel_controlfilename('pending-merges')
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
1251
        if not self._transport.has(cfn):
815 by Martin Pool
- track pending-merges
1252
            return []
1253
        p = []
1254
        for l in self.controlfile('pending-merges', 'r').readlines():
1255
            p.append(l.rstrip('\n'))
1256
        return p
1257
1258
907.1.4 by John Arbash Meinel
Add pending_merge can take multiple entries.
1259
    def add_pending_merge(self, *revision_ids):
815 by Martin Pool
- track pending-merges
1260
        from bzrlib.revision import validate_revision_id
1261
907.1.4 by John Arbash Meinel
Add pending_merge can take multiple entries.
1262
        for rev_id in revision_ids:
1263
            validate_revision_id(rev_id)
815 by Martin Pool
- track pending-merges
1264
1265
        p = self.pending_merges()
907.1.4 by John Arbash Meinel
Add pending_merge can take multiple entries.
1266
        updated = False
1267
        for rev_id in revision_ids:
1268
            if rev_id in p:
1269
                continue
1270
            p.append(rev_id)
1271
            updated = True
1272
        if updated:
1273
            self.set_pending_merges(p)
815 by Martin Pool
- track pending-merges
1274
1275
    def set_pending_merges(self, rev_list):
1276
        self.lock_write()
1277
        try:
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
1278
            self._transport.put(self._rel_controlfilename('pending-merges'),
907.1.3 by John Arbash Meinel
Updated bzr set-pending-merges to use transport.
1279
                    '\n'.join(rev_list))
815 by Martin Pool
- track pending-merges
1280
        finally:
1281
            self.unlock()
1282
1283
1 by mbp at sourcefrog
import from baz patch-364
1284
1285
class ScratchBranch(Branch):
1286
    """Special test class: a branch that cleans up after itself.
1287
1288
    >>> b = ScratchBranch()
1289
    >>> isdir(b.base)
1290
    True
1291
    >>> bd = b.base
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1292
    >>> b.destroy()
1 by mbp at sourcefrog
import from baz patch-364
1293
    >>> isdir(bd)
1294
    False
1295
    """
622 by Martin Pool
Updated merge patch from Aaron
1296
    def __init__(self, files=[], dirs=[], base=None):
1 by mbp at sourcefrog
import from baz patch-364
1297
        """Make a test branch.
1298
1299
        This creates a temporary directory and runs init-tree in it.
1300
1301
        If any files are listed, they are created in the working copy.
1302
        """
800 by Martin Pool
Merge John's import-speedup branch:
1303
        from tempfile import mkdtemp
622 by Martin Pool
Updated merge patch from Aaron
1304
        init = False
1305
        if base is None:
800 by Martin Pool
Merge John's import-speedup branch:
1306
            base = mkdtemp()
622 by Martin Pool
Updated merge patch from Aaron
1307
            init = True
1308
        Branch.__init__(self, base, init=init)
100 by mbp at sourcefrog
- add test case for ignore files
1309
        for d in dirs:
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
1310
            self._transport.mkdir(d)
100 by mbp at sourcefrog
- add test case for ignore files
1311
            
1 by mbp at sourcefrog
import from baz patch-364
1312
        for f in files:
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
1313
            self._transport.put(f, 'content of %s' % f)
1 by mbp at sourcefrog
import from baz patch-364
1314
1315
622 by Martin Pool
Updated merge patch from Aaron
1316
    def clone(self):
1317
        """
1318
        >>> orig = ScratchBranch(files=["file1", "file2"])
1319
        >>> clone = orig.clone()
1320
        >>> os.path.samefile(orig.base, clone.base)
1321
        False
1322
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
1323
        True
1324
        """
800 by Martin Pool
Merge John's import-speedup branch:
1325
        from shutil import copytree
1326
        from tempfile import mkdtemp
1327
        base = mkdtemp()
622 by Martin Pool
Updated merge patch from Aaron
1328
        os.rmdir(base)
800 by Martin Pool
Merge John's import-speedup branch:
1329
        copytree(self.base, base, symlinks=True)
622 by Martin Pool
Updated merge patch from Aaron
1330
        return ScratchBranch(base=base)
1331
        
1 by mbp at sourcefrog
import from baz patch-364
1332
    def __del__(self):
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1333
        self.destroy()
1334
1335
    def destroy(self):
1 by mbp at sourcefrog
import from baz patch-364
1336
        """Destroy the test branch, removing the scratch directory."""
800 by Martin Pool
Merge John's import-speedup branch:
1337
        from shutil import rmtree
163 by mbp at sourcefrog
merge win32 portability fixes
1338
        try:
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
1339
            if self.base:
1340
                mutter("delete ScratchBranch %s" % self.base)
800 by Martin Pool
Merge John's import-speedup branch:
1341
                rmtree(self.base)
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1342
        except OSError, e:
163 by mbp at sourcefrog
merge win32 portability fixes
1343
            # Work around for shutil.rmtree failing on Windows when
1344
            # readonly files are encountered
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1345
            mutter("hit exception in destroying ScratchBranch: %s" % e)
163 by mbp at sourcefrog
merge win32 portability fixes
1346
            for root, dirs, files in os.walk(self.base, topdown=False):
1347
                for name in files:
1348
                    os.chmod(os.path.join(root, name), 0700)
800 by Martin Pool
Merge John's import-speedup branch:
1349
            rmtree(self.base)
907.1.19 by John Arbash Meinel
Updated ScratchBranch and Branch.base, All Tests PASS !!!
1350
        self._transport = None
1 by mbp at sourcefrog
import from baz patch-364
1351
1352
    
1353
1354
######################################################################
1355
# predicates
1356
1357
1358
def is_control_file(filename):
1359
    ## FIXME: better check
1360
    filename = os.path.normpath(filename)
1361
    while filename != '':
1362
        head, tail = os.path.split(filename)
1363
        ## mutter('check %r for control file' % ((head, tail), ))
1364
        if tail == bzrlib.BZRDIR:
1365
            return True
70 by mbp at sourcefrog
Prepare for smart recursive add.
1366
        if filename == head:
1367
            break
1 by mbp at sourcefrog
import from baz patch-364
1368
        filename = head
1369
    return False
1370
1371
1372
70 by mbp at sourcefrog
Prepare for smart recursive add.
1373
def gen_file_id(name):
1 by mbp at sourcefrog
import from baz patch-364
1374
    """Return new file id.
1375
1376
    This should probably generate proper UUIDs, but for the moment we
1377
    cope with just randomness because running uuidgen every time is
1378
    slow."""
535 by Martin Pool
- try to eliminate wierd characters from file names when they're
1379
    import re
800 by Martin Pool
Merge John's import-speedup branch:
1380
    from binascii import hexlify
1381
    from time import time
535 by Martin Pool
- try to eliminate wierd characters from file names when they're
1382
1383
    # get last component
70 by mbp at sourcefrog
Prepare for smart recursive add.
1384
    idx = name.rfind('/')
1385
    if idx != -1:
1386
        name = name[idx+1 : ]
262 by Martin Pool
- gen_file_id: break the file on either / or \ when looking
1387
    idx = name.rfind('\\')
1388
    if idx != -1:
1389
        name = name[idx+1 : ]
70 by mbp at sourcefrog
Prepare for smart recursive add.
1390
535 by Martin Pool
- try to eliminate wierd characters from file names when they're
1391
    # make it not a hidden file
70 by mbp at sourcefrog
Prepare for smart recursive add.
1392
    name = name.lstrip('.')
1393
535 by Martin Pool
- try to eliminate wierd characters from file names when they're
1394
    # remove any wierd characters; we don't escape them but rather
1395
    # just pull them out
1396
    name = re.sub(r'[^\w.]', '', name)
1397
190 by mbp at sourcefrog
64 bits of randomness in file/revision ids
1398
    s = hexlify(rand_bytes(8))
800 by Martin Pool
Merge John's import-speedup branch:
1399
    return '-'.join((name, compact_date(time()), s))