~bzr-pqm/bzr/bzr.dev

70 by mbp at sourcefrog
Prepare for smart recursive add.
1
# Copyright (C) 2005 Canonical Ltd
2
1 by mbp at sourcefrog
import from baz patch-364
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
18
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
19
import traceback, socket, fnmatch, difflib, time
20
from binascii import hexlify
21
22
import bzrlib
23
from inventory import Inventory
24
from trace import mutter, note
453 by Martin Pool
- Split WorkingTree into its own file
25
from tree import Tree, EmptyTree, RevisionTree
1 by mbp at sourcefrog
import from baz patch-364
26
from inventory import InventoryEntry, Inventory
319 by Martin Pool
- remove trivial chomp() function
27
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
1 by mbp at sourcefrog
import from baz patch-364
28
     format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
672 by Martin Pool
- revision records include the hash of their inventory and
29
     joinpath, sha_file, sha_string, file_kind, local_time_offset, appendpath
1 by mbp at sourcefrog
import from baz patch-364
30
from store import ImmutableStore
31
from revision import Revision
576 by Martin Pool
- raise exceptions rather than using bailout()
32
from errors import BzrError
1 by mbp at sourcefrog
import from baz patch-364
33
from textui import show_status
34
35
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
36
## TODO: Maybe include checks for common corruption of newlines, etc?
37
38
39
416 by Martin Pool
- bzr log and bzr root now accept an http URL
40
def find_branch(f, **args):
455 by Martin Pool
- fix 'bzr root'
41
    if f and (f.startswith('http://') or f.startswith('https://')):
416 by Martin Pool
- bzr log and bzr root now accept an http URL
42
        import remotebranch 
43
        return remotebranch.RemoteBranch(f, **args)
44
    else:
45
        return Branch(f, **args)
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
46
47
790 by Martin Pool
Merge from aaron:
48
def find_cached_branch(f, cache_root, **args):
49
    from remotebranch import RemoteBranch
50
    br = find_branch(f, **args)
51
    def cacheify(br, store_name):
52
        from meta_store import CachedStore
53
        cache_path = os.path.join(cache_root, store_name)
54
        os.mkdir(cache_path)
55
        new_store = CachedStore(getattr(br, store_name), cache_path)
56
        setattr(br, store_name, new_store)
57
58
    if isinstance(br, RemoteBranch):
59
        cacheify(br, 'inventory_store')
60
        cacheify(br, 'text_store')
61
        cacheify(br, 'revision_store')
62
    return br
63
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
64
600 by Martin Pool
- Better Branch.relpath that doesn't match on
65
def _relpath(base, path):
66
    """Return path relative to base, or raise exception.
67
68
    The path may be either an absolute path or a path relative to the
69
    current working directory.
70
71
    Lifted out of Branch.relpath for ease of testing.
72
73
    os.path.commonprefix (python2.4) has a bad bug that it works just
74
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
75
    avoids that problem."""
76
    rp = os.path.abspath(path)
77
78
    s = []
79
    head = rp
80
    while len(head) >= len(base):
81
        if head == base:
82
            break
83
        head, tail = os.path.split(head)
84
        if tail:
85
            s.insert(0, tail)
86
    else:
87
        from errors import NotBranchError
88
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
89
90
    return os.sep.join(s)
416 by Martin Pool
- bzr log and bzr root now accept an http URL
91
        
92
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
93
def find_branch_root(f=None):
94
    """Find the branch root enclosing f, or pwd.
95
416 by Martin Pool
- bzr log and bzr root now accept an http URL
96
    f may be a filename or a URL.
97
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
98
    It is not necessary that f exists.
99
100
    Basically we keep looking up until we find the control directory or
101
    run into the root."""
184 by mbp at sourcefrog
pychecker fixups
102
    if f == None:
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
103
        f = os.getcwd()
104
    elif hasattr(os.path, 'realpath'):
105
        f = os.path.realpath(f)
106
    else:
107
        f = os.path.abspath(f)
425 by Martin Pool
- check from aaron for existence of a branch
108
    if not os.path.exists(f):
109
        raise BzrError('%r does not exist' % f)
110
        
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
111
112
    orig_f = f
113
114
    while True:
115
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
116
            return f
117
        head, tail = os.path.split(f)
118
        if head == f:
119
            # reached the root, whatever that may be
184 by mbp at sourcefrog
pychecker fixups
120
            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
121
        f = head
122
    
628 by Martin Pool
- merge aaron's updated merge/pull code
123
class DivergedBranches(Exception):
124
    def __init__(self, branch1, branch2):
125
        self.branch1 = branch1
126
        self.branch2 = branch2
127
        Exception.__init__(self, "These branches have diverged.")
1 by mbp at sourcefrog
import from baz patch-364
128
685 by Martin Pool
- add -r option to the branch command
129
130
class NoSuchRevision(BzrError):
131
    def __init__(self, branch, revision):
132
        self.branch = branch
133
        self.revision = revision
134
        msg = "Branch %s has no revision %d" % (branch, revision)
135
        BzrError.__init__(self, msg)
136
137
1 by mbp at sourcefrog
import from baz patch-364
138
######################################################################
139
# branch objects
140
558 by Martin Pool
- All top-level classes inherit from object
141
class Branch(object):
1 by mbp at sourcefrog
import from baz patch-364
142
    """Branch holding a history of revisions.
143
343 by Martin Pool
doc
144
    base
145
        Base directory of the branch.
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
146
147
    _lock_mode
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
148
        None, or 'r' or 'w'
149
150
    _lock_count
151
        If _lock_mode is true, a positive count of the number of times the
152
        lock has been taken.
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
153
614 by Martin Pool
- unify two defintions of LockError
154
    _lock
155
        Lock object from bzrlib.lock.
1 by mbp at sourcefrog
import from baz patch-364
156
    """
564 by Martin Pool
- Set Branch.base in class def to avoid it being undefined
157
    base = None
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
158
    _lock_mode = None
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
159
    _lock_count = None
