/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
18
from sets import Set
19
20
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
21
import traceback, socket, fnmatch, difflib, time
22
from binascii import hexlify
23
24
import bzrlib
25
from inventory import Inventory
26
from trace import mutter, note
27
from tree import Tree, EmptyTree, RevisionTree, WorkingTree
28
from inventory import InventoryEntry, Inventory
319 by Martin Pool
- remove trivial chomp() function
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
1 by mbp at sourcefrog
import from baz patch-364
30
     format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
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
31
     joinpath, sha_string, file_kind, local_time_offset, appendpath
1 by mbp at sourcefrog
import from baz patch-364
32
from store import ImmutableStore
33
from revision import Revision
184 by mbp at sourcefrog
pychecker fixups
34
from errors import bailout, BzrError
1 by mbp at sourcefrog
import from baz patch-364
35
from textui import show_status
36
from diff import diff_trees
37
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
## TODO: Maybe include checks for common corruption of newlines, etc?
40
41
42
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
43
def find_branch_root(f=None):
44
    """Find the branch root enclosing f, or pwd.
45
46
    It is not necessary that f exists.
47
48
    Basically we keep looking up until we find the control directory or
49
    run into the root."""
184 by mbp at sourcefrog
pychecker fixups
50
    if f == None:
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
51
        f = os.getcwd()
52
    elif hasattr(os.path, 'realpath'):
53
        f = os.path.realpath(f)
54
    else:
55
        f = os.path.abspath(f)
56
57
    orig_f = f
58
59
    while True:
60
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
61
            return f
62
        head, tail = os.path.split(f)
63
        if head == f:
64
            # reached the root, whatever that may be
184 by mbp at sourcefrog
pychecker fixups
65
            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
66
        f = head
67
    
1 by mbp at sourcefrog
import from baz patch-364
68
69
70
######################################################################
71
# branch objects
72
73
class Branch:
74
    """Branch holding a history of revisions.
75
343 by Martin Pool
doc
76
    base
77
        Base directory of the branch.
1 by mbp at sourcefrog
import from baz patch-364
78
    """
353 by Martin Pool
- Per-branch locks in read and write modes.
79
    _lockmode = None
80
    
81
    def __init__(self, base, init=False, find_root=True, lock_mode='w'):
1 by mbp at sourcefrog
import from baz patch-364
82
        """Create new branch object at a particular location.
83
254 by Martin Pool
- Doc cleanups from Magnus Therning
84
        base -- Base directory for the branch.
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
85
        
254 by Martin Pool
- Doc cleanups from Magnus Therning
86
        init -- If True, create new control files in a previously
1 by mbp at sourcefrog
import from baz patch-364
87
             unversioned directory.  If False, the branch must already
88
             be versioned.
89
254 by Martin Pool
- Doc cleanups from Magnus Therning
90
        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
91
             existing branch containing base.
92
1 by mbp at sourcefrog
import from baz patch-364
93
        In the test suite, creation of new trees is tested using the
94
        `ScratchBranch` class.
95
        """
96
        if init:
64 by mbp at sourcefrog
- fix up init command for new find-branch-root function
97
            self.base = os.path.realpath(base)
1 by mbp at sourcefrog
import from baz patch-364
98
            self._make_control()
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
99
        elif find_root:
100
            self.base = find_branch_root(base)
1 by mbp at sourcefrog
import from baz patch-364
101
        else:
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
102
            self.base = os.path.realpath(base)
1 by mbp at sourcefrog
import from baz patch-364
103
            if not isdir(self.controlfilename('.')):
104
                bailout("not a bzr branch: %s" % quotefn(base),
105
                        ['use "bzr init" to initialize a new working tree',
106
                         'current bzr can only operate from top-of-tree'])
62 by mbp at sourcefrog
- new find_branch_root function; based on suggestion from aaron
107
        self._check_format()
353 by Martin Pool
- Per-branch locks in read and write modes.
108
        self.lock(lock_mode)
1 by mbp at sourcefrog
import from baz patch-364
109
110
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
111
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
112
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
113
114
115
    def __str__(self):
116
        return '%s(%r)' % (self.__class__.__name__, self.base)
117
118
119
    __repr__ = __str__
120
121
353 by Martin Pool
- Per-branch locks in read and write modes.
122
123
    def lock(self, mode='w'):
124
        """Lock the on-disk branch, excluding other processes."""
125
        try:
126
            import fcntl
127
128
            if mode == 'w':
129
                lm = fcntl.LOCK_EX
130
                om = os.O_WRONLY | os.O_CREAT
131
            elif mode == 'r':
132
                lm = fcntl.LOCK_SH
133
                om = os.O_RDONLY
134
            else:
135
                raise BzrError("invalid locking mode %r" % mode)
136
137
            lockfile = os.open(self.controlfilename('branch-lock'), om)
138
            fcntl.lockf(lockfile, lm)
139
            def unlock(self):
140
                fcntl.lockf(lockfile, fcntl.LOCK_UN)
141
                os.close(lockfile)
142
                self._lockmode = None
143
            self.unlock = unlock
144
            self._lockmode = mode
145
        except ImportError:
146
            warning("please write a locking method for platform %r" % sys.platform)
147
            def unlock(self):
148
                self._lockmode = None
149
            self.unlock = unlock
150
            self._lockmode = mode
151
152
153
    def _need_readlock(self):
154
        if self._lockmode not in ['r', 'w']:
155
            raise BzrError('need read lock on branch, only have %r' % self._lockmode)
156
157
    def _need_writelock(self):
158
        if self._lockmode not in ['w']:
159
            raise BzrError('need write lock on branch, only have %r' % self._lockmode)
160
161
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
162
    def abspath(self, name):
