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