26
27
from trace import mutter, note
27
28
from tree import Tree, EmptyTree, RevisionTree, WorkingTree
28
29
from inventory import InventoryEntry, Inventory
29
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
30
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, chomp, \
30
31
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
31
joinpath, sha_string, file_kind, local_time_offset, appendpath
32
joinpath, sha_string, file_kind, local_time_offset
32
33
from store import ImmutableStore
33
34
from revision import Revision
34
from errors import bailout, BzrError
35
from errors import bailout
35
36
from textui import show_status
36
37
from diff import diff_trees
74
50
"""Branch holding a history of revisions.
76
TODO: Perhaps use different stores for different classes of object,
52
:todo: Perhaps use different stores for different classes of object,
77
53
so that we can keep track of how much space each one uses,
78
54
or garbage-collect them.
80
TODO: Add a RemoteBranch subclass. For the basic case of read-only
56
:todo: Add a RemoteBranch subclass. For the basic case of read-only
81
57
HTTP access this should be very easy by,
82
58
just redirecting controlfile access into HTTP requests.
83
59
We would need a RemoteStore working similarly.
85
TODO: Keep the on-disk branch locked while the object exists.
61
:todo: Keep the on-disk branch locked while the object exists.
63
:todo: mkdir() method.
89
def __init__(self, base, init=False, find_root=True):
65
def __init__(self, base, init=False):
90
66
"""Create new branch object at a particular location.
92
base -- Base directory for the branch.
94
init -- If True, create new control files in a previously
68
:param base: Base directory for the branch.
70
:param init: If True, create new control files in a previously
95
71
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.
101
74
In the test suite, creation of new trees is tested using the
102
75
`ScratchBranch` class.
77
self.base = os.path.realpath(base)
105
self.base = os.path.realpath(base)
106
79
self._make_control()
108
self.base = find_branch_root(base)
110
self.base = os.path.realpath(base)
111
81
if not isdir(self.controlfilename('.')):
112
82
bailout("not a bzr branch: %s" % quotefn(base),
113
83
['use "bzr init" to initialize a new working tree',
114
84
'current bzr can only operate from top-of-tree'])
117
87
self.text_store = ImmutableStore(self.controlfilename('text-store'))
118
88
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
154
111
def controlfile(self, file_or_path, mode='r'):
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)
112
"""Open a control file for this branch"""
113
return file(self.controlfilename(file_or_path), mode)
178
116
def _make_control(self):
302
229
bailout("cannot add top-level %r" % f)
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)
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))
319
253
show_status('A', kind, quotefn(f))
321
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
255
mutter("add file %s file_id:{%s} kind=%r parent_id={%s}"
256
% (f, file_id, kind, parent_id))
323
257
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)
336
261
def remove(self, files, verbose=False):
337
262
"""Mark nominated files for removal from the inventory.
339
264
This does not remove their text. This does not run on
341
TODO: Refuse to remove modified files unless --force is given?
266
:todo: Refuse to remove modified files unless --force is given?
343
268
>>> b = ScratchBranch(files=['foo'])
578
497
## TODO: Also calculate and store the inventory SHA1
579
498
mutter("committing patch r%d" % (self.revno() + 1))
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)
500
mutter("append to revision-history")
501
self.controlfile('revision-history', 'at').write(rev_id + '\n')
607
506
def get_revision(self, revision_id):
748
645
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)
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):
653
def show_status(branch, show_all=False):
880
654
"""Display single-line status for non-ignored working files.
882
656
The list is show sorted in order by file name.