163
        """Return absolute filename for something in the branch"""
1 by mbp at sourcefrog
import from baz patch-364
164
        return os.path.join(self.base, name)
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
165
1 by mbp at sourcefrog
import from baz patch-364
166
68 by mbp at sourcefrog
- new relpath command and function
167
    def relpath(self, path):
168
        """Return path relative to this branch of something inside it.
169
170
        Raises an error if path is not in this branch."""
171
        rp = os.path.realpath(path)
172
        # FIXME: windows
173
        if not rp.startswith(self.base):
174
            bailout("path %r is not within branch %r" % (rp, self.base))
175
        rp = rp[len(self.base):]
176
        rp = rp.lstrip(os.sep)
177
        return rp
178
179
1 by mbp at sourcefrog
import from baz patch-364
180
    def controlfilename(self, file_or_path):
181
        """Return location relative to branch."""
182
        if isinstance(file_or_path, types.StringTypes):
183
            file_or_path = [file_or_path]
184
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
185
186
187
    def controlfile(self, file_or_path, mode='r'):
245 by mbp at sourcefrog
- control files always in utf-8-unix format
188
        """Open a control file for this branch.
189
190
        There are two classes of file in the control directory: text
191
        and binary.  binary files are untranslated byte streams.  Text
192
        control files are stored with Unix newlines and in UTF-8, even
193
        if the platform or locale defaults are different.
194
        """
195
196
        fn = self.controlfilename(file_or_path)
197
198
        if mode == 'rb' or mode == 'wb':
199
            return file(fn, mode)
200
        elif mode == 'r' or mode == 'w':
259 by Martin Pool
- use larger file buffers when opening branch control file
201
            # open in binary mode anyhow so there's no newline translation;
202
            # codecs uses line buffering by default; don't want that.
245 by mbp at sourcefrog
- control files always in utf-8-unix format
203
            import codecs
259 by Martin Pool
- use larger file buffers when opening branch control file
204
            return codecs.open(fn, mode + 'b', 'utf-8',
205
                               buffering=60000)
245 by mbp at sourcefrog
- control files always in utf-8-unix format
206
        else:
207
            raise BzrError("invalid controlfile mode %r" % mode)
208
1 by mbp at sourcefrog
import from baz patch-364
209
210
211
    def _make_control(self):
212
        os.mkdir(self.controlfilename([]))
213
        self.controlfile('README', 'w').write(
214
            "This is a Bazaar-NG control directory.\n"
215
            "Do not change any files in this directory.")
245 by mbp at sourcefrog
- control files always in utf-8-unix format
216
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
1 by mbp at sourcefrog
import from baz patch-364
217
        for d in ('text-store', 'inventory-store', 'revision-store'):
218
            os.mkdir(self.controlfilename(d))
219
        for f in ('revision-history', 'merged-patches',
353 by Martin Pool
- Per-branch locks in read and write modes.
220
                  'pending-merged-patches', 'branch-name',
221
                  'branch-lock'):
1 by mbp at sourcefrog
import from baz patch-364
222
            self.controlfile(f, 'w').write('')
223
        mutter('created control directory in ' + self.base)
224
        Inventory().write_xml(self.controlfile('inventory','w'))
225
226
227
    def _check_format(self):
228
        """Check this branch format is supported.
229
230
        The current tool only supports the current unstable format.
231
232
        In the future, we might need different in-memory Branch
233
        classes to support downlevel branches.  But not yet.
163 by mbp at sourcefrog
merge win32 portability fixes
234
        """
235
        # This ignores newlines so that we can open branches created
236
        # on Windows from Linux and so on.  I think it might be better
237
        # to always make all internal files in unix format.
245 by mbp at sourcefrog
- control files always in utf-8-unix format
238
        fmt = self.controlfile('branch-format', 'r').read()
163 by mbp at sourcefrog
merge win32 portability fixes
239
        fmt.replace('\r\n', '')
1 by mbp at sourcefrog
import from baz patch-364
240
        if fmt != BZR_BRANCH_FORMAT:
241
            bailout('sorry, branch format %r not supported' % fmt,
242
                    ['use a different bzr version',
243
                     'or remove the .bzr directory and "bzr init" again'])
244
245
246
    def read_working_inventory(self):
247
        """Read the working inventory."""
353 by Martin Pool
- Per-branch locks in read and write modes.
248
        self._need_readlock()
1 by mbp at sourcefrog
import from baz patch-364
249
        before = time.time()
245 by mbp at sourcefrog
- control files always in utf-8-unix format
250
        # ElementTree does its own conversion from UTF-8, so open in
251
        # binary.
252
        inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
1 by mbp at sourcefrog
import from baz patch-364
253
        mutter("loaded inventory of %d items in %f"
254
               % (len(inv), time.time() - before))
255
        return inv
256
257
258
    def _write_inventory(self, inv):
259
        """Update the working inventory.
260
261
        That is to say, the inventory describing changes underway, that
262
        will be committed to the next revision.
263
        """
353 by Martin Pool
- Per-branch locks in read and write modes.
264
        self._need_writelock()
14 by mbp at sourcefrog
write inventory to temporary file and atomically replace
265
        ## TODO: factor out to atomicfile?  is rename safe on windows?
70 by mbp at sourcefrog
Prepare for smart recursive add.
266
        ## TODO: Maybe some kind of clean/dirty marker on inventory?
14 by mbp at sourcefrog
write inventory to temporary file and atomically replace
267
        tmpfname = self.controlfilename('inventory.tmp')
245 by mbp at sourcefrog
- control files always in utf-8-unix format
268
        tmpf = file(tmpfname, 'wb')