615 by Martin Pool
Major rework of locking code:
160
    _lock = None
353 by Martin Pool
- Per-branch locks in read and write modes.
161
    
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
162
    def __init__(self, base, init=False, find_root=True):
1 by mbp at sourcefrog
import from baz patch-364
163
        """Create new branch object at a particular location.
164
254 by Martin Pool
- Doc cleanups from Magnus Therning
165
        base -- Base directory for the branch.
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
166
        
254 by Martin Pool
- Doc cleanups from Magnus Therning
167
        init -- If True, create new control files in a previously
1 by mbp at sourcefrog
import from baz patch-364
168
             unversioned directory.  If False, the branch must already
169
             be versioned.
170
254 by Martin Pool
- Doc cleanups from Magnus Therning
171
        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
172
             existing branch containing base.
173
1 by mbp at sourcefrog
import from baz patch-364
174
        In the test suite, creation of new trees is tested using the
175
        `ScratchBranch` class.
176
        """
177
        if init:
64 by mbp at sourcefrog
- fix up init command for new find-branch-root function
178
            self.base = os.path.realpath(base)
1 by mbp at sourcefrog
import from baz patch-364
179
            self._make_control()
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
180
        elif find_root:
181
            self.base = find_branch_root(base)
1 by mbp at sourcefrog
import from baz patch-364
182
        else:
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
183
            self.base = os.path.realpath(base)
1 by mbp at sourcefrog
import from baz patch-364
184
            if not isdir(self.controlfilename('.')):
576 by Martin Pool
- raise exceptions rather than using bailout()
185
                from errors import NotBranchError
186
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
187
                                     ['use "bzr init" to initialize a new working tree',
188
                                      'current bzr can only operate from top-of-tree'])
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
189
        self._check_format()
1 by mbp at sourcefrog
import from baz patch-364
190
191
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
192
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
193
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
194
195
196
    def __str__(self):
197
        return '%s(%r)' % (self.__class__.__name__, self.base)
198
199
200
    __repr__ = __str__
201
202
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
203
    def __del__(self):
615 by Martin Pool
Major rework of locking code:
204
        if self._lock_mode or self._lock:
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
205
            from warnings import warn
206
            warn("branch %r was not explicitly unlocked" % self)
615 by Martin Pool
Major rework of locking code:
207
            self._lock.unlock()
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
208
209
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
210
211
    def lock_write(self):
212
        if self._lock_mode:
213
            if self._lock_mode != 'w':
214
                from errors import LockError
215
                raise LockError("can't upgrade to a write lock from %r" %
216
                                self._lock_mode)
217
            self._lock_count += 1
218
        else:
615 by Martin Pool
Major rework of locking code:
219
            from bzrlib.lock import WriteLock
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
220
615 by Martin Pool
Major rework of locking code:
221
            self._lock = WriteLock(self.controlfilename('branch-lock'))
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
222
            self._lock_mode = 'w'
223
            self._lock_count = 1
224
225
226
227
    def lock_read(self):
228
        if self._lock_mode:
229
            assert self._lock_mode in ('r', 'w'), \
230
                   "invalid lock mode %r" % self._lock_mode
231
            self._lock_count += 1
232
        else:
615 by Martin Pool
Major rework of locking code:
233
            from bzrlib.lock import ReadLock
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
234
615 by Martin Pool
Major rework of locking code:
235
            self._lock = ReadLock(self.controlfilename('branch-lock'))
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
236
            self._lock_mode = 'r'
237
            self._lock_count = 1
238
                        
239
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
240
            
578 by Martin Pool
- start to move toward Branch.lock and unlock methods,
241
    def unlock(self):
242
        if not self._lock_mode:
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
243
            from errors import LockError
244
            raise LockError('branch %r is not locked' % (self))
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
245
246
        if self._lock_count > 1:
247
            self._lock_count -= 1
248
        else:
615 by Martin Pool
Major rework of locking code:
249
            self._lock.unlock()
250
            self._lock = None
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
251
            self._lock_mode = self._lock_count = None
353 by Martin Pool
- Per-branch locks in read and write modes.
252
253
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
254
    def abspath(self, name):
255
        """Return absolute filename for something in the branch"""
1 by mbp at sourcefrog
import from baz patch-364
256
        return os.path.join(self.base, name)
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
257
1 by mbp at sourcefrog
import from baz patch-364
258
68 by mbp at sourcefrog
- new relpath command and function
259
    def relpath(self, path):
260
        """Return path relative to this branch of something inside it.
261
262
        Raises an error if path is not in this branch."""
600 by Martin Pool
- Better Branch.relpath that doesn't match on
263
        return _relpath(self.base, path)
68 by mbp at sourcefrog
- new relpath command and function
264
265
1 by mbp at sourcefrog
import from baz patch-364
266
    def controlfilename(self, file_or_path):
267
        """Return location relative to branch."""
268
        if isinstance(file_or_path, types.StringTypes):
269
            file_or_path = [file_or_path]
270
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
271
272
273
    def controlfile(self, file_or_path, mode='r'):
245 by mbp at sourcefrog
- control files always in utf-8-unix format
274
        """Open a control file for this branch.
275
276
        There are two classes of file in the control directory: text
277
        and binary.  binary files are untranslated byte streams.  Text
278
        control files are stored with Unix newlines and in UTF-8, even
279
        if the platform or locale defaults are different.
430 by Martin Pool
doc
280
281
        Controlfiles should almost never be opened in write mode but
282
        rather should be atomically copied and replaced using atomicfile.
245 by mbp at sourcefrog
- control files always in utf-8-unix format
283
        """
284
285
        fn = self.controlfilename(file_or_path)
286
287
        if mode == 'rb' or mode == 'wb':
288
            return file(fn, mode)
289
        elif mode == 'r' or mode == 'w':
259 by Martin Pool
- use larger file buffers when opening branch control file
290
            # open in binary mode anyhow so there's no newline translation;
291
            # codecs uses line buffering by default; don't want that.
