~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-05-03 07:48:35 UTC
  • Revision ID: mbp@sourcefrog.net-20050503074835-0133bd0c16440fcd
doc

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, chomp, \
 
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
30
30
     format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
31
31
     joinpath, sha_string, file_kind, local_time_offset, appendpath
32
32
from store import ImmutableStore
73
73
class Branch:
74
74
    """Branch holding a history of revisions.
75
75
 
76
 
    :todo: Perhaps use different stores for different classes of object,
 
76
    TODO: Perhaps use different stores for different classes of object,
77
77
           so that we can keep track of how much space each one uses,
78
78
           or garbage-collect them.
79
79
 
80
 
    :todo: Add a RemoteBranch subclass.  For the basic case of read-only
 
80
    TODO: Add a RemoteBranch subclass.  For the basic case of read-only
81
81
           HTTP access this should be very easy by, 
82
82
           just redirecting controlfile access into HTTP requests.
83
83
           We would need a RemoteStore working similarly.
84
84
 
85
 
    :todo: Keep the on-disk branch locked while the object exists.
 
85
    TODO: Keep the on-disk branch locked while the object exists.
86
86
 
87
 
    :todo: mkdir() method.
 
87
    TODO: mkdir() method.
88
88
    """
89
89
    def __init__(self, base, init=False, find_root=True):
90
90
        """Create new branch object at a particular location.
91
91
 
92
 
        :param base: Base directory for the branch.
 
92
        base -- Base directory for the branch.
93
93
        
94
 
        :param init: If True, create new control files in a previously
 
94
        init -- If True, create new control files in a previously
95
95
             unversioned directory.  If False, the branch must already
96
96
             be versioned.
97
97
 
98
 
        :param find_root: If true and init is false, find the root of the
 
98
        find_root -- If true and init is false, find the root of the
99
99
             existing branch containing base.
100
100
 
101
101
        In the test suite, creation of new trees is tested using the
152
152
 
153
153
 
154
154
    def controlfile(self, file_or_path, mode='r'):
155
 
        """Open a control file for this branch"""
156
 
        return file(self.controlfilename(file_or_path), mode)
 
155
        """Open a control file for this branch.
 
156
 
 
157
        There are two classes of file in the control directory: text
 
158
        and binary.  binary files are untranslated byte streams.  Text
 
159
        control files are stored with Unix newlines and in UTF-8, even
 
160
        if the platform or locale defaults are different.
 
161
        """
 
162
 
 
163
        fn = self.controlfilename(file_or_path)
 
164
 
 
165
        if mode == 'rb' or mode == 'wb':
 
166
            return file(fn, mode)
 
167
        elif mode == 'r' or mode == 'w':
 
168
            # open in binary mode anyhow so there's no newline translation;
 
169
            # codecs uses line buffering by default; don't want that.
 
170
            import codecs
 
171
            return codecs.open(fn, mode + 'b', 'utf-8',
 
172
                               buffering=60000)
 
173
        else:
 
174
            raise BzrError("invalid controlfile mode %r" % mode)
 
175
 
157
176
 
158
177
 
159
178
    def _make_control(self):
161
180
        self.controlfile('README', 'w').write(
162
181
            "This is a Bazaar-NG control directory.\n"
163
182
            "Do not change any files in this directory.")
164
 
        self.controlfile('branch-format', 'wb').write(BZR_BRANCH_FORMAT)
 
183
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
165
184
        for d in ('text-store', 'inventory-store', 'revision-store'):
166
185
            os.mkdir(self.controlfilename(d))
167
186
        for f in ('revision-history', 'merged-patches',
182
201
        # This ignores newlines so that we can open branches created
183
202
        # on Windows from Linux and so on.  I think it might be better
184
203
        # to always make all internal files in unix format.
185
 
        fmt = self.controlfile('branch-format', 'rb').read()
 
204
        fmt = self.controlfile('branch-format', 'r').read()
186
205
        fmt.replace('\r\n', '')
187
206
        if fmt != BZR_BRANCH_FORMAT:
188
207
            bailout('sorry, branch format %r not supported' % fmt,
193
212
    def read_working_inventory(self):
194
213
        """Read the working inventory."""
195
214
        before = time.time()
196
 
        inv = Inventory.read_xml(self.controlfile('inventory', 'r'))
 
215
        # ElementTree does its own conversion from UTF-8, so open in
 
216
        # binary.
 
217
        inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
197
218
        mutter("loaded inventory of %d items in %f"
198
219
               % (len(inv), time.time() - before))
199
220
        return inv
208
229
        ## TODO: factor out to atomicfile?  is rename safe on windows?
209
230
        ## TODO: Maybe some kind of clean/dirty marker on inventory?
210
231
        tmpfname = self.controlfilename('inventory.tmp')
211
 
        tmpf = file(tmpfname, 'w')
 
232
        tmpf = file(tmpfname, 'wb')
212
233
        inv.write_xml(tmpf)
213
234
        tmpf.close()
214
235
        inv_fname = self.controlfilename('inventory')
225
246
    def add(self, files, verbose=False):
226
247
        """Make files versioned.