14 by mbp at sourcefrog
write inventory to temporary file and atomically replace
269
        inv.write_xml(tmpf)
270
        tmpf.close()
163 by mbp at sourcefrog
merge win32 portability fixes
271
        inv_fname = self.controlfilename('inventory')
272
        if sys.platform == 'win32':
273
            os.remove(inv_fname)
274
        os.rename(tmpfname, inv_fname)
14 by mbp at sourcefrog
write inventory to temporary file and atomically replace
275
        mutter('wrote working inventory')
1 by mbp at sourcefrog
import from baz patch-364
276
277
278
    inventory = property(read_working_inventory, _write_inventory, None,
279
                         """Inventory for the working copy.""")
280
281
282
    def add(self, files, verbose=False):
283
        """Make files versioned.
284
247 by mbp at sourcefrog
doc
285
        Note that the command line normally calls smart_add instead.
286
1 by mbp at sourcefrog
import from baz patch-364
287
        This puts the files in the Added state, so that they will be
288
        recorded by the next commit.
289
254 by Martin Pool
- Doc cleanups from Magnus Therning
290
        TODO: Perhaps have an option to add the ids even if the files do
1 by mbp at sourcefrog
import from baz patch-364
291
               not (yet) exist.
292
254 by Martin Pool
- Doc cleanups from Magnus Therning
293
        TODO: Perhaps return the ids of the files?  But then again it
1 by mbp at sourcefrog
import from baz patch-364
294
               is easy to retrieve them if they're needed.
295
254 by Martin Pool
- Doc cleanups from Magnus Therning
296
        TODO: Option to specify file id.
1 by mbp at sourcefrog
import from baz patch-364
297
254 by Martin Pool
- Doc cleanups from Magnus Therning
298
        TODO: Adding a directory should optionally recurse down and
1 by mbp at sourcefrog
import from baz patch-364
299
               add all non-ignored children.  Perhaps do that in a
300
               higher-level method.
301
302
        >>> b = ScratchBranch(files=['foo'])
303
        >>> 'foo' in b.unknowns()
304
        True
305
        >>> b.show_status()
306
        ?       foo
307
        >>> b.add('foo')
308
        >>> 'foo' in b.unknowns()
309
        False
310
        >>> bool(b.inventory.path2id('foo'))
311
        True
312
        >>> b.show_status()
313
        A       foo
314
315
        >>> b.add('foo')
316
        Traceback (most recent call last):
317
        ...
318
        BzrError: ('foo is already versioned', [])
319
320
        >>> b.add(['nothere'])
321
        Traceback (most recent call last):
322
        BzrError: ('cannot add: not a regular file or directory: nothere', [])
323
        """
353 by Martin Pool
- Per-branch locks in read and write modes.
324
        self._need_writelock()
1 by mbp at sourcefrog
import from baz patch-364
325
326
        # TODO: Re-adding a file that is removed in the working copy
327
        # should probably put it back with the previous ID.
328
        if isinstance(files, types.StringTypes):
329
            files = [files]
330
        
331
        inv = self.read_working_inventory()
332
        for f in files:
333
            if is_control_file(f):
334
                bailout("cannot add control file %s" % quotefn(f))
335
336
            fp = splitpath(f)
337
338
            if len(fp) == 0:
339
                bailout("cannot add top-level %r" % f)
340
                
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
341
            fullpath = os.path.normpath(self.abspath(f))
1 by mbp at sourcefrog
import from baz patch-364
342
70 by mbp at sourcefrog
Prepare for smart recursive add.
343
            try:
344
                kind = file_kind(fullpath)
345
            except OSError:
346
                # maybe something better?
347
                bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
348
            
349
            if kind != 'file' and kind != 'directory':
350
                bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
351
352
            file_id = gen_file_id(f)
353
            inv.add_path(f, kind=kind, file_id=file_id)
354
1 by mbp at sourcefrog
import from baz patch-364
355
            if verbose:
356
                show_status('A', kind, quotefn(f))
357
                
70 by mbp at sourcefrog
Prepare for smart recursive add.
358
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
359
            
1 by mbp at sourcefrog
import from baz patch-364
360
        self._write_inventory(inv)
361
362
176 by mbp at sourcefrog
New cat command contributed by janmar.
363
    def print_file(self, file, revno):
364
        """Print `file` to stdout."""
353 by Martin Pool
- Per-branch locks in read and write modes.
365
        self._need_readlock()
176 by mbp at sourcefrog
New cat command contributed by janmar.
366
        tree = self.revision_tree(self.lookup_revision(revno))
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
367
        # use inventory as it was in that revision
368
        file_id = tree.inventory.path2id(file)
369
        if not file_id:
370
            bailout("%r is not present in revision %d" % (file, revno))
371
        tree.print_file(file_id)
176 by mbp at sourcefrog
New cat command contributed by janmar.
372
        
1 by mbp at sourcefrog
import from baz patch-364
373
374
    def remove(self, files, verbose=False):