245 by mbp at sourcefrog
- control files always in utf-8-unix format
292
            import codecs
259 by Martin Pool
- use larger file buffers when opening branch control file
293
            return codecs.open(fn, mode + 'b', 'utf-8',
294
                               buffering=60000)
245 by mbp at sourcefrog
- control files always in utf-8-unix format
295
        else:
296
            raise BzrError("invalid controlfile mode %r" % mode)
297
1 by mbp at sourcefrog
import from baz patch-364
298
299
300
    def _make_control(self):
301
        os.mkdir(self.controlfilename([]))
302
        self.controlfile('README', 'w').write(
303
            "This is a Bazaar-NG control directory.\n"
679 by Martin Pool
- put trailing newline on newly-created .bzr/README
304
            "Do not change any files in this directory.\n")
245 by mbp at sourcefrog
- control files always in utf-8-unix format
305
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
1 by mbp at sourcefrog
import from baz patch-364
306
        for d in ('text-store', 'inventory-store', 'revision-store'):
307
            os.mkdir(self.controlfilename(d))
308
        for f in ('revision-history', 'merged-patches',
353 by Martin Pool
- Per-branch locks in read and write modes.
309
                  'pending-merged-patches', 'branch-name',
310
                  'branch-lock'):
1 by mbp at sourcefrog
import from baz patch-364
311
            self.controlfile(f, 'w').write('')
312
        mutter('created control directory in ' + self.base)
313
        Inventory().write_xml(self.controlfile('inventory','w'))
314
315
316
    def _check_format(self):
317
        """Check this branch format is supported.
318
319
        The current tool only supports the current unstable format.
320
321
        In the future, we might need different in-memory Branch
322
        classes to support downlevel branches.  But not yet.
163 by mbp at sourcefrog
merge win32 portability fixes
323
        """
324
        # This ignores newlines so that we can open branches created
325
        # on Windows from Linux and so on.  I think it might be better
326
        # to always make all internal files in unix format.
245 by mbp at sourcefrog
- control files always in utf-8-unix format
327
        fmt = self.controlfile('branch-format', 'r').read()
163 by mbp at sourcefrog
merge win32 portability fixes
328
        fmt.replace('\r\n', '')
1 by mbp at sourcefrog
import from baz patch-364
329
        if fmt != BZR_BRANCH_FORMAT:
576 by Martin Pool
- raise exceptions rather than using bailout()
330
            raise BzrError('sorry, branch format %r not supported' % fmt,
331
                           ['use a different bzr version',
332
                            'or remove the .bzr directory and "bzr init" again'])
1 by mbp at sourcefrog
import from baz patch-364
333
334
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
335
1 by mbp at sourcefrog
import from baz patch-364
336
    def read_working_inventory(self):
337
        """Read the working inventory."""
338
        before = time.time()
245 by mbp at sourcefrog
- control files always in utf-8-unix format
339
        # ElementTree does its own conversion from UTF-8, so open in
340
        # binary.
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
341
        self.lock_read()
342
        try:
343
            inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
344
            mutter("loaded inventory of %d items in %f"
345
                   % (len(inv), time.time() - before))
346
            return inv
347
        finally:
348
            self.unlock()
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
349
            
1 by mbp at sourcefrog
import from baz patch-364
350
351
    def _write_inventory(self, inv):
352
        """Update the working inventory.
353
354
        That is to say, the inventory describing changes underway, that
355
        will be committed to the next revision.
356
        """
770 by Martin Pool
- write new working inventory using AtomicFile
357
        self.lock_write()
358
        try:
359
            from bzrlib.atomicfile import AtomicFile
360
361
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
362
            try:
363
                inv.write_xml(f)
364
                f.commit()
365
            finally:
366
                f.close()
367
        finally:
368
            self.unlock()
369
        
14 by mbp at sourcefrog
write inventory to temporary file and atomically replace
370
        mutter('wrote working inventory')
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
371
            
1 by mbp at sourcefrog
import from baz patch-364
372
373
    inventory = property(read_working_inventory, _write_inventory, None,
374
                         """Inventory for the working copy.""")
375
376
493 by Martin Pool
- Merge aaron's merge command
377
    def add(self, files, verbose=False, ids=None):
1 by mbp at sourcefrog
import from baz patch-364
378
        """Make files versioned.
379
247 by mbp at sourcefrog
doc
380
        Note that the command line normally calls smart_add instead.
381
1 by mbp at sourcefrog
import from baz patch-364
382
        This puts the files in the Added state, so that they will be
383
        recorded by the next commit.
384
596 by Martin Pool
doc
385
        files
386
            List of paths to add, relative to the base of the tree.
387
388
        ids
389
            If set, use these instead of automatically generated ids.
390
            Must be the same length as the list of files, but may
391
            contain None for ids that are to be autogenerated.
392
254 by Martin Pool
- Doc cleanups from Magnus Therning
393
        TODO: Perhaps have an option to add the ids even if the files do
596 by Martin Pool
doc
394
              not (yet) exist.
1 by mbp at sourcefrog
import from baz patch-364
395
254 by Martin Pool
- Doc cleanups from Magnus Therning
396
        TODO: Perhaps return the ids of the files?  But then again it
596 by Martin Pool
doc
397
              is easy to retrieve them if they're needed.
1 by mbp at sourcefrog
import from baz patch-364
398
254 by Martin Pool
- Doc cleanups from Magnus Therning
399
        TODO: Adding a directory should optionally recurse down and
596 by Martin Pool
doc
400
              add all non-ignored children.  Perhaps do that in a
401
              higher-level method.
1 by mbp at sourcefrog
import from baz patch-364
402
        """
403
        # TODO: Re-adding a file that is removed in the working copy
404
        # should probably put it back with the previous ID.
405
        if isinstance(files, types.StringTypes):
493 by Martin Pool
- Merge aaron's merge command
406
            assert(ids is None or isinstance(ids, types.StringTypes))
1 by mbp at sourcefrog
import from baz patch-364
407
            files = [files]
