29
28
from inventory import InventoryEntry, Inventory
30
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, chomp, \
31
30
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
32
joinpath, sha_string, file_kind, local_time_offset
31
joinpath, sha_string, file_kind, local_time_offset, appendpath
33
32
from store import ImmutableStore
34
33
from revision import Revision
35
from errors import bailout
34
from errors import bailout, BzrError
36
35
from textui import show_status
37
36
from diff import diff_trees
50
74
"""Branch holding a history of revisions.
52
:todo: Perhaps use different stores for different classes of object,
76
TODO: Perhaps use different stores for different classes of object,
53
77
so that we can keep track of how much space each one uses,
54
78
or garbage-collect them.
56
: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
57
81
HTTP access this should be very easy by,
58
82
just redirecting controlfile access into HTTP requests.
59
83
We would need a RemoteStore working similarly.
61
:todo: Keep the on-disk branch locked while the object exists.
85
TODO: Keep the on-disk branch locked while the object exists.
63
:todo: mkdir() method.
65
def __init__(self, base, init=False):
89
def __init__(self, base, init=False, find_root=True):
66
90
"""Create new branch object at a particular location.
68
:param base: Base directory for the branch.
70
:param init: If True, create new control files in a previously
92
base -- Base directory for the branch.
94
init -- If True, create new control files in a previously
71
95
unversioned directory. If False, the branch must already
98
find_root -- If true and init is false, find the root of the
99
existing branch containing base.
74
101
In the test suite, creation of new trees is tested using the
75
102
`ScratchBranch` class.
77
self.base = os.path.realpath(base)
105
self.base = os.path.realpath(base)
79
106
self._make_control()
108
self.base = find_branch_root(base)
110
self.base = os.path.realpath(base)
81
111
if not isdir(self.controlfilename('.')):
82
112
bailout("not a bzr branch: %s" % quotefn(base),
83
113
['use "bzr init" to initialize a new working tree',
84
114
'current bzr can only operate from top-of-tree'])
87
117
self.text_store = ImmutableStore(self.controlfilename('text-store'))
88
118
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
111
154
def controlfile(self, file_or_path, mode='r'):
112
"""Open a control file for this branch"""
113
return file(self.controlfilename(file_or_path), mode)
155
"""Open a control file for this branch.
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.
163
fn = self.controlfilename(file_or_path)
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.
171
return codecs.open(fn, mode + 'b', 'utf-8',
174
raise BzrError("invalid controlfile mode %r" % mode)
116
178
def _make_control(self):
229
302
bailout("cannot add top-level %r" % f)
231
fullpath = os.path.normpath(self._rel(f))
235
elif isdir(fullpath):
238
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
241
parent_name = joinpath(fp[:-1])
242
mutter("lookup parent %r" % parent_name)
243
parent_id = inv.path2id(parent_name)
244
if parent_id == None:
245
bailout("cannot add: parent %r is not versioned"
250
file_id = _gen_file_id(fp[-1])
251
inv.add(InventoryEntry(file_id, fp[-1], kind=kind, parent_id=parent_id))
304
fullpath = os.path.normpath(self.abspath(f))
307
kind = file_kind(fullpath)
309
# maybe something better?
310
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
312
if kind != 'file' and kind != 'directory':
313
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
315
file_id = gen_file_id(f)
316
inv.add_path(f, kind=kind, file_id=file_id)
253
319
show_status('A', kind, quotefn(f))
255
mutter("add file %s file_id:{%s} kind=%r parent_id={%s}"
256
% (f, file_id, kind, parent_id))
321
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
257
323
self._write_inventory(inv)
326
def print_file(self, file, revno):
327
"""Print `file` to stdout."""
328
tree = self.revision_tree(self.lookup_revision(revno))
329
# use inventory as it was in that revision
330
file_id = tree.inventory.path2id(file)
332
bailout("%r is not present in revision %d" % (file, revno))
333
tree.print_file(file_id)
261
336
def remove(self, files, verbose=False):
262
337
"""Mark nominated files for removal from the inventory.
264
339
This does not remove their text. This does not run on
266
:todo: Refuse to remove modified files unless --force is given?
341
TODO: Refuse to remove modified files unless --force is given?
268
343
>>> b = ScratchBranch(files=['foo'])
503
578
## TODO: Also calculate and store the inventory SHA1
504
579
mutter("committing patch r%d" % (self.revno() + 1))
506
mutter("append to revision-history")
507
self.controlfile('revision-history', 'at').write(rev_id + '\n')
582
self.append_revision(rev_id)
585
note("commited r%d" % self.revno())
588
def append_revision(self, revision_id):
589
mutter("add {%s} to revision-history" % revision_id)
590
rev_history = self.revision_history()
592
tmprhname = self.controlfilename('revision-history.tmp')
593
rhname = self.controlfilename('revision-history')
595
f = file(tmprhname, 'wt')
596
rev_history.append(revision_id)
597
f.write('\n'.join(rev_history))
601
if sys.platform == 'win32':
603
os.rename(tmprhname, rhname)
512
607
def get_revision(self, revision_id):
651
748
for l in rev.message.split('\n'):
751
if verbose == True and precursor != None:
752
print 'changed files:'
753
tree = self.revision_tree(p)
754
prevtree = self.revision_tree(precursor)
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)
659
def show_status(branch, show_all=False):
770
def rename_one(self, from_rel, to_rel):
773
This can change the directory or the filename or both.
775
tree = self.working_tree()
777
if not tree.has_filename(from_rel):
778
bailout("can't rename: old working file %r does not exist" % from_rel)
779
if tree.has_filename(to_rel):
780
bailout("can't rename: new working file %r already exists" % to_rel)
782
file_id = inv.path2id(from_rel)
784
bailout("can't rename: old name %r is not versioned" % from_rel)
786
if inv.path2id(to_rel):
787
bailout("can't rename: new name %r is already versioned" % to_rel)
789
to_dir, to_tail = os.path.split(to_rel)
790
to_dir_id = inv.path2id(to_dir)
791
if to_dir_id == None and to_dir != '':
792
bailout("can't determine destination directory id for %r" % to_dir)
794
mutter("rename_one:")
795
mutter(" file_id {%s}" % file_id)
796
mutter(" from_rel %r" % from_rel)
797
mutter(" to_rel %r" % to_rel)
798
mutter(" to_dir %r" % to_dir)
799
mutter(" to_dir_id {%s}" % to_dir_id)
801
inv.rename(file_id, to_dir_id, to_tail)
803
print "%s => %s" % (from_rel, to_rel)
805
from_abs = self.abspath(from_rel)
806
to_abs = self.abspath(to_rel)
808
os.rename(from_abs, to_abs)
810
bailout("failed to rename %r to %r: %s"
811
% (from_abs, to_abs, e[1]),
812
["rename rolled back"])
814
self._write_inventory(inv)
818
def move(self, from_paths, to_name):
821
to_name must exist as a versioned directory.
823
If to_name exists and is a directory, the files are moved into
824
it, keeping their old names. If it is a directory,
826
Note that to_name is only the last component of the new name;
827
this doesn't change the directory.
829
## TODO: Option to move IDs only
830
assert not isinstance(from_paths, basestring)
831
tree = self.working_tree()
833
to_abs = self.abspath(to_name)
834
if not isdir(to_abs):
835
bailout("destination %r is not a directory" % to_abs)
836
if not tree.has_filename(to_name):
837
bailout("destination %r not in working directory" % to_abs)
838
to_dir_id = inv.path2id(to_name)
839
if to_dir_id == None and to_name != '':
840
bailout("destination %r is not a versioned directory" % to_name)
841
to_dir_ie = inv[to_dir_id]
842
if to_dir_ie.kind not in ('directory', 'root_directory'):
843
bailout("destination %r is not a directory" % to_abs)
845
to_idpath = Set(inv.get_idpath(to_dir_id))
848
if not tree.has_filename(f):
849
bailout("%r does not exist in working tree" % f)
850
f_id = inv.path2id(f)
852
bailout("%r is not versioned" % f)
853
name_tail = splitpath(f)[-1]
854
dest_path = appendpath(to_name, name_tail)
855
if tree.has_filename(dest_path):
856
bailout("destination %r already exists" % dest_path)
857
if f_id in to_idpath:
858
bailout("can't move %r to a subdirectory of itself" % f)
860
# OK, so there's a race here, it's possible that someone will
861
# create a file in this interval and then the rename might be
862
# left half-done. But we should have caught most problems.
865
name_tail = splitpath(f)[-1]
866
dest_path = appendpath(to_name, name_tail)
867
print "%s => %s" % (f, dest_path)
868
inv.rename(inv.path2id(f), to_dir_id, name_tail)
870
os.rename(self.abspath(f), self.abspath(dest_path))
872
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
873
["rename rolled back"])
875
self._write_inventory(inv)
879
def show_status(self, show_all=False):
660
880
"""Display single-line status for non-ignored working files.
662
882
The list is show sorted in order by file name.