/brz/remove-bazaar

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