493 by Martin Pool
- Merge aaron's merge command
408
            if ids is not None:
409
                ids = [ids]
410
411
        if ids is None:
412
            ids = [None] * len(files)
413
        else:
414
            assert(len(ids) == len(files))
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
415
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
416
        self.lock_write()
417
        try:
418
            inv = self.read_working_inventory()
419
            for f,file_id in zip(files, ids):
420
                if is_control_file(f):
421
                    raise BzrError("cannot add control file %s" % quotefn(f))
422
423
                fp = splitpath(f)
424
425
                if len(fp) == 0:
426
                    raise BzrError("cannot add top-level %r" % f)
427
428
                fullpath = os.path.normpath(self.abspath(f))
429
430
                try:
431
                    kind = file_kind(fullpath)
432
                except OSError:
433
                    # maybe something better?
434
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
435
436
                if kind != 'file' and kind != 'directory':
437
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
438
439
                if file_id is None:
440
                    file_id = gen_file_id(f)
441
                inv.add_path(f, kind=kind, file_id=file_id)
442
443
                if verbose:
772 by Martin Pool
- fix verbose output from Branch.add
444
                    print 'added', quotefn(f)
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
445
446
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
447
448
            self._write_inventory(inv)
449
        finally:
450
            self.unlock()
70 by mbp at sourcefrog
Prepare for smart recursive add.
451
            
1 by mbp at sourcefrog
import from baz patch-364
452
176 by mbp at sourcefrog
New cat command contributed by janmar.
453
    def print_file(self, file, revno):
454
        """Print `file` to stdout."""
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
455
        self.lock_read()
456
        try:
457
            tree = self.revision_tree(self.lookup_revision(revno))
458
            # use inventory as it was in that revision
459
            file_id = tree.inventory.path2id(file)
460
            if not file_id:
461
                raise BzrError("%r is not present in revision %d" % (file, revno))
462
            tree.print_file(file_id)
463
        finally:
464
            self.unlock()
465
466
1 by mbp at sourcefrog
import from baz patch-364
467
    def remove(self, files, verbose=False):
468
        """Mark nominated files for removal from the inventory.
469
470
        This does not remove their text.  This does not run on 
471
254 by Martin Pool
- Doc cleanups from Magnus Therning
472
        TODO: Refuse to remove modified files unless --force is given?
1 by mbp at sourcefrog
import from baz patch-364
473
254 by Martin Pool
- Doc cleanups from Magnus Therning
474
        TODO: Do something useful with directories.
1 by mbp at sourcefrog
import from baz patch-364
475
254 by Martin Pool
- Doc cleanups from Magnus Therning
476
        TODO: Should this remove the text or not?  Tough call; not
1 by mbp at sourcefrog
import from baz patch-364
477
        removing may be useful and the user can just use use rm, and
478
        is the opposite of add.  Removing it is consistent with most
479
        other tools.  Maybe an option.
480
        """
481
        ## TODO: Normalize names
482
        ## TODO: Remove nested loops; better scalability
483
        if isinstance(files, types.StringTypes):
484
            files = [files]
580 by Martin Pool
- Use explicit lock methods on a branch, rather than doing it
485
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
486
        self.lock_write()
487
488
        try:
489
            tree = self.working_tree()
490
            inv = tree.inventory
491
492
            # do this before any modifications
493
            for f in files:
494
                fid = inv.path2id(f)
495
                if not fid:
496
                    raise BzrError("cannot remove unversioned file %s" % quotefn(f))
497
                mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
498
                if verbose:
499
                    # having remove it, it must be either ignored or unknown
500
                    if tree.is_ignored(f):
501
                        new_status = 'I'
502
                    else:
503
                        new_status = '?'
504
                    show_status(new_status, inv[fid].kind, quotefn(f))
505
                del inv[fid]
506
507
            self._write_inventory(inv)
508
        finally:
509
            self.unlock()
510
511
612 by Martin Pool
doc
512
    # FIXME: this doesn't need to be a branch method
493 by Martin Pool
- Merge aaron's merge command
513
    def set_inventory(self, new_inventory_list):
514
        inv = Inventory()
515
        for path, file_id, parent, kind in new_inventory_list:
516
            name = os.path.basename(path)
517
            if name == "":
518
                continue
519
            inv.add(InventoryEntry(file_id, name, kind, parent))
520
        self._write_inventory(inv)
521
1 by mbp at sourcefrog
import from baz patch-364
522
523
    def unknowns(self):
524
        """Return all unknown files.
525
526
        These are files in the working directory that are not versioned or
527
        control files or ignored.
528
        
529
        >>> b = ScratchBranch(files=['foo', 'foo~'])
530
        >>> list(b.unknowns())
531
        ['foo']
532
        >>> b.add('foo')
533
        >>> list(b.unknowns())
534
        []
535
        >>> b.remove('foo')
536
        >>> list(b.unknowns())
537
        ['foo']
538
        """
539
        return self.working_tree().unknowns()
540
541
233 by mbp at sourcefrog
- more output from test.sh
542
    def append_revision(self, revision_id):
769 by Martin Pool
- append to branch revision history using AtomicFile
543
        from bzrlib.atomicfile import AtomicFile
544
233 by mbp at sourcefrog
- more output from test.sh
545
        mutter("add {%s} to revision-history" % revision_id)
769 by Martin Pool
- append to branch revision history using AtomicFile
546
        rev_history = self.revision_history() + [revision_id]
547
548
        f = AtomicFile(self.controlfilename('revision-history'))
549
        try:
550
            for rev_id in rev_history:
551
                print >>f, rev_id
552
            f.commit()
553
        finally:
554
            f.close()
233 by mbp at sourcefrog
- more output from test.sh
555
556
1 by mbp at sourcefrog
import from baz patch-364
557
    def get_revision(self, revision_id):
558
        """Return the Revision object for a named revision"""
666 by Martin Pool
- add check on revision-ids
559
        if not revision_id or not isinstance(revision_id, basestring):