375
        """Mark nominated files for removal from the inventory.
376
377
        This does not remove their text.  This does not run on 
378
254 by Martin Pool
- Doc cleanups from Magnus Therning
379
        TODO: Refuse to remove modified files unless --force is given?
1 by mbp at sourcefrog
import from baz patch-364
380
381
        >>> b = ScratchBranch(files=['foo'])
382
        >>> b.add('foo')
383
        >>> b.inventory.has_filename('foo')
384
        True
385
        >>> b.remove('foo')
386
        >>> b.working_tree().has_filename('foo')
387
        True
388
        >>> b.inventory.has_filename('foo')
389
        False
390
        
391
        >>> b = ScratchBranch(files=['foo'])
392
        >>> b.add('foo')
393
        >>> b.commit('one')
394
        >>> b.remove('foo')
395
        >>> b.commit('two')
396
        >>> b.inventory.has_filename('foo') 
397
        False
398
        >>> b.basis_tree().has_filename('foo') 
399
        False
400
        >>> b.working_tree().has_filename('foo') 
401
        True
402
254 by Martin Pool
- Doc cleanups from Magnus Therning
403
        TODO: Do something useful with directories.
1 by mbp at sourcefrog
import from baz patch-364
404
254 by Martin Pool
- Doc cleanups from Magnus Therning
405
        TODO: Should this remove the text or not?  Tough call; not
1 by mbp at sourcefrog
import from baz patch-364
406
        removing may be useful and the user can just use use rm, and
407
        is the opposite of add.  Removing it is consistent with most
408
        other tools.  Maybe an option.
409
        """
410
        ## TODO: Normalize names
411
        ## TODO: Remove nested loops; better scalability
353 by Martin Pool
- Per-branch locks in read and write modes.
412
        self._need_writelock()
1 by mbp at sourcefrog
import from baz patch-364
413
414
        if isinstance(files, types.StringTypes):
415
            files = [files]
416
        
29 by Martin Pool
When removing files, new status should be I or ?, not D
417
        tree = self.working_tree()
418
        inv = tree.inventory
1 by mbp at sourcefrog
import from baz patch-364
419
420
        # do this before any modifications
421
        for f in files:
422
            fid = inv.path2id(f)
423
            if not fid:
424
                bailout("cannot remove unversioned file %s" % quotefn(f))
425
            mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
426
            if verbose:
29 by Martin Pool
When removing files, new status should be I or ?, not D
427
                # having remove it, it must be either ignored or unknown
428
                if tree.is_ignored(f):
429
                    new_status = 'I'
430
                else:
431
                    new_status = '?'
432
                show_status(new_status, inv[fid].kind, quotefn(f))
1 by mbp at sourcefrog
import from baz patch-364
433
            del inv[fid]
434
435
        self._write_inventory(inv)
436
437
438
    def unknowns(self):
439
        """Return all unknown files.
440
441
        These are files in the working directory that are not versioned or
442
        control files or ignored.
443
        
444
        >>> b = ScratchBranch(files=['foo', 'foo~'])
445
        >>> list(b.unknowns())
446
        ['foo']
447
        >>> b.add('foo')
448
        >>> list(b.unknowns())
449
        []
450
        >>> b.remove('foo')
451
        >>> list(b.unknowns())
452
        ['foo']
453
        """
454
        return self.working_tree().unknowns()
455
456
8 by mbp at sourcefrog
store committer's timezone in revision and show
457
    def commit(self, message, timestamp=None, timezone=None,
458
               committer=None,
1 by mbp at sourcefrog
import from baz patch-364
459
               verbose=False):
460
        """Commit working copy as a new revision.
461
        
462
        The basic approach is to add all the file texts into the
463
        store, then the inventory, then make a new revision pointing
464
        to that inventory and store that.
465
        
466
        This is not quite safe if the working copy changes during the
467
        commit; for the moment that is simply not allowed.  A better
468
        approach is to make a temporary copy of the files before
469
        computing their hashes, and then add those hashes in turn to
470
        the inventory.  This should mean at least that there are no
471
        broken hash pointers.  There is no way we can get a snapshot
472
        of the whole directory at an instant.  This would also have to
473
        be robust against files disappearing, moving, etc.  So the
474
        whole thing is a bit hard.
475
254 by Martin Pool
- Doc cleanups from Magnus Therning
476
        timestamp -- if not None, seconds-since-epoch for a
1 by mbp at sourcefrog
import from baz patch-364
477
             postdated/predated commit.
478
        """
353 by Martin Pool
- Per-branch locks in read and write modes.
479
        self._need_writelock()
1 by mbp at sourcefrog
import from baz patch-364
480
481
        ## TODO: Show branch names
482
483
        # TODO: Don't commit if there are no changes, unless forced?
484
485
        # First walk over the working inventory; and both update that
486
        # and also build a new revision inventory.  The revision
487
        # inventory needs to hold the text-id, sha1 and size of the
488
        # actual file versions committed in the revision.  (These are
489
        # not present in the working inventory.)  We also need to
490
        # detect missing/deleted files, and remove them from the
491
        # working inventory.
492
493
        work_inv = self.read_working_inventory()
494
        inv = Inventory()
495
        basis = self.basis_tree()
496
        basis_inv = basis.inventory
497
        missing_ids = []
498
        for path, entry in work_inv.iter_entries():
499
            ## TODO: Cope with files that have gone missing.
500
501
            ## TODO: Check that the file kind has not changed from the previous
502
            ## revision of this file (if any).
503
504
            entry = entry.copy()
505
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
506
            p = self.abspath(path)
1 by mbp at sourcefrog
import from baz patch-364
507
            file_id = entry.file_id
508
            mutter('commit prep file %s, id %r ' % (p, file_id))
509
510
            if not os.path.exists(p):
511
                mutter("    file is missing, removing from inventory")
512
                if verbose:
513
                    show_status('D', entry.kind, quotefn(path))
514
                missing_ids.append(file_id)
515
                continue
516
517
            # TODO: Handle files that have been deleted
518
519
            # TODO: Maybe a special case for empty files?  Seems a
520
            # waste to store them many times.
521
522
            inv.add(entry)
523
524
            if basis_inv.has_id(file_id):
525
                old_kind = basis_inv[file_id].kind
526
                if old_kind != entry.kind:
527
                    bailout("entry %r changed kind from %r to %r"
528
                            % (file_id, old_kind, entry.kind))
529
530
            if entry.kind == 'directory':
