~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-23 06:25:55 UTC
  • Revision ID: mbp@sourcefrog.net-20050323062555-5489339018d0c043
- import a subset of elementtree for easier installation

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
from trace import mutter, note
27
27
from tree import Tree, EmptyTree, RevisionTree, WorkingTree
28
28
from inventory import InventoryEntry, Inventory
29
 
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
 
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, chomp, \
30
30
     format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
31
 
     joinpath, sha_string, file_kind, local_time_offset, appendpath
 
31
     joinpath, sha_string, file_kind, local_time_offset
32
32
from store import ImmutableStore
33
33
from revision import Revision
34
 
from errors import bailout, BzrError
 
34
from errors import bailout
35
35
from textui import show_status
36
36
from diff import diff_trees
37
37
 
47
47
 
48
48
    Basically we keep looking up until we find the control directory or
49
49
    run into the root."""
50
 
    if f == None:
 
50
    if f is None:
51
51
        f = os.getcwd()
52
52
    elif hasattr(os.path, 'realpath'):
53
53
        f = os.path.realpath(f)
56
56
 
57
57
    orig_f = f
58
58
 
 
59
    last_f = f
59
60
    while True:
60
61
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
61
62
            return f
62
63
        head, tail = os.path.split(f)
63
64
        if head == f:
64
65
            # reached the root, whatever that may be
65
 
            raise BzrError('%r is not in a branch' % orig_f)
 
66
            bailout('%r is not in a branch' % orig_f)
66
67
        f = head
67
68
    
68
69
 
73
74
class Branch:
74
75
    """Branch holding a history of revisions.
75
76
 
76
 
    base
77
 
        Base directory of the branch.
 
77
    :todo: Perhaps use different stores for different classes of object,
 
78
           so that we can keep track of how much space each one uses,
 
79
           or garbage-collect them.
 
80
 
 
81
    :todo: Add a RemoteBranch subclass.  For the basic case of read-only
 
82
           HTTP access this should be very easy by, 
 
83
           just redirecting controlfile access into HTTP requests.
 
84
           We would need a RemoteStore working similarly.
 
85
 
 
86
    :todo: Keep the on-disk branch locked while the object exists.
 
87
 
 
88
    :todo: mkdir() method.
78
89
    """
79
 
    _lockmode = None
80
 
    
81
 
    def __init__(self, base, init=False, find_root=True, lock_mode='w'):
 
90
    def __init__(self, base, init=False, find_root=True):
82
91
        """Create new branch object at a particular location.
83
92
 
84
 
        base -- Base directory for the branch.
 
93
        :param base: Base directory for the branch.
85
94
        
86
 
        init -- If True, create new control files in a previously
 
95
        :param init: If True, create new control files in a previously
87
96
             unversioned directory.  If False, the branch must already
88
97
             be versioned.
89
98
 
90
 
        find_root -- If true and init is false, find the root of the
 
99
        :param find_root: If true and init is false, find the root of the
91
100
             existing branch containing base.
92
101
 
93
102
        In the test suite, creation of new trees is tested using the
105
114
                        ['use "bzr init" to initialize a new working tree',
106
115
                         'current bzr can only operate from top-of-tree'])
107
116
        self._check_format()
108
 
        self.lock(lock_mode)
109
117
 
110
118
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
111
119
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
119
127
    __repr__ = __str__
120
128
 
121
129
 
122
 
 
123
 
    def lock(self, mode='w'):
124
 
        """Lock the on-disk branch, excluding other processes."""
125
 
        try:
126
 
            import fcntl, errno
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
 
            try:
138
 
                lockfile = os.open(self.controlfilename('branch-lock'), om)
139
 
            except OSError, e:
140
 
                if e.errno == errno.ENOENT:
141
 
                    # might not exist on branches from <0.0.4
142
 
                    self.controlfile('branch-lock', 'w').close()
143
 
                    lockfile = os.open(self.controlfilename('branch-lock'), om)
144
 
                else:
145
 
                    raise e
146
 
            
147
 
            fcntl.lockf(lockfile, lm)
148
 
            def unlock():
149
 
                fcntl.lockf(lockfile, fcntl.LOCK_UN)
150
 
                os.close(lockfile)
151
 
                self._lockmode = None
152
 
            self.unlock = unlock
153
 
            self._lockmode = mode
154
 
        except ImportError:
155
 
            warning("please write a locking method for platform %r" % sys.platform)