560
            raise ValueError('invalid revision-id: %r' % revision_id)
1 by mbp at sourcefrog
import from baz patch-364
561
        r = Revision.read_xml(self.revision_store[revision_id])
562
        assert r.revision_id == revision_id
563
        return r
564
672 by Martin Pool
- revision records include the hash of their inventory and
565
    def get_revision_sha1(self, revision_id):
566
        """Hash the stored value of a revision, and return it."""
567
        # In the future, revision entries will be signed. At that
568
        # point, it is probably best *not* to include the signature
569
        # in the revision hash. Because that lets you re-sign
570
        # the revision, (add signatures/remove signatures) and still
571
        # have all hash pointers stay consistent.
572
        # But for now, just hash the contents.
573
        return sha_file(self.revision_store[revision_id])
574
1 by mbp at sourcefrog
import from baz patch-364
575
576
    def get_inventory(self, inventory_id):
577
        """Get Inventory object by hash.
578
254 by Martin Pool
- Doc cleanups from Magnus Therning
579
        TODO: Perhaps for this and similar methods, take a revision
1 by mbp at sourcefrog
import from baz patch-364
580
               parameter which can be either an integer revno or a
581
               string hash."""
582
        i = Inventory.read_xml(self.inventory_store[inventory_id])
583
        return i
584
672 by Martin Pool
- revision records include the hash of their inventory and
585
    def get_inventory_sha1(self, inventory_id):
586
        """Return the sha1 hash of the inventory entry
587
        """
588
        return sha_file(self.inventory_store[inventory_id])
589
1 by mbp at sourcefrog
import from baz patch-364
590
591
    def get_revision_inventory(self, revision_id):
592
        """Return inventory of a past revision."""
593
        if revision_id == None:
594
            return Inventory()
595
        else:
596
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
597
598
599
    def revision_history(self):
600
        """Return sequence of revision hashes on to this branch.
601
602
        >>> ScratchBranch().revision_history()
603
        []
604
        """
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
605
        self.lock_read()
606
        try:
607
            return [l.rstrip('\r\n') for l in
608
                    self.controlfile('revision-history', 'r').readlines()]
609
        finally:
610
            self.unlock()
1 by mbp at sourcefrog
import from baz patch-364
611
612
622 by Martin Pool
Updated merge patch from Aaron
613
    def common_ancestor(self, other, self_revno=None, other_revno=None):
614
        """
615
        >>> import commit
616
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
617
        >>> sb.common_ancestor(sb) == (None, None)
618
        True
619
        >>> commit.commit(sb, "Committing first revision", verbose=False)
620
        >>> sb.common_ancestor(sb)[0]
621
        1
622
        >>> clone = sb.clone()
623
        >>> commit.commit(sb, "Committing second revision", verbose=False)
624
        >>> sb.common_ancestor(sb)[0]
625
        2
626
        >>> sb.common_ancestor(clone)[0]
627
        1
628
        >>> commit.commit(clone, "Committing divergent second revision", 
629
        ...               verbose=False)
630
        >>> sb.common_ancestor(clone)[0]
631
        1
632
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
633
        True
634
        >>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
635
        True
636
        >>> clone2 = sb.clone()
637
        >>> sb.common_ancestor(clone2)[0]
638
        2
639
        >>> sb.common_ancestor(clone2, self_revno=1)[0]
640
        1
641
        >>> sb.common_ancestor(clone2, other_revno=1)[0]
642
        1
643
        """
644
        my_history = self.revision_history()
645
        other_history = other.revision_history()
646
        if self_revno is None:
647
            self_revno = len(my_history)
648
        if other_revno is None:
649
            other_revno = len(other_history)
650
        indices = range(min((self_revno, other_revno)))
651
        indices.reverse()
652
        for r in indices:
653
            if my_history[r] == other_history[r]:
654
                return r+1, my_history[r]
655
        return None, None
656
385 by Martin Pool
- New Branch.enum_history method
657
    def enum_history(self, direction):
658
        """Return (revno, revision_id) for history of branch.
659
660
        direction
661
            'forward' is from earliest to latest
662
            'reverse' is from latest to earliest
663
        """
664
        rh = self.revision_history()
665
        if direction == 'forward':
666
            i = 1
667
            for rid in rh:
668
                yield i, rid
669
                i += 1
670
        elif direction == 'reverse':
671
            i = len(rh)
672
            while i > 0:
673
                yield i, rh[i-1]
674
                i -= 1
675
        else:
526 by Martin Pool
- use ValueError for bad internal parameters
676
            raise ValueError('invalid history direction', direction)
385 by Martin Pool
- New Branch.enum_history method
677
678
1 by mbp at sourcefrog
import from baz patch-364
679
    def revno(self):
680
        """Return current revision number for this branch.
681
682
        That is equivalent to the number of revisions committed to
683
        this branch.
684
        """
685
        return len(self.revision_history())
686
687
688
    def last_patch(self):
689
        """Return last patch hash, or None if no history.
690
        """
691
        ph = self.revision_history()
692
        if ph:
693
            return ph[-1]
184 by mbp at sourcefrog
pychecker fixups
694
        else:
695
            return None
485 by Martin Pool
- move commit code into its own module
696
697
685 by Martin Pool
- add -r option to the branch command
698
    def missing_revisions(self, other, stop_revision=None):
628 by Martin Pool
- merge aaron's updated merge/pull code
699
        """
700
        If self and other have not diverged, return a list of the revisions
701
        present in other, but missing from self.
702
703
        >>> from bzrlib.commit import commit
704
        >>> bzrlib.trace.silent = True
705
        >>> br1 = ScratchBranch()
706
        >>> br2 = ScratchBranch()
707
        >>> br1.missing_revisions(br2)
708
        []
709
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
710
        >>> br1.missing_revisions(br2)
711
        [u'REVISION-ID-1']
712
        >>> br2.missing_revisions(br1)
713
        []
714
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
715
        >>> br1.missing_revisions(br2)
716
        []
717
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
718
        >>> br1.missing_revisions(br2)
719
        [u'REVISION-ID-2A']
720
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
721
        >>> br1.missing_revisions(br2)
722
        Traceback (most recent call last):
723
        DivergedBranches: These branches have diverged.
724
        """