531
                if not isdir(p):
532
                    bailout("%s is entered as directory but not a directory" % quotefn(p))
533
            elif entry.kind == 'file':
534
                if not isfile(p):
535
                    bailout("%s is entered as file but is not a file" % quotefn(p))
536
537
                content = file(p, 'rb').read()
538
539
                entry.text_sha1 = sha_string(content)
540
                entry.text_size = len(content)
541
542
                old_ie = basis_inv.has_id(file_id) and basis_inv[file_id]
543
                if (old_ie
544
                    and (old_ie.text_size == entry.text_size)
545
                    and (old_ie.text_sha1 == entry.text_sha1)):
546
                    ## assert content == basis.get_file(file_id).read()
547
                    entry.text_id = basis_inv[file_id].text_id
548
                    mutter('    unchanged from previous text_id {%s}' %
549
                           entry.text_id)
550
                    
551
                else:
70 by mbp at sourcefrog
Prepare for smart recursive add.
552
                    entry.text_id = gen_file_id(entry.name)
1 by mbp at sourcefrog
import from baz patch-364
553
                    self.text_store.add(content, entry.text_id)
554
                    mutter('    stored with text_id {%s}' % entry.text_id)
555
                    if verbose:
556
                        if not old_ie:
557
                            state = 'A'
558
                        elif (old_ie.name == entry.name
559
                              and old_ie.parent_id == entry.parent_id):
93 by mbp at sourcefrog
Fix inverted display of 'R' and 'M' during 'commit -v'
560
                            state = 'M'
561
                        else:
1 by mbp at sourcefrog
import from baz patch-364
562
                            state = 'R'
563
564
                        show_status(state, entry.kind, quotefn(path))
565
566
        for file_id in missing_ids:
567
            # have to do this later so we don't mess up the iterator.
568
            # since parents may be removed before their children we
569
            # have to test.
570
571
            # FIXME: There's probably a better way to do this; perhaps
572
            # the workingtree should know how to filter itself.
573
            if work_inv.has_id(file_id):
574
                del work_inv[file_id]
575
576
577
        inv_id = rev_id = _gen_revision_id(time.time())
578
        
579
        inv_tmp = tempfile.TemporaryFile()
580
        inv.write_xml(inv_tmp)
581
        inv_tmp.seek(0)
582
        self.inventory_store.add(inv_tmp, inv_id)
583
        mutter('new inventory_id is {%s}' % inv_id)
584
585
        self._write_inventory(work_inv)
586
587
        if timestamp == None:
588
            timestamp = time.time()
589
590
        if committer == None:
591
            committer = username()
592
8 by mbp at sourcefrog
store committer's timezone in revision and show
593
        if timezone == None:
594
            timezone = local_time_offset()
595
1 by mbp at sourcefrog
import from baz patch-364
596
        mutter("building commit log message")
597
        rev = Revision(timestamp=timestamp,
8 by mbp at sourcefrog
store committer's timezone in revision and show
598
                       timezone=timezone,
1 by mbp at sourcefrog
import from baz patch-364
599
                       committer=committer,
600
                       precursor = self.last_patch(),
601
                       message = message,
602
                       inventory_id=inv_id,
603
                       revision_id=rev_id)
604
605
        rev_tmp = tempfile.TemporaryFile()
606
        rev.write_xml(rev_tmp)
607
        rev_tmp.seek(0)
608
        self.revision_store.add(rev_tmp, rev_id)
609
        mutter("new revision_id is {%s}" % rev_id)
610
        
611
        ## XXX: Everything up to here can simply be orphaned if we abort
612
        ## the commit; it will leave junk files behind but that doesn't
613
        ## matter.
614
615
        ## TODO: Read back the just-generated changeset, and make sure it
616
        ## applies and recreates the right state.
617
618
        ## TODO: Also calculate and store the inventory SHA1
619
        mutter("committing patch r%d" % (self.revno() + 1))
620
621
233 by mbp at sourcefrog
- more output from test.sh
622
        self.append_revision(rev_id)
623
        
96 by mbp at sourcefrog
with commit -v, show committed revision number
624
        if verbose:
625
            note("commited r%d" % self.revno())
1 by mbp at sourcefrog
import from baz patch-364
626
627
233 by mbp at sourcefrog
- more output from test.sh
628
    def append_revision(self, revision_id):
629
        mutter("add {%s} to revision-history" % revision_id)
630
        rev_history = self.revision_history()
631
632
        tmprhname = self.controlfilename('revision-history.tmp')
633
        rhname = self.controlfilename('revision-history')
634
        
635
        f = file(tmprhname, 'wt')
636
        rev_history.append(revision_id)
637
        f.write('\n'.join(rev_history))
638
        f.write('\n')
639
        f.close()
640
641
        if sys.platform == 'win32':
642
            os.remove(rhname)
643
        os.rename(tmprhname, rhname)
644
        
645
646
1 by mbp at sourcefrog
import from baz patch-364
647
    def get_revision(self, revision_id):
648
        """Return the Revision object for a named revision"""
353 by Martin Pool
- Per-branch locks in read and write modes.
649
        self._need_readlock()
1 by mbp at sourcefrog
import from baz patch-364
650
        r = Revision.read_xml(self.revision_store[revision_id])
651
        assert r.revision_id == revision_id
652
        return r
653
654
655
    def get_inventory(self, inventory_id):
656
        """Get Inventory object by hash.
657
254 by Martin Pool
- Doc cleanups from Magnus Therning
658
        TODO: Perhaps for this and similar methods, take a revision
1 by mbp at sourcefrog
import from baz patch-364
659
               parameter which can be either an integer revno or a
660
               string hash."""
