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):
226
159
That is to say, the inventory describing changes underway, that
227
160
will be committed to the next revision.
229
## TODO: factor out to atomicfile? is rename safe on windows?
230
## TODO: Maybe some kind of clean/dirty marker on inventory?
231
tmpfname = self.controlfilename('inventory.tmp')
232
tmpf = file(tmpfname, 'wb')
235
inv_fname = self.controlfilename('inventory')
236
if sys.platform == 'win32':
238
os.rename(tmpfname, inv_fname)
239
mutter('wrote working inventory')
162
inv.write_xml(self.controlfile('inventory', 'w'))
163
mutter('wrote inventory to %s' % quotefn(self.controlfilename('inventory')))
242
166
inventory = property(read_working_inventory, _write_inventory, None,
302
224
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)
226
fullpath = os.path.normpath(self._rel(f))
230
elif isdir(fullpath):
233
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
236
parent_name = joinpath(fp[:-1])
237
mutter("lookup parent %r" % parent_name)
238
parent_id = inv.path2id(parent_name)
239
if parent_id == None:
240
bailout("cannot add: parent %r is not versioned"
245
file_id = _gen_file_id(fp[-1])
246
inv.add(InventoryEntry(file_id, fp[-1], kind=kind, parent_id=parent_id))
319
248
show_status('A', kind, quotefn(f))
321
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
250
mutter("add file %s file_id:{%s} kind=%r parent_id={%s}"
251
% (f, file_id, kind, parent_id))
323
252
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
256
def remove(self, files, verbose=False):
337
257
"""Mark nominated files for removal from the inventory.
339
259
This does not remove their text. This does not run on
341
TODO: Refuse to remove modified files unless --force is given?
261
:todo: Refuse to remove modified files unless --force is given?
343
263
>>> b = ScratchBranch(files=['foo'])
578
492
## TODO: Also calculate and store the inventory SHA1
579
493
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)
495
mutter("append to revision-history")
496
self.controlfile('revision-history', 'at').write(rev_id + '\n')
607
501
def get_revision(self, revision_id):
748
639
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):
647
def show_status(branch, show_all=False):
880
648
"""Display single-line status for non-ignored working files.
882
650
The list is show sorted in order by file name.