725
        self_history = self.revision_history()
726
        self_len = len(self_history)
727
        other_history = other.revision_history()
728
        other_len = len(other_history)
729
        common_index = min(self_len, other_len) -1
730
        if common_index >= 0 and \
731
            self_history[common_index] != other_history[common_index]:
732
            raise DivergedBranches(self, other)
685 by Martin Pool
- add -r option to the branch command
733
734
        if stop_revision is None:
735
            stop_revision = other_len
736
        elif stop_revision > other_len:
737
            raise NoSuchRevision(self, stop_revision)
738
        
739
        return other_history[self_len:stop_revision]
740
741
742
    def update_revisions(self, other, stop_revision=None):
663 by Martin Pool
doc
743
        """Pull in all new revisions from other branch.
744
        
628 by Martin Pool
- merge aaron's updated merge/pull code
745
        >>> from bzrlib.commit import commit
746
        >>> bzrlib.trace.silent = True
747
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
748
        >>> br1.add('foo')
749
        >>> br1.add('bar')
750
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
751
        >>> br2 = ScratchBranch()
752
        >>> br2.update_revisions(br1)
753
        Added 2 texts.
754
        Added 1 inventories.
755
        Added 1 revisions.
756
        >>> br2.revision_history()
757
        [u'REVISION-ID-1']
758
        >>> br2.update_revisions(br1)
759
        Added 0 texts.
760
        Added 0 inventories.
761
        Added 0 revisions.
762
        >>> br1.text_store.total_size() == br2.text_store.total_size()
763
        True
764
        """
670 by Martin Pool
- Show progress while branching
765
        from bzrlib.progress import ProgressBar
766
767
        pb = ProgressBar()
768
769
        pb.update('comparing histories')
685 by Martin Pool
- add -r option to the branch command
770
        revision_ids = self.missing_revisions(other, stop_revision)
790 by Martin Pool
Merge from aaron:
771
772
        if hasattr(other.revision_store, "prefetch"):
773
            other.revision_store.prefetch(revision_ids)
774
        if hasattr(other.inventory_store, "prefetch"):
775
            inventory_ids = [other.get_revision(r).inventory_id
776
                             for r in revision_ids]
777
            other.inventory_store.prefetch(inventory_ids)
778
                
670 by Martin Pool
- Show progress while branching
779
        revisions = []
628 by Martin Pool
- merge aaron's updated merge/pull code
780
        needed_texts = sets.Set()
670 by Martin Pool
- Show progress while branching
781
        i = 0
782
        for rev_id in revision_ids:
783
            i += 1
784
            pb.update('fetching revision', i, len(revision_ids))
785
            rev = other.get_revision(rev_id)
786
            revisions.append(rev)
628 by Martin Pool
- merge aaron's updated merge/pull code
787
            inv = other.get_inventory(str(rev.inventory_id))
788
            for key, entry in inv.iter_entries():
789
                if entry.text_id is None:
790
                    continue
791
                if entry.text_id not in self.text_store:
792
                    needed_texts.add(entry.text_id)
670 by Martin Pool
- Show progress while branching
793
794
        pb.clear()
795
                    
628 by Martin Pool
- merge aaron's updated merge/pull code
796
        count = self.text_store.copy_multi(other.text_store, needed_texts)
797
        print "Added %d texts." % count 
798
        inventory_ids = [ f.inventory_id for f in revisions ]
799
        count = self.inventory_store.copy_multi(other.inventory_store, 
800
                                                inventory_ids)
801
        print "Added %d inventories." % count 
802
        revision_ids = [ f.revision_id for f in revisions]
803
        count = self.revision_store.copy_multi(other.revision_store, 
804
                                               revision_ids)
805
        for revision_id in revision_ids:
806
            self.append_revision(revision_id)
807
        print "Added %d revisions." % count
808
                    
809
        
485 by Martin Pool
- move commit code into its own module
810
    def commit(self, *args, **kw):
811
        from bzrlib.commit import commit
812
        commit(self, *args, **kw)
184 by mbp at sourcefrog
pychecker fixups
813
        
1 by mbp at sourcefrog
import from baz patch-364
814
815
    def lookup_revision(self, revno):
816
        """Return revision hash for revision number."""
817
        if revno == 0:
818
            return None
819
820
        try:
821
            # list is 0-based; revisions are 1-based
822
            return self.revision_history()[revno-1]
823
        except IndexError:
184 by mbp at sourcefrog
pychecker fixups
824
            raise BzrError("no such revision %s" % revno)
1 by mbp at sourcefrog
import from baz patch-364
825
826
827
    def revision_tree(self, revision_id):
828
        """Return Tree for a revision on this branch.
829
830
        `revision_id` may be None for the null revision, in which case
831
        an `EmptyTree` is returned."""
529 by Martin Pool
todo
832
        # TODO: refactor this to use an existing revision object
833
        # so we don't need to read it in twice.
1 by mbp at sourcefrog
import from baz patch-364
834
        if revision_id == None:
835
            return EmptyTree()
836
        else:
837
            inv = self.get_revision_inventory(revision_id)
838
            return RevisionTree(self.text_store, inv)
839
840
841
    def working_tree(self):
842
        """Return a `Tree` for the working copy."""
453 by Martin Pool
- Split WorkingTree into its own file
843
        from workingtree import WorkingTree
1 by mbp at sourcefrog
import from baz patch-364
844
        return WorkingTree(self.base, self.read_working_inventory())
845
846
847
    def basis_tree(self):
848
        """Return `Tree` object for last revision.
849
850
        If there are no revisions yet, return an `EmptyTree`.
851
        """
852
        r = self.last_patch()
853
        if r == None:
854
            return EmptyTree()
855
        else:
856
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
857
858
859
168 by mbp at sourcefrog
new "rename" command
860
    def rename_one(self, from_rel, to_rel):
309 by Martin Pool
doc
861
        """Rename one file.
862
863
        This can change the directory or the filename or both.
353 by Martin Pool
- Per-branch locks in read and write modes.
864
        """
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
865
        self.lock_write()
171 by mbp at sourcefrog
better error message when working file rename fails
866
        try:
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
867
            tree = self.working_tree()
868
            inv = tree.inventory
869
            if not tree.has_filename(from_rel):
870
                raise BzrError("can't rename: old working file %r does not exist" % from_rel)
871
            if tree.has_filename(to_rel):
872
                raise BzrError("can't rename: new working file %r already exists" % to_rel)
873
874
            file_id = inv.path2id(from_rel)
875
            if file_id == None:
876
                raise BzrError("can't rename: old name %r is not versioned" % from_rel)
877
878
            if inv.path2id(to_rel):
879
                raise BzrError("can't rename: new name %r is already versioned" % to_rel)
880
881
            to_dir, to_tail = os.path.split(to_rel)
882
            to_dir_id = inv.path2id(to_dir)
883
            if to_dir_id == None and to_dir != '':
884
                raise BzrError("can't determine destination directory id for %r" % to_dir)
885
886
            mutter("rename_one:")
887
            mutter("  file_id    {%s}" % file_id)
888
            mutter("  from_rel   %r" % from_rel)
889
            mutter("  to_rel     %r" % to_rel)
890
            mutter("  to_dir     %r" % to_dir)
891
            mutter("  to_dir_id  {%s}" % to_dir_id)
892
893
            inv.rename(file_id, to_dir_id, to_tail)
894
895
            print "%s => %s" % (from_rel, to_rel)
896
897
            from_abs = self.abspath(from_rel)
898
            to_abs = self.abspath(to_rel)
899
            try:
900
                os.rename(from_abs, to_abs)
901
            except OSError, e:
902
                raise BzrError("failed to rename %r to %r: %s"
903
                        % (from_abs, to_abs, e[1]),
904
                        ["rename rolled back"])
905
906
            self._write_inventory(inv)
907
        finally:
908
            self.unlock()
909
910
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
911
    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
912
        """Rename files.
913
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
914
        to_name must exist as a versioned directory.
915
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
916
        If to_name exists and is a directory, the files are moved into
917
        it, keeping their old names.  If it is a directory, 
918
919
        Note that to_name is only the last component of the new name;
920
        this doesn't change the directory.
921
        """
611 by Martin Pool
- remove @with_writelock, @with_readlock decorators
922
        self.lock_write()
923
        try:
924
            ## TODO: Option to move IDs only
925
            assert not isinstance(from_paths, basestring)
926
            tree = self.working_tree()
927
            inv = tree.inventory
928
            to_abs = self.abspath(to_name)
929
            if not isdir(to_abs):
930
                raise BzrError("destination %r is not a directory" % to_abs)
931
            if not tree.has_filename(to_name):
932
                raise BzrError("destination %r not in working directory" % to_abs)
933
            to_dir_id = inv.path2id(to_name)
934
            if to_dir_id == None and to_name != '':
935
                raise BzrError("destination %r is not a versioned directory" % to_name)
936
            to_dir_ie = inv[to_dir_id]
937
            if to_dir_ie.kind not in ('directory', 'root_directory'):
938
                raise BzrError("destination %r is not a directory" % to_abs)
939
940
            to_idpath = inv.get_idpath(to_dir_id)
941
942
            for f in from_paths:
943
                if not tree.has_filename(f):
944
                    raise BzrError("%r does not exist in working tree" % f)
945
                f_id = inv.path2id(f)
946
                if f_id == None:
947
                    raise BzrError("%r is not versioned" % f)
948
                name_tail = splitpath(f)[-1]
949
                dest_path = appendpath(to_name, name_tail)
950
                if tree.has_filename(dest_path):
951
                    raise BzrError("destination %r already exists" % dest_path)
952
                if f_id in to_idpath:
953
                    raise BzrError("can't move %r to a subdirectory of itself" % f)
954
955
            # OK, so there's a race here, it's possible that someone will
956
            # create a file in this interval and then the rename might be
957
            # left half-done.  But we should have caught most problems.
958
959
            for f in from_paths:
960
                name_tail = splitpath(f)[-1]
961
                dest_path = appendpath(to_name, name_tail)
962
                print "%s => %s" % (f, dest_path)
963
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
964
                try:
965
                    os.rename(self.abspath(f), self.abspath(dest_path))
966
                except OSError, e:
967
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
968
                            ["rename rolled back"])
969
970
            self._write_inventory(inv)
971
        finally:
972
            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
973
974
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
975
    def revert(self, filenames, old_tree=None, backups=True):
778 by Martin Pool
- simple revert of text files
976
        """Restore selected files to the versions from a previous tree.
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
977
978
        backups
979
            If true (default) backups are made of files before
980
            they're renamed.
778 by Martin Pool
- simple revert of text files
981
        """
982
        from bzrlib.errors import NotVersionedError, BzrError
983
        from bzrlib.atomicfile import AtomicFile
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
984
        from bzrlib.osutils import backup_file
778 by Martin Pool
- simple revert of text files
985
        
986
        inv = self.read_working_inventory()
987
        if old_tree is None:
988
            old_tree = self.basis_tree()
989
        old_inv = old_tree.inventory
990
991
        nids = []
992
        for fn in filenames:
993
            file_id = inv.path2id(fn)
994
            if not file_id:
995
                raise NotVersionedError("not a versioned file", fn)
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
996
            if not old_inv.has_id(file_id):
997
                raise BzrError("file not present in old tree", fn, file_id)
778 by Martin Pool
- simple revert of text files
998
            nids.append((fn, file_id))
999
            
1000
        # TODO: Rename back if it was previously at a different location
1001
1002
        # TODO: If given a directory, restore the entire contents from