353 by Martin Pool
- Per-branch locks in read and write modes.
661
        self._need_readlock()
1 by mbp at sourcefrog
import from baz patch-364
662
        i = Inventory.read_xml(self.inventory_store[inventory_id])
663
        return i
664
665
666
    def get_revision_inventory(self, revision_id):
667
        """Return inventory of a past revision."""
353 by Martin Pool
- Per-branch locks in read and write modes.
668
        self._need_readlock()
1 by mbp at sourcefrog
import from baz patch-364
669
        if revision_id == None:
670
            return Inventory()
671
        else:
672
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
673
674
675
    def revision_history(self):
676
        """Return sequence of revision hashes on to this branch.
677
678
        >>> ScratchBranch().revision_history()
679
        []
680
        """
353 by Martin Pool
- Per-branch locks in read and write modes.
681
        self._need_readlock()
319 by Martin Pool
- remove trivial chomp() function
682
        return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
1 by mbp at sourcefrog
import from baz patch-364
683
684
685
    def revno(self):
686
        """Return current revision number for this branch.
687
688
        That is equivalent to the number of revisions committed to
689
        this branch.
690
691
        >>> b = ScratchBranch()
692
        >>> b.revno()
693
        0
694
        >>> b.commit('no foo')
695
        >>> b.revno()
696
        1
697
        """
698
        return len(self.revision_history())
699
700
701
    def last_patch(self):
702
        """Return last patch hash, or None if no history.
703
704
        >>> ScratchBranch().last_patch() == None
705
        True
706
        """
707
        ph = self.revision_history()
708
        if ph:
709
            return ph[-1]
184 by mbp at sourcefrog
pychecker fixups
710
        else:
711
            return None
712
        
1 by mbp at sourcefrog
import from baz patch-364
713
714
    def lookup_revision(self, revno):
715
        """Return revision hash for revision number."""
716
        if revno == 0:
717
            return None
718
719
        try:
720
            # list is 0-based; revisions are 1-based
721
            return self.revision_history()[revno-1]
722
        except IndexError:
184 by mbp at sourcefrog
pychecker fixups
723
            raise BzrError("no such revision %s" % revno)
1 by mbp at sourcefrog
import from baz patch-364
724
725
726
    def revision_tree(self, revision_id):
727
        """Return Tree for a revision on this branch.
728
729
        `revision_id` may be None for the null revision, in which case
730
        an `EmptyTree` is returned."""
353 by Martin Pool
- Per-branch locks in read and write modes.
731
        self._need_readlock()
1 by mbp at sourcefrog
import from baz patch-364
732
        if revision_id == None:
733
            return EmptyTree()
734
        else:
735
            inv = self.get_revision_inventory(revision_id)
736
            return RevisionTree(self.text_store, inv)
737
738
739
    def working_tree(self):
740
        """Return a `Tree` for the working copy."""
741
        return WorkingTree(self.base, self.read_working_inventory())
742
743
744
    def basis_tree(self):
745
        """Return `Tree` object for last revision.
746
747
        If there are no revisions yet, return an `EmptyTree`.
748
749
        >>> b = ScratchBranch(files=['foo'])
750
        >>> b.basis_tree().has_filename('foo')
751
        False
752
        >>> b.working_tree().has_filename('foo')
753
        True
754
        >>> b.add('foo')
755
        >>> b.commit('add foo')
756
        >>> b.basis_tree().has_filename('foo')
757
        True
758
        """
759
        r = self.last_patch()
760
        if r == None:
761
            return EmptyTree()
762
        else:
763
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
764
765
766
244 by mbp at sourcefrog
- New 'bzr log --verbose' from Sebastian Cote
767
    def write_log(self, show_timezone='original', verbose=False):
1 by mbp at sourcefrog
import from baz patch-364
768
        """Write out human-readable log of commits to this branch
769
254 by Martin Pool
- Doc cleanups from Magnus Therning
770
        utc -- If true, show dates in universal time, not local time."""
353 by Martin Pool
- Per-branch locks in read and write modes.
771
        self._need_readlock()
9 by mbp at sourcefrog
doc
772
        ## TODO: Option to choose either original, utc or local timezone
1 by mbp at sourcefrog
import from baz patch-364
773
        revno = 1
774
        precursor = None
775
        for p in self.revision_history():
776
            print '-' * 40
777
            print 'revno:', revno
778
            ## TODO: Show hash if --id is given.
779
            ##print 'revision-hash:', p
780
            rev = self.get_revision(p)
781
            print 'committer:', rev.committer
12 by mbp at sourcefrog
new --timezone option for bzr log
782
            print 'timestamp: %s' % (format_date(rev.timestamp, rev.timezone or 0,
783
                                                 show_timezone))
1 by mbp at sourcefrog
import from baz patch-364
784
785
            ## opportunistic consistency check, same as check_patch_chaining
786
            if rev.precursor != precursor:
787
                bailout("mismatched precursor!")
788
789
            print 'message:'
790
            if not rev.message:
791
                print '  (no message)'
792
            else:
793
                for l in rev.message.split('\n'):
794
                    print '  ' + l
795
244 by mbp at sourcefrog
- New 'bzr log --verbose' from Sebastian Cote
796
            if verbose == True and precursor != None:
797
                print 'changed files:'
798
                tree = self.revision_tree(p)
799
                prevtree = self.revision_tree(precursor)
800
                
801
                for file_state, fid, old_name, new_name, kind in \
802
                                        diff_trees(prevtree, tree, ):
803
                    if file_state == 'A' or file_state == 'M':
804
                        show_status(file_state, kind, new_name)
805
                    elif file_state == 'D':
806
                        show_status(file_state, kind, old_name)
