28
29
from inventory import InventoryEntry, Inventory
29
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):
771
tree = self.working_tree()
773
if not tree.has_filename(from_rel):
774
bailout("can't rename: old working file %r does not exist" % from_rel)
775
if tree.has_filename(to_rel):
776
bailout("can't rename: new working file %r already exists" % to_rel)
778
file_id = inv.path2id(from_rel)
780
bailout("can't rename: old name %r is not versioned" % from_rel)
782
if inv.path2id(to_rel):
783
bailout("can't rename: new name %r is already versioned" % to_rel)
785
to_dir, to_tail = os.path.split(to_rel)
786
to_dir_id = inv.path2id(to_dir)
787
if to_dir_id == None and to_dir != '':
788
bailout("can't determine destination directory id for %r" % to_dir)
790
mutter("rename_one:")
791
mutter(" file_id {%s}" % file_id)
792
mutter(" from_rel %r" % from_rel)
793
mutter(" to_rel %r" % to_rel)
794
mutter(" to_dir %r" % to_dir)
795
mutter(" to_dir_id {%s}" % to_dir_id)
797
inv.rename(file_id, to_dir_id, to_tail)
799
print "%s => %s" % (from_rel, to_rel)
801
from_abs = self.abspath(from_rel)
802
to_abs = self.abspath(to_rel)
804
os.rename(from_abs, to_abs)
806
bailout("failed to rename %r to %r: %s"
807
% (from_abs, to_abs, e[1]),
808
["rename rolled back"])
810
self._write_inventory(inv)
814
def move(self, from_paths, to_name):
817
to_name must exist as a versioned directory.
819
If to_name exists and is a directory, the files are moved into
820
it, keeping their old names. If it is a directory,
822
Note that to_name is only the last component of the new name;
823
this doesn't change the directory.
825
## TODO: Option to move IDs only
826
assert not isinstance(from_paths, basestring)
827
tree = self.working_tree()
829
to_abs = self.abspath(to_name)
830
if not isdir(to_abs):
831
bailout("destination %r is not a directory" % to_abs)
832
if not tree.has_filename(to_name):
833
bailout("destination %r not in working directory" % to_abs)
834
to_dir_id = inv.path2id(to_name)
835
if to_dir_id == None and to_name != '':
836
bailout("destination %r is not a versioned directory" % to_name)
837
to_dir_ie = inv[to_dir_id]
838
if to_dir_ie.kind not in ('directory', 'root_directory'):
839
bailout("destination %r is not a directory" % to_abs)
841
to_idpath = Set(inv.get_idpath(to_dir_id))
844
if not tree.has_filename(f):
845
bailout("%r does not exist in working tree" % f)
846
f_id = inv.path2id(f)
848
bailout("%r is not versioned" % f)
849
name_tail = splitpath(f)[-1]
850
dest_path = appendpath(to_name, name_tail)
851
if tree.has_filename(dest_path):
852
bailout("destination %r already exists" % dest_path)
853
if f_id in to_idpath:
854
bailout("can't move %r to a subdirectory of itself" % f)
856
# OK, so there's a race here, it's possible that someone will
857
# create a file in this interval and then the rename might be
858
# left half-done. But we should have caught most problems.
861
name_tail = splitpath(f)[-1]
862
dest_path = appendpath(to_name, name_tail)
863
print "%s => %s" % (f, dest_path)
864
inv.rename(inv.path2id(f), to_dir_id, name_tail)
866
os.rename(self.abspath(f), self.abspath(dest_path))
868
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
869
["rename rolled back"])
871
self._write_inventory(inv)
875
def show_status(self, show_all=False):
647
def show_status(branch, show_all=False):
876
648
"""Display single-line status for non-ignored working files.
878
650
The list is show sorted in order by file name.