227
248
 
 
249
        Note that the command line normally calls smart_add instead.
 
250
 
228
251
        This puts the files in the Added state, so that they will be
229
252
        recorded by the next commit.
230
253
 
231
 
        :todo: Perhaps have an option to add the ids even if the files do
 
254
        TODO: Perhaps have an option to add the ids even if the files do
232
255
               not (yet) exist.
233
256
 
234
 
        :todo: Perhaps return the ids of the files?  But then again it
 
257
        TODO: Perhaps return the ids of the files?  But then again it
235
258
               is easy to retrieve them if they're needed.
236
259
 
237
 
        :todo: Option to specify file id.
 
260
        TODO: Option to specify file id.
238
261
 
239
 
        :todo: Adding a directory should optionally recurse down and
 
262
        TODO: Adding a directory should optionally recurse down and
240
263
               add all non-ignored children.  Perhaps do that in a
241
264
               higher-level method.
242
265
 
315
338
 
316
339
        This does not remove their text.  This does not run on 
317
340
 
318
 
        :todo: Refuse to remove modified files unless --force is given?
 
341
        TODO: Refuse to remove modified files unless --force is given?
319
342
 
320
343
        >>> b = ScratchBranch(files=['foo'])
321
344
        >>> b.add('foo')
339
362
        >>> b.working_tree().has_filename('foo') 
340
363
        True
341
364
 
342
 
        :todo: Do something useful with directories.
 
365
        TODO: Do something useful with directories.
343
366
 
344
 
        :todo: Should this remove the text or not?  Tough call; not
 
367
        TODO: Should this remove the text or not?  Tough call; not
345
368
        removing may be useful and the user can just use use rm, and
346
369
        is the opposite of add.  Removing it is consistent with most
347
370
        other tools.  Maybe an option.
411
434
        be robust against files disappearing, moving, etc.  So the
412
435
        whole thing is a bit hard.
413
436
 
414
 
        :param timestamp: if not None, seconds-since-epoch for a
 
437
        timestamp -- if not None, seconds-since-epoch for a
415
438
             postdated/predated commit.
416
439
        """
417
440
 
555
578
        ## TODO: Also calculate and store the inventory SHA1
556
579
        mutter("committing patch r%d" % (self.revno() + 1))
557
580
 
558
 
        mutter("append to revision-history")
559
 
        f = self.controlfile('revision-history', 'at')
560
 
        f.write(rev_id + '\n')
561
 
        f.close()
562
581
 
 
582
        self.append_revision(rev_id)
 
583
        
563
584
        if verbose:
564
585
            note("commited r%d" % self.revno())
565
586
 
566
587
 
 
588
    def append_revision(self, revision_id):
 
589
        mutter("add {%s} to revision-history" % revision_id)
 
590
        rev_history = self.revision_history()
 
591
 
 
592
        tmprhname = self.controlfilename('revision-history.tmp')
 
593
        rhname = self.controlfilename('revision-history')
 
594
        
 
595
        f = file(tmprhname, 'wt')
 
596
        rev_history.append(revision_id)
 
597
        f.write('\n'.join(rev_history))
 
598
        f.write('\n')
 
599
        f.close()
 
600
 
 
601
        if sys.platform == 'win32':
 
602
            os.remove(rhname)
 
603
        os.rename(tmprhname, rhname)
 
604
        
 
605
 
 
606
 
567
607
    def get_revision(self, revision_id):
568
608
        """Return the Revision object for a named revision"""
569
609
        r = Revision.read_xml(self.revision_store[revision_id])
574
614
    def get_inventory(self, inventory_id):
575
615
        """Get Inventory object by hash.