807
                    elif file_state == 'R':
808
                        show_status(file_state, kind,
809
                            old_name + ' => ' + new_name)
810
                
1 by mbp at sourcefrog
import from baz patch-364
811
            revno += 1
812
            precursor = p
813
814
168 by mbp at sourcefrog
new "rename" command
815
    def rename_one(self, from_rel, to_rel):
309 by Martin Pool
doc
816
        """Rename one file.
817
818
        This can change the directory or the filename or both.
353 by Martin Pool
- Per-branch locks in read and write modes.
819
        """
820
        self._need_writelock()
168 by mbp at sourcefrog
new "rename" command
821
        tree = self.working_tree()
822
        inv = tree.inventory
823
        if not tree.has_filename(from_rel):
824
            bailout("can't rename: old working file %r does not exist" % from_rel)
825
        if tree.has_filename(to_rel):
826
            bailout("can't rename: new working file %r already exists" % to_rel)
827
            
828
        file_id = inv.path2id(from_rel)
829
        if file_id == None:
830
            bailout("can't rename: old name %r is not versioned" % from_rel)
831
832
        if inv.path2id(to_rel):
833
            bailout("can't rename: new name %r is already versioned" % to_rel)
834
835
        to_dir, to_tail = os.path.split(to_rel)
836
        to_dir_id = inv.path2id(to_dir)
837
        if to_dir_id == None and to_dir != '':
838
            bailout("can't determine destination directory id for %r" % to_dir)
839
840
        mutter("rename_one:")
841
        mutter("  file_id    {%s}" % file_id)
842
        mutter("  from_rel   %r" % from_rel)
843
        mutter("  to_rel     %r" % to_rel)
844
        mutter("  to_dir     %r" % to_dir)
845
        mutter("  to_dir_id  {%s}" % to_dir_id)
846
            
847
        inv.rename(file_id, to_dir_id, to_tail)
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
848
849
        print "%s => %s" % (from_rel, to_rel)
171 by mbp at sourcefrog
better error message when working file rename fails
850
        
851
        from_abs = self.abspath(from_rel)
852
        to_abs = self.abspath(to_rel)
853
        try:
854
            os.rename(from_abs, to_abs)
855
        except OSError, e:
856
            bailout("failed to rename %r to %r: %s"
857
                    % (from_abs, to_abs, e[1]),
858
                    ["rename rolled back"])
168 by mbp at sourcefrog
new "rename" command
859
860
        self._write_inventory(inv)
861
            
862
1 by mbp at sourcefrog
import from baz patch-364
863
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
864
    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
865
        """Rename files.
866
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
867
        to_name must exist as a versioned directory.
868
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
869
        If to_name exists and is a directory, the files are moved into
870
        it, keeping their old names.  If it is a directory, 
871
872
        Note that to_name is only the last component of the new name;
873
        this doesn't change the directory.
874
        """
353 by Martin Pool
- Per-branch locks in read and write modes.
875
        self._need_writelock()
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
876
        ## TODO: Option to move IDs only
877
        assert not isinstance(from_paths, basestring)
878
        tree = self.working_tree()
879
        inv = tree.inventory
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
880
        to_abs = self.abspath(to_name)
881
        if not isdir(to_abs):
882
            bailout("destination %r is not a directory" % to_abs)
883
        if not tree.has_filename(to_name):
175 by mbp at sourcefrog
fix up moving files into branch root
884
            bailout("destination %r not in working directory" % to_abs)
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
885
        to_dir_id = inv.path2id(to_name)
886
        if to_dir_id == None and to_name != '':
887
            bailout("destination %r is not a versioned directory" % to_name)
888
        to_dir_ie = inv[to_dir_id]
175 by mbp at sourcefrog
fix up moving files into branch root
889
        if to_dir_ie.kind not in ('directory', 'root_directory'):
890
            bailout("destination %r is not a directory" % to_abs)
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
891
892
        to_idpath = Set(inv.get_idpath(to_dir_id))
893
894
        for f in from_paths:
895
            if not tree.has_filename(f):
896
                bailout("%r does not exist in working tree" % f)
897
            f_id = inv.path2id(f)
898
            if f_id == None:
899
                bailout("%r is not versioned" % f)
900
            name_tail = splitpath(f)[-1]
901
            dest_path = appendpath(to_name, name_tail)
902
            if tree.has_filename(dest_path):
903
                bailout("destination %r already exists" % dest_path)
904
            if f_id in to_idpath:
905
                bailout("can't move %r to a subdirectory of itself" % f)
906
907
        # OK, so there's a race here, it's possible that someone will
908
        # create a file in this interval and then the rename might be
909
        # left half-done.  But we should have caught most problems.
910
911
        for f in from_paths:
912
            name_tail = splitpath(f)[-1]
913
            dest_path = appendpath(to_name, name_tail)
914
            print "%s => %s" % (f, dest_path)
915
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
916
            try:
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
917
                os.rename(self.abspath(f), self.abspath(dest_path))
174 by mbp at sourcefrog
- New 'move' command; now separated out from rename
918
            except OSError, e:
919
                bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
920
                        ["rename rolled back"])
921
922
        self._write_inventory(inv)
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
923
924
925
184 by mbp at sourcefrog
pychecker fixups
926
    def show_status(self, show_all=False):