156
 
            def unlock():
157
 
                self._lockmode = None
158
 
            self.unlock = unlock
159
 
            self._lockmode = mode
160
 
 
161
 
 
162
 
    def _need_readlock(self):
163
 
        if self._lockmode not in ['r', 'w']:
164
 
            raise BzrError('need read lock on branch, only have %r' % self._lockmode)
165
 
 
166
 
    def _need_writelock(self):
167
 
        if self._lockmode not in ['w']:
168
 
            raise BzrError('need write lock on branch, only have %r' % self._lockmode)
169
 
 
170
 
 
171
130
    def abspath(self, name):
172
131
        """Return absolute filename for something in the branch"""
173
132
        return os.path.join(self.base, name)
194
153
 
195
154
 
196
155
    def controlfile(self, file_or_path, mode='r'):
197
 
        """Open a control file for this branch.
198
 
 
199
 
        There are two classes of file in the control directory: text
200
 
        and binary.  binary files are untranslated byte streams.  Text
201
 
        control files are stored with Unix newlines and in UTF-8, even
202
 
        if the platform or locale defaults are different.
203
 
        """
204
 
 
205
 
        fn = self.controlfilename(file_or_path)
206
 
 
207
 
        if mode == 'rb' or mode == 'wb':
208
 
            return file(fn, mode)
209
 
        elif mode == 'r' or mode == 'w':
210
 
            # open in binary mode anyhow so there's no newline translation;
211
 
            # codecs uses line buffering by default; don't want that.
212
 
            import codecs
213
 
            return codecs.open(fn, mode + 'b', 'utf-8',
214
 
                               buffering=60000)
215
 
        else:
216
 
            raise BzrError("invalid controlfile mode %r" % mode)
217
 
 
 
156
        """Open a control file for this branch"""
 
157
        return file(self.controlfilename(file_or_path), mode)
218
158
 
219
159
 
220
160
    def _make_control(self):
226
166
        for d in ('text-store', 'inventory-store', 'revision-store'):
227
167
            os.mkdir(self.controlfilename(d))
228
168
        for f in ('revision-history', 'merged-patches',
229
 
                  'pending-merged-patches', 'branch-name',
230
 
                  'branch-lock'):
 
169
                  'pending-merged-patches', 'branch-name'):
231
170
            self.controlfile(f, 'w').write('')
232
171
        mutter('created control directory in ' + self.base)
233
172
        Inventory().write_xml(self.controlfile('inventory','w'))
240
179
 
241
180
        In the future, we might need different in-memory Branch
242
181
        classes to support downlevel branches.  But not yet.
243
 
        """
244
 
        # This ignores newlines so that we can open branches created
245
 
        # on Windows from Linux and so on.  I think it might be better
246
 
        # to always make all internal files in unix format.
247
 
        fmt = self.controlfile('branch-format', 'r').read()
248
 
        fmt.replace('\r\n', '')
 
182
        """        
 
183
        # read in binary mode to detect newline wierdness.
 
184
        fmt = self.controlfile('branch-format', 'rb').read()
249
185
        if fmt != BZR_BRANCH_FORMAT:
250
186
            bailout('sorry, branch format %r not supported' % fmt,
251
187
                    ['use a different bzr version',
254
190
 
255
191
    def read_working_inventory(self):
256
192
        """Read the working inventory."""
257
 
        self._need_readlock()
258
193
        before = time.time()
259
 
        # ElementTree does its own conversion from UTF-8, so open in
260
 
        # binary.
261
 
        inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
 
194
        inv = Inventory.read_xml(self.controlfile('inventory', 'r'))
262
195
        mutter("loaded inventory of %d items in %f"
263
196
               % (len(inv), time.time() - before))
264
197
        return inv
270
203
        That is to say, the inventory describing changes underway, that
271
204
        will be committed to the next revision.
272
205
        """
273
 
        self._need_writelock()
274
206
        ## TODO: factor out to atomicfile?  is rename safe on windows?
275
207
        ## TODO: Maybe some kind of clean/dirty marker on inventory?
276
208
        tmpfname = self.controlfilename('inventory.tmp')
277
 
        tmpf = file(tmpfname, 'wb')
 
209
        tmpf = file(tmpfname, 'w')
278
210
        inv.write_xml(tmpf)
279
211
        tmpf.close()
280
 
        inv_fname = self.controlfilename('inventory')
281
 
        if sys.platform == 'win32':
282
 
            os.remove(inv_fname)
283
 
        os.rename(tmpfname, inv_fname)
 
212
        os.rename(tmpfname, self.controlfilename('inventory'))
284
213
        mutter('wrote working inventory')
285
214
 
286
215
 
291
220
    def add(self, files, verbose=False):
292
221
        """Make files versioned.
293
222
 
294
 
        Note that the command line normally calls smart_add instead.
295
 
 
296
223
        This puts the files in the Added state, so that they will be
297
224
        recorded by the next commit.
298
225
 
299
 
        TODO: Perhaps have an option to add the ids even if the files do
 
226
        :todo: Perhaps have an option to add the ids even if the files do
300
227
               not (yet) exist.
301
228
 
302
 
        TODO: Perhaps return the ids of the files?  But then again it
 
229
        :todo: Perhaps return the ids of the files?  But then again it
303
230
               is easy to retrieve them if they're needed.
304
231
 
305
 
        TODO: Option to specify file id.
 
232
        :todo: Option to specify file id.
306
233
 
307
 
        TODO: Adding a directory should optionally recurse down and
 
234
        :todo: Adding a directory should optionally recurse down and
308
235
               add all non-ignored children.  Perhaps do that in a
309
236
               higher-level method.
310
237
 
330
257
        Traceback (most recent call last):
331
258
        BzrError: ('cannot add: not a regular file or directory: nothere', [])
332
259
        """
333
 
        self._need_writelock()
334
260
 
335
261
        # TODO: Re-adding a file that is removed in the working copy
336
262
        # should probably put it back with the previous ID.
369
295
        self._write_inventory(inv)
370
296
 
371
297
 
372
 
    def print_file(self, file, revno):
373
 
        """Print `file` to stdout."""
374
 
        self._need_readlock()
375
 
        tree = self.revision_tree(self.lookup_revision(revno))
376
 
        # use inventory as it was in that revision
377
 
        file_id = tree.inventory.path2id(file)
378
 
        if not file_id:
379
 
            bailout("%r is not present in revision %d" % (file, revno))
380
 
        tree.print_file(file_id)
381
 
        
382
298
 
383
299
    def remove(self, files, verbose=False):
384
300
        """Mark nominated files for removal from the inventory.
385
301
 
386
302
        This does not remove their text.  This does not run on 
387
303
 
388
 
        TODO: Refuse to remove modified files unless --force is given?
 
304
        :todo: Refuse to remove modified files unless --force is given?
389
305
 
390
306
        >>> b = ScratchBranch(files=['foo'])
391
307
        >>> b.add('foo')
409
325
        >>> b.working_tree().has_filename('foo') 
410
326
        True
411
327
 
412
 
        TODO: Do something useful with directories.
 
328
        :todo: Do something useful with directories.
413
329
 
414
 
        TODO: Should this remove the text or not?  Tough call; not
 
330
        :todo: Should this remove the text or not?  Tough call; not
415
331
        removing may be useful and the user can just use use rm, and
416
332
        is the opposite of add.  Removing it is consistent with most
417
333
        other tools.  Maybe an option.
418
334
        """
419
335
        ## TODO: Normalize names
420
336
        ## TODO: Remove nested loops; better scalability
421
 
        self._need_writelock()
422
337
 
423
338
        if isinstance(files, types.StringTypes):
424
339
            files = [files]
482
397
        be robust against files disappearing, moving, etc.  So the
483
398
        whole thing is a bit hard.
484
399
 
485
 
        timestamp -- if not None, seconds-since-epoch for a
 
400
        :param timestamp: if not None, seconds-since-epoch for a
486
401
             postdated/predated commit.
487
402
        """
488
 
        self._need_writelock()
489
403
 
490
404
        ## TODO: Show branch names
491
405
 
566
480
                            state = 'A'
567
481
                        elif (old_ie.name == entry.name
568
482
                              and old_ie.parent_id == entry.parent_id):
 
483
                            state = 'R'
 
484
                        else:
569
485
                            state = 'M'
570
 
                        else:
571
 
                            state = 'R'
572
486
 
573
487
                        show_status(state, entry.kind, quotefn(path))
574
488
 
627
541
        ## TODO: Also calculate and store the inventory SHA1
628
542
        mutter("committing patch r%d" % (self.revno() + 1))
629
543
 
630
 
 
631
 
        self.append_revision(rev_id)
632
 
        
633
 
        if verbose:
634
 
            note("commited r%d" % self.revno())
635
 
 
636
 
 
637
 
    def append_revision(self, revision_id):
638
 
        mutter("add {%s} to revision-history" % revision_id)
639
 
        rev_history = self.revision_history()
640
 
 
641
 
        tmprhname = self.controlfilename('revision-history.tmp')
642
 
        rhname = self.controlfilename('revision-history')
643
 
        
644
 
        f = file(tmprhname, 'wt')
645
 
        rev_history.append(revision_id)
646
 
        f.write('\n'.join(rev_history))
647
 
        f.write('\n')
648
 
        f.close()
649
 
 
650
 
        if sys.platform == 'win32':
651
 
            os.remove(rhname)
652
 
        os.rename(tmprhname, rhname)
653
 
        
 
544
        mutter("append to revision-history")
 
545
        self.controlfile('revision-history', 'at').write(rev_id + '\n')
 
546
 
 
547
        mutter("done!")
654
548
 
655
549
 
656
550
    def get_revision(self, revision_id):
657
551
        """Return the Revision object for a named revision"""
658
 
        self._need_readlock()
659
552
        r = Revision.read_xml(self.revision_store[revision_id])
660
553
        assert r.revision_id == revision_id
661
554
        return r
664
557
    def get_inventory(self, inventory_id):
665
558
        """Get Inventory object by hash.
666
559
 
667
 
        TODO: Perhaps for this and similar methods, take a revision
 
560
        :todo: Perhaps for this and similar methods, take a revision
668
561
               parameter which can be either an integer revno or a
669
562
               string hash."""
670
 
        self._need_readlock()
671
563
        i = Inventory.read_xml(self.inventory_store[inventory_id])
672
564
        return i
673
565
 
674
566
 
675
567
    def get_revision_inventory(self, revision_id):
676
568
        """Return inventory of a past revision."""
677
 
        self._need_readlock()
678
569
        if revision_id == None:
679
570
            return Inventory()
680
571
        else:
687
578
        >>> ScratchBranch().revision_history()
688
579
        []
689
580
        """
690
 
        self._need_readlock()
691
 
        return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
692
 
 
693
 
 
694
 
    def enum_history(self, direction):
695
 
        """Return (revno, revision_id) for history of branch.
696
 
 
697
 
        direction
698
 
            'forward' is from earliest to latest
699
 
            'reverse' is from latest to earliest
700
 
        """
701
 
        rh = self.revision_history()
702
 
        if direction == 'forward':
703
 
            i = 1
704
 
            for rid in rh:
705
 
                yield i, rid
706
 
                i += 1
707
 
        elif direction == 'reverse':
708
 
            i = len(rh)
709
 
            while i > 0:
710
 
                yield i, rh[i-1]
711
 
                i -= 1
712
 
        else:
713
 
            raise BzrError('invalid history direction %r' % direction)
 
581
        return [chomp(l) for l in self.controlfile('revision-history').readlines()]
714
582
 
715
583
 
716
584
    def revno(self):
738
606
        ph = self.revision_history()
739
607
        if ph:
740
608
            return ph[-1]
741
 
        else:
742
 
            return None
743
 
        
 
609
 
744
610
 
745
611
    def lookup_revision(self, revno):
746
612
        """Return revision hash for revision number."""
751
617
            # list is 0-based; revisions are 1-based
752
618
            return self.revision_history()[revno-1]
753
619
        except IndexError:
754
 
            raise BzrError("no such revision %s" % revno)
 
620
            bailout("no such revision %s" % revno)
755
621
 
756
622
 
757
623
    def revision_tree(self, revision_id):
759
625
 
760
626
        `revision_id` may be None for the null revision, in which case
761
627
        an `EmptyTree` is returned."""
762
 
        self._need_readlock()
 
628
 
763
629
        if revision_id == None:
764
630
            return EmptyTree()
765
631
        else:
795
661
 
796
662
 
797
663
 
798
 
    def rename_one(self, from_rel, to_rel):
799
 
        """Rename one file.
800
 
 
801
 
        This can change the directory or the filename or both.
802
 
        """
803
 
        self._need_writelock()
804
 
        tree = self.working_tree()
805
 
        inv = tree.inventory
806
 
        if not tree.has_filename(from_rel):
807
 
            bailout("can't rename: old working file %r does not exist" % from_rel)
808
 
        if tree.has_filename(to_rel):
809
 
            bailout("can't rename: new working file %r already exists" % to_rel)
810
 
            
811
 
        file_id = inv.path2id(from_rel)
812
 
        if file_id == None:
813
 
            bailout("can't rename: old name %r is not versioned" % from_rel)
814
 
 
815
 
        if inv.path2id(to_rel):
816
 
            bailout("can't rename: new name %r is already versioned" % to_rel)
817
 
 
818
 
        to_dir, to_tail = os.path.split(to_rel)
819
 
        to_dir_id = inv.path2id(to_dir)
820
 
        if to_dir_id == None and to_dir != '':
821
 
            bailout("can't determine destination directory id for %r" % to_dir)
822
 
 
823
 
        mutter("rename_one:")
824
 
        mutter("  file_id    {%s}" % file_id)
825
 
        mutter("  from_rel   %r" % from_rel)
826
 
        mutter("  to_rel     %r" % to_rel)
827
 
        mutter("  to_dir     %r" % to_dir)
828
 
        mutter("  to_dir_id  {%s}" % to_dir_id)
829
 
            
830
 
        inv.rename(file_id, to_dir_id, to_tail)
831
 
 
832
 
        print "%s => %s" % (from_rel, to_rel)
833
 
        
834
 
        from_abs = self.abspath(from_rel)
835
 
        to_abs = self.abspath(to_rel)
836
 
        try:
837
 
            os.rename(from_abs, to_abs)
838
 
        except OSError, e:
839
 
            bailout("failed to rename %r to %r: %s"
840
 
                    % (from_abs, to_abs, e[1]),
841
 
                    ["rename rolled back"])
842
 
 
843
 
        self._write_inventory(inv)
844
 
            
845
 
 
846
 
 
847
 
    def move(self, from_paths, to_name):
848
 
        """Rename files.
849
 
 
850
 
        to_name must exist as a versioned directory.
851
 
 
852
 
        If to_name exists and is a directory, the files are moved into
853
 
        it, keeping their old names.  If it is a directory, 
854
 
 
855
 
        Note that to_name is only the last component of the new name;
856
 
        this doesn't change the directory.
857
 
        """
858
 
        self._need_writelock()
859
 
        ## TODO: Option to move IDs only
860
 
        assert not isinstance(from_paths, basestring)
861
 
        tree = self.working_tree()
862
 
        inv = tree.inventory
863
 
        to_abs = self.abspath(to_name)
864
 
        if not isdir(to_abs):
865
 
            bailout("destination %r is not a directory" % to_abs)
866
 
        if not tree.has_filename(to_name):
867
 
            bailout("destination %r not in working directory" % to_abs)
868
 
        to_dir_id = inv.path2id(to_name)
869
 
        if to_dir_id == None and to_name != '':
870
 
            bailout("destination %r is not a versioned directory" % to_name)
871
 
        to_dir_ie = inv[to_dir_id]
872
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
873
 
            bailout("destination %r is not a directory" % to_abs)
874
 
 
875
 
        to_idpath = Set(inv.get_idpath(to_dir_id))
876
 
 
877
 
        for f in from_paths:
878
 
            if not tree.has_filename(f):
879
 
                bailout("%r does not exist in working tree" % f)
880
 
            f_id = inv.path2id(f)
881
 
            if f_id == None:
882
 
                bailout("%r is not versioned" % f)
883
 
            name_tail = splitpath(f)[-1]
884
 
            dest_path = appendpath(to_name, name_tail)
885
 
            if tree.has_filename(dest_path):
886
 
                bailout("destination %r already exists" % dest_path)
887
 
            if f_id in to_idpath:
888
 
                bailout("can't move %r to a subdirectory of itself" % f)
889
 
 
890
 
        # OK, so there's a race here, it's possible that someone will
891
 
        # create a file in this interval and then the rename might be
892
 
        # left half-done.  But we should have caught most problems.
893
 
 
894
 
        for f in from_paths:
895
 
            name_tail = splitpath(f)[-1]
896
 
            dest_path = appendpath(to_name, name_tail)
897
 
            print "%s => %s" % (f, dest_path)
898
 
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
899
 
            try:
900
 
                os.rename(self.abspath(f), self.abspath(dest_path))
901
 
            except OSError, e:
902
 
                bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
903
 
                        ["rename rolled back"])
904
 
 
905
 
        self._write_inventory(inv)
906
 
 
907
 
 
908
 
 
909
 
    def show_status(self, show_all=False):
 
664
    def write_log(self, show_timezone='original'):
 
665
        """Write out human-readable log of commits to this branch
 
666
 
 
667
        :param utc: If true, show dates in universal time, not local time."""
 
668
        ## TODO: Option to choose either original, utc or local timezone
 
669
        revno = 1
 
670
        precursor = None
 
671
        for p in self.revision_history():
 
672
            print '-' * 40
 
673
            print 'revno:', revno
 
674
            ## TODO: Show hash if --id is given.
 
675
            ##print 'revision-hash:', p
 
676
            rev = self.get_revision(p)
 
677
            print 'committer:', rev.committer
 
678
            print 'timestamp: %s' % (format_date(rev.timestamp, rev.timezone or 0,
 
679
                                                 show_timezone))
 
680
 
 
681
            ## opportunistic consistency check, same as check_patch_chaining
 
682
            if rev.precursor != precursor:
 
683
                bailout("mismatched precursor!")
 
684
 
 
685
            print 'message:'
 
686
            if not rev.message:
 
687
                print '  (no message)'
 
688
            else:
 
689
                for l in rev.message.split('\n'):
 
690
                    print '  ' + l
 
691
 
 
692
            revno += 1
 
693
            precursor = p
 
694
 
 
695
 
 
696
 
 
697
    def show_status(branch, show_all=False):
910
698
        """Display single-line status for non-ignored working files.
911
699
 
912
700
        The list is show sorted in order by file name.
923
711
        >>> b.show_status()
924
712
        D       foo
925
713
        
926
 
        TODO: Get state for single files.
 
714
 
 
715
        :todo: Get state for single files.
 
716
 
 
717
        :todo: Perhaps show a slash at the end of directory names.        
 
718
 
927
719
        """
928
 
        self._need_readlock()
929
720
 
930
721
        # We have to build everything into a list first so that it can
931
722
        # sorted by name, incorporating all the different sources.
936
727
        # Interesting case: the old ID for a file has been removed,
937
728
        # but a new file has been created under that name.
938
729
 
939
 
        old = self.basis_tree()
940
 
        new = self.working_tree()
 
730
        old = branch.basis_tree()
 
731
        old_inv = old.inventory
 
732
        new = branch.working_tree()
 
733
        new_inv = new.inventory
941
734
 
942
735
        for fs, fid, oldname, newname, kind in diff_trees(old, new):
943
736
            if fs == 'R':
956
749
            elif fs == '?':
957
750
                show_status(fs, kind, newname)
958
751
            else:
959
 
                bailout("weird file state %r" % ((fs, fid),))
 
752
                bailout("wierd file state %r" % ((fs, fid),))
960
753
                
961
754
 
962
755
 
971
764
    >>> isdir(bd)
972
765
    False
973
766
    """
974
 
    def __init__(self, files=[], dirs=[]):
 
767
    def __init__(self, files = []):
975
768
        """Make a test branch.
976
769
 
977
770
        This creates a temporary directory and runs init-tree in it.
979
772
        If any files are listed, they are created in the working copy.
980
773
        """
981
774
        Branch.__init__(self, tempfile.mkdtemp(), init=True)
982
 
        for d in dirs:
983
 
            os.mkdir(self.abspath(d))
984
 
            
985
775
        for f in files:
986
776
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
987
777
 
988
778
 
989
779
    def __del__(self):
990
780
        """Destroy the test branch, removing the scratch directory."""
991
 
        try:
992
 
            shutil.rmtree(self.base)
993
 
        except OSError:
994
 
            # Work around for shutil.rmtree failing on Windows when
995
 
            # readonly files are encountered
996
 
            for root, dirs, files in os.walk(self.base, topdown=False):
997
 
                for name in files:
998
 
                    os.chmod(os.path.join(root, name), 0700)
999
 
            shutil.rmtree(self.base)
 
781
        shutil.rmtree(self.base)
1000
782
 
1001
783
    
1002
784
 
1035
817
    idx = name.rfind('/')
1036
818
    if idx != -1:
1037
819
        name = name[idx+1 : ]
1038
 
    idx = name.rfind('\\')
1039
 
    if idx != -1:
1040
 
        name = name[idx+1 : ]
1041
820
 
1042
821
    name = name.lstrip('.')
1043
822
 
1044
823
    s = hexlify(rand_bytes(8))
1045
824
    return '-'.join((name, compact_date(time.time()), s))
 
825
 
 
826