576
616
 
577
 
        :todo: Perhaps for this and similar methods, take a revision
 
617
        TODO: Perhaps for this and similar methods, take a revision
578
618
               parameter which can be either an integer revno or a
579
619
               string hash."""
580
620
        i = Inventory.read_xml(self.inventory_store[inventory_id])
595
635
        >>> ScratchBranch().revision_history()
596
636
        []
597
637
        """
598
 
        return [chomp(l) for l in self.controlfile('revision-history').readlines()]
 
638
        return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
599
639
 
600
640
 
601
641
    def revno(self):
680
720
 
681
721
 
682
722
 
683
 
    def write_log(self, show_timezone='original'):
 
723
    def write_log(self, show_timezone='original', verbose=False):
684
724
        """Write out human-readable log of commits to this branch
685
725
 
686
 
        :param utc: If true, show dates in universal time, not local time."""
 
726
        utc -- If true, show dates in universal time, not local time."""
687
727
        ## TODO: Option to choose either original, utc or local timezone
688
728
        revno = 1
689
729
        precursor = None
708
748
                for l in rev.message.split('\n'):
709
749
                    print '  ' + l
710
750
 
 
751
            if verbose == True and precursor != None:
 
752
                print 'changed files:'
 
753
                tree = self.revision_tree(p)
 
754
                prevtree = self.revision_tree(precursor)
 
755
                
 
756
                for file_state, fid, old_name, new_name, kind in \
 
757
                                        diff_trees(prevtree, tree, ):
 
758
                    if file_state == 'A' or file_state == 'M':
 
759
                        show_status(file_state, kind, new_name)
 
760
                    elif file_state == 'D':
 
761
                        show_status(file_state, kind, old_name)
 
762
                    elif file_state == 'R':
 
763
                        show_status(file_state, kind,
 
764
                            old_name + ' => ' + new_name)
 
765
                
711
766
            revno += 1
712
767
            precursor = p
713
768
 
714
769
 
715
770
    def rename_one(self, from_rel, to_rel):
 
771
        """Rename one file.
 
772
 
 
773
        This can change the directory or the filename or both.
 
774
         """
716
775
        tree = self.working_tree()
717
776
        inv = tree.inventory
718
777
        if not tree.has_filename(from_rel):
834
893
        >>> b.show_status()
835
894
        D       foo
836
895
        
837
 
 
838
 
        :todo: Get state for single files.
839
 
 
840
 
        :todo: Perhaps show a slash at the end of directory names.        
841
 
 
 
896
        TODO: Get state for single files.
842
897
        """
843
898
 
844
899
        # We have to build everything into a list first so that it can
870
925
            elif fs == '?':
871
926
                show_status(fs, kind, newname)
872
927
            else:
873
 
                bailout("wierd file state %r" % ((fs, fid),))
 
928
                bailout("weird file state %r" % ((fs, fid),))
874
929
                
875
930
 
876
931
 
949
1004
    idx = name.rfind('/')
950
1005
    if idx != -1:
951
1006
        name = name[idx+1 : ]
 
1007
    idx = name.rfind('\\')
 
1008
    if idx != -1:
 
1009
        name = name[idx+1 : ]
952
1010
 
953
1011
    name = name.lstrip('.')
954
1012
 
955
1013
    s = hexlify(rand_bytes(8))
956
1014
    return '-'.join((name, compact_date(time.time()), s))
957
 
 
958