1 by mbp at sourcefrog
import from baz patch-364
927
        """Display single-line status for non-ignored working files.
928
929
        The list is show sorted in order by file name.
930
931
        >>> b = ScratchBranch(files=['foo', 'foo~'])
932
        >>> b.show_status()
933
        ?       foo
934
        >>> b.add('foo')
935
        >>> b.show_status()
936
        A       foo
937
        >>> b.commit("add foo")
938
        >>> b.show_status()
67 by mbp at sourcefrog
use abspath() for the function that makes an absolute
939
        >>> os.unlink(b.abspath('foo'))
15 by mbp at sourcefrog
files that have been deleted are not considered present in the WorkingTree
940
        >>> b.show_status()
941
        D       foo
942
        
254 by Martin Pool
- Doc cleanups from Magnus Therning
943
        TODO: Get state for single files.
1 by mbp at sourcefrog
import from baz patch-364
944
        """
353 by Martin Pool
- Per-branch locks in read and write modes.
945
        self._need_readlock()
1 by mbp at sourcefrog
import from baz patch-364
946
947
        # We have to build everything into a list first so that it can
948
        # sorted by name, incorporating all the different sources.
949
950
        # FIXME: Rather than getting things in random order and then sorting,
951
        # just step through in order.
952
953
        # Interesting case: the old ID for a file has been removed,
954
        # but a new file has been created under that name.
955
184 by mbp at sourcefrog
pychecker fixups
956
        old = self.basis_tree()
957
        new = self.working_tree()
1 by mbp at sourcefrog
import from baz patch-364
958
959
        for fs, fid, oldname, newname, kind in diff_trees(old, new):
960
            if fs == 'R':
961
                show_status(fs, kind,
962
                            oldname + ' => ' + newname)
963
            elif fs == 'A' or fs == 'M':
964
                show_status(fs, kind, newname)
965
            elif fs == 'D':
966
                show_status(fs, kind, oldname)
967
            elif fs == '.':
968
                if show_all:
969
                    show_status(fs, kind, newname)
970
            elif fs == 'I':
971
                if show_all:
972
                    show_status(fs, kind, newname)
973
            elif fs == '?':
974
                show_status(fs, kind, newname)
975
            else:
254 by Martin Pool
- Doc cleanups from Magnus Therning
976
                bailout("weird file state %r" % ((fs, fid),))
1 by mbp at sourcefrog
import from baz patch-364
977
                
978
979
980
class ScratchBranch(Branch):
981
    """Special test class: a branch that cleans up after itself.
982
983
    >>> b = ScratchBranch()
984
    >>> isdir(b.base)
985
    True
986
    >>> bd = b.base
987
    >>> del b
988
    >>> isdir(bd)
989
    False
990
    """
100 by mbp at sourcefrog
- add test case for ignore files
991
    def __init__(self, files=[], dirs=[]):
1 by mbp at sourcefrog
import from baz patch-364
992
        """Make a test branch.
993
994
        This creates a temporary directory and runs init-tree in it.
995
996
        If any files are listed, they are created in the working copy.
997
        """
998
        Branch.__init__(self, tempfile.mkdtemp(), init=True)
100 by mbp at sourcefrog
- add test case for ignore files
999
        for d in dirs:
1000
            os.mkdir(self.abspath(d))
1001
            
1 by mbp at sourcefrog
import from baz patch-364
1002
        for f in files:
1003
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
1004
1005
1006
    def __del__(self):
1007
        """Destroy the test branch, removing the scratch directory."""
163 by mbp at sourcefrog
merge win32 portability fixes
1008
        try:
1009
            shutil.rmtree(self.base)
1010
        except OSError:
1011
            # Work around for shutil.rmtree failing on Windows when
1012
            # readonly files are encountered
1013
            for root, dirs, files in os.walk(self.base, topdown=False):
1014
                for name in files:
1015
                    os.chmod(os.path.join(root, name), 0700)
1016
            shutil.rmtree(self.base)
1 by mbp at sourcefrog
import from baz patch-364
1017
1018
    
1019
1020
######################################################################
1021
# predicates
1022
1023
1024
def is_control_file(filename):
1025
    ## FIXME: better check
1026
    filename = os.path.normpath(filename)
1027
    while filename != '':
1028
        head, tail = os.path.split(filename)
1029
        ## mutter('check %r for control file' % ((head, tail), ))
1030
        if tail == bzrlib.BZRDIR:
1031
            return True
70 by mbp at sourcefrog
Prepare for smart recursive add.
1032
        if filename == head:
1033
            break
1 by mbp at sourcefrog
import from baz patch-364
1034
        filename = head
1035
    return False
1036
1037
1038
1039
def _gen_revision_id(when):
1040
    """Return new revision-id."""
1041
    s = '%s-%s-' % (user_email(), compact_date(when))
190 by mbp at sourcefrog
64 bits of randomness in file/revision ids
1042
    s += hexlify(rand_bytes(8))
1 by mbp at sourcefrog
import from baz patch-364
1043
    return s
1044
1045
70 by mbp at sourcefrog
Prepare for smart recursive add.
1046
def gen_file_id(name):
1 by mbp at sourcefrog
import from baz patch-364
1047
    """Return new file id.
1048
1049
    This should probably generate proper UUIDs, but for the moment we
1050
    cope with just randomness because running uuidgen every time is
1051
    slow."""
70 by mbp at sourcefrog
Prepare for smart recursive add.
1052
    idx = name.rfind('/')
1053
    if idx != -1:
1054
        name = name[idx+1 : ]
262 by Martin Pool
- gen_file_id: break the file on either / or \ when looking
1055
    idx = name.rfind('\\')
1056
    if idx != -1:
1057
        name = name[idx+1 : ]
70 by mbp at sourcefrog
Prepare for smart recursive add.
1058
1059
    name = name.lstrip('.')
1060
190 by mbp at sourcefrog
64 bits of randomness in file/revision ids
1061
    s = hexlify(rand_bytes(8))
1 by mbp at sourcefrog
import from baz patch-364
1062
    return '-'.join((name, compact_date(time.time()), s))