1003
        # the previous version.
1004
1005
        # TODO: Make a backup to a temporary file.
1006
1007
        # TODO: If the file previously didn't exist, delete it?
1008
        for fn, file_id in nids:
782 by Martin Pool
- Branch.revert copies files to backups before reverting them
1009
            backup_file(fn)
1010
            
778 by Martin Pool
- simple revert of text files
1011
            f = AtomicFile(fn, 'wb')
1012
            try:
1013
                f.write(old_tree.get_file(file_id).read())
1014
                f.commit()
1015
            finally:
1016
                f.close()
1017
1018
1 by mbp at sourcefrog
import from baz patch-364
1019
1020
class ScratchBranch(Branch):
1021
    """Special test class: a branch that cleans up after itself.
1022
1023
    >>> b = ScratchBranch()
1024
    >>> isdir(b.base)
1025
    True
1026
    >>> bd = b.base
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1027
    >>> b.destroy()
1 by mbp at sourcefrog
import from baz patch-364
1028
    >>> isdir(bd)
1029
    False
1030
    """
622 by Martin Pool
Updated merge patch from Aaron
1031
    def __init__(self, files=[], dirs=[], base=None):
1 by mbp at sourcefrog
import from baz patch-364
1032
        """Make a test branch.
1033
1034
        This creates a temporary directory and runs init-tree in it.
1035
1036
        If any files are listed, they are created in the working copy.
1037
        """
622 by Martin Pool
Updated merge patch from Aaron
1038
        init = False
1039
        if base is None:
1040
            base = tempfile.mkdtemp()
1041
            init = True
1042
        Branch.__init__(self, base, init=init)
100 by mbp at sourcefrog
- add test case for ignore files
1043
        for d in dirs:
1044
            os.mkdir(self.abspath(d))
1045
            
1 by mbp at sourcefrog
import from baz patch-364
1046
        for f in files:
1047
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
1048
1049
622 by Martin Pool
Updated merge patch from Aaron
1050
    def clone(self):
1051
        """
1052
        >>> orig = ScratchBranch(files=["file1", "file2"])
1053
        >>> clone = orig.clone()
1054
        >>> os.path.samefile(orig.base, clone.base)
1055
        False
1056
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
1057
        True
1058
        """
1059
        base = tempfile.mkdtemp()
1060
        os.rmdir(base)
1061
        shutil.copytree(self.base, base, symlinks=True)
1062
        return ScratchBranch(base=base)
1063
        
1 by mbp at sourcefrog
import from baz patch-364
1064
    def __del__(self):
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1065
        self.destroy()
1066
1067
    def destroy(self):
1 by mbp at sourcefrog
import from baz patch-364
1068
        """Destroy the test branch, removing the scratch directory."""
163 by mbp at sourcefrog
merge win32 portability fixes
1069
        try:
610 by Martin Pool
- replace Branch.lock(mode) with separate lock_read and lock_write
1070
            if self.base:
1071
                mutter("delete ScratchBranch %s" % self.base)
1072
                shutil.rmtree(self.base)
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1073
        except OSError, e:
163 by mbp at sourcefrog
merge win32 portability fixes
1074
            # Work around for shutil.rmtree failing on Windows when
1075
            # readonly files are encountered
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1076
            mutter("hit exception in destroying ScratchBranch: %s" % e)
163 by mbp at sourcefrog
merge win32 portability fixes
1077
            for root, dirs, files in os.walk(self.base, topdown=False):
1078
                for name in files:
1079
                    os.chmod(os.path.join(root, name), 0700)
1080
            shutil.rmtree(self.base)
396 by Martin Pool
- Using the destructor on a ScratchBranch is not reliable;
1081
        self.base = None
1 by mbp at sourcefrog
import from baz patch-364
1082
1083
    
1084
1085
######################################################################
1086
# predicates
1087
1088
1089
def is_control_file(filename):
1090
    ## FIXME: better check
1091
    filename = os.path.normpath(filename)
1092
    while filename != '':
1093
        head, tail = os.path.split(filename)
1094
        ## mutter('check %r for control file' % ((head, tail), ))
1095
        if tail == bzrlib.BZRDIR:
1096
            return True
70 by mbp at sourcefrog
Prepare for smart recursive add.
1097
        if filename == head:
1098
            break
1 by mbp at sourcefrog
import from baz patch-364
1099
        filename = head
1100
    return False
1101
1102
1103
70 by mbp at sourcefrog
Prepare for smart recursive add.
1104
def gen_file_id(name):
1 by mbp at sourcefrog
import from baz patch-364
1105
    """Return new file id.
1106
1107
    This should probably generate proper UUIDs, but for the moment we
1108
    cope with just randomness because running uuidgen every time is
1109
    slow."""
535 by Martin Pool
- try to eliminate wierd characters from file names when they're
1110
    import re
1111
1112
    # get last component
70 by mbp at sourcefrog
Prepare for smart recursive add.
1113
    idx = name.rfind('/')
1114
    if idx != -1:
1115
        name = name[idx+1 : ]
262 by Martin Pool
- gen_file_id: break the file on either / or \ when looking
1116
    idx = name.rfind('\\')
1117
    if idx != -1:
1118
        name = name[idx+1 : ]
70 by mbp at sourcefrog
Prepare for smart recursive add.
1119
535 by Martin Pool
- try to eliminate wierd characters from file names when they're
1120
    # make it not a hidden file
70 by mbp at sourcefrog
Prepare for smart recursive add.
1121
    name = name.lstrip('.')
1122
535 by Martin Pool
- try to eliminate wierd characters from file names when they're
1123
    # remove any wierd characters; we don't escape them but rather
1124
    # just pull them out
1125
    name = re.sub(r'[^\w.]', '', name)
1126
190 by mbp at sourcefrog
64 bits of randomness in file/revision ids
1127
    s = hexlify(rand_bytes(8))
1 by mbp at sourcefrog
import from baz patch-364
1128
    return '-'.join((name, compact_date(time.time()), s))