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
joinpath, sha_string, file_kind, local_time_offset
31
joinpath, sha_string, file_kind, local_time_offset, appendpath
32
32
from store import ImmutableStore
33
33
from revision import Revision
34
from errors import bailout
34
from errors import bailout, BzrError
35
35
from textui import show_status
36
36
from diff import diff_trees
75
84
"""Branch holding a history of revisions.
77
:todo: Perhaps use different stores for different classes of object,
78
so that we can keep track of how much space each one uses,
79
or garbage-collect them.
81
:todo: Add a RemoteBranch subclass. For the basic case of read-only
82
HTTP access this should be very easy by,
83
just redirecting controlfile access into HTTP requests.
84
We would need a RemoteStore working similarly.
86
:todo: Keep the on-disk branch locked while the object exists.
88
:todo: mkdir() method.
87
Base directory of the branch.
90
def __init__(self, base, init=False, find_root=True):
91
def __init__(self, base, init=False, find_root=True, lock_mode='w'):
91
92
"""Create new branch object at a particular location.
93
:param base: Base directory for the branch.
94
base -- Base directory for the branch.
95
:param init: If True, create new control files in a previously
96
init -- If True, create new control files in a previously
96
97
unversioned directory. If False, the branch must already
99
:param find_root: If true and init is false, find the root of the
100
find_root -- If true and init is false, find the root of the
100
101
existing branch containing base.
102
103
In the test suite, creation of new trees is tested using the
127
129
__repr__ = __str__
133
def lock(self, mode='w'):
134
"""Lock the on-disk branch, excluding other processes."""
140
om = os.O_WRONLY | os.O_CREAT
145
raise BzrError("invalid locking mode %r" % mode)
148
lockfile = os.open(self.controlfilename('branch-lock'), om)
150
if e.errno == errno.ENOENT:
151
# might not exist on branches from <0.0.4
152
self.controlfile('branch-lock', 'w').close()
153
lockfile = os.open(self.controlfilename('branch-lock'), om)
157
fcntl.lockf(lockfile, lm)
159
fcntl.lockf(lockfile, fcntl.LOCK_UN)
161
self._lockmode = None
163
self._lockmode = mode
165
warning("please write a locking method for platform %r" % sys.platform)
167
self._lockmode = None
169
self._lockmode = mode
172
def _need_readlock(self):
173
if self._lockmode not in ['r', 'w']:
174
raise BzrError('need read lock on branch, only have %r' % self._lockmode)
176
def _need_writelock(self):
177
if self._lockmode not in ['w']:
178
raise BzrError('need write lock on branch, only have %r' % self._lockmode)
130
181
def abspath(self, name):
131
182
"""Return absolute filename for something in the branch"""
132
183
return os.path.join(self.base, name)
155
206
def controlfile(self, file_or_path, mode='r'):
156
"""Open a control file for this branch"""
157
return file(self.controlfilename(file_or_path), mode)
207
"""Open a control file for this branch.
209
There are two classes of file in the control directory: text
210
and binary. binary files are untranslated byte streams. Text
211
control files are stored with Unix newlines and in UTF-8, even
212
if the platform or locale defaults are different.
215
fn = self.controlfilename(file_or_path)
217
if mode == 'rb' or mode == 'wb':
218
return file(fn, mode)
219
elif mode == 'r' or mode == 'w':
220
# open in binary mode anyhow so there's no newline translation;
221
# codecs uses line buffering by default; don't want that.
223
return codecs.open(fn, mode + 'b', 'utf-8',
226
raise BzrError("invalid controlfile mode %r" % mode)
160
230
def _make_control(self):
541
637
## TODO: Also calculate and store the inventory SHA1
542
638
mutter("committing patch r%d" % (self.revno() + 1))
544
mutter("append to revision-history")
545
f = self.controlfile('revision-history', 'at')
546
f.write(rev_id + '\n')
641
self.append_revision(rev_id)
550
644
note("commited r%d" % self.revno())
647
def append_revision(self, revision_id):
648
mutter("add {%s} to revision-history" % revision_id)
649
rev_history = self.revision_history()
651
tmprhname = self.controlfilename('revision-history.tmp')
652
rhname = self.controlfilename('revision-history')
654
f = file(tmprhname, 'wt')
655
rev_history.append(revision_id)
656
f.write('\n'.join(rev_history))
660
if sys.platform == 'win32':
662
os.rename(tmprhname, rhname)
553
666
def get_revision(self, revision_id):
554
667
"""Return the Revision object for a named revision"""
668
self._need_readlock()
555
669
r = Revision.read_xml(self.revision_store[revision_id])
556
670
assert r.revision_id == revision_id
667
def write_log(self, show_timezone='original'):
668
"""Write out human-readable log of commits to this branch
670
:param utc: If true, show dates in universal time, not local time."""
671
## TODO: Option to choose either original, utc or local timezone
674
for p in self.revision_history():
676
print 'revno:', revno
677
## TODO: Show hash if --id is given.
678
##print 'revision-hash:', p
679
rev = self.get_revision(p)
680
print 'committer:', rev.committer
681
print 'timestamp: %s' % (format_date(rev.timestamp, rev.timezone or 0,
684
## opportunistic consistency check, same as check_patch_chaining
685
if rev.precursor != precursor:
686
bailout("mismatched precursor!")
690
print ' (no message)'
692
for l in rev.message.split('\n'):
700
def show_status(branch, show_all=False):
808
def rename_one(self, from_rel, to_rel):
811
This can change the directory or the filename or both.
813
self._need_writelock()
814
tree = self.working_tree()
816
if not tree.has_filename(from_rel):
817
bailout("can't rename: old working file %r does not exist" % from_rel)
818
if tree.has_filename(to_rel):
819
bailout("can't rename: new working file %r already exists" % to_rel)
821
file_id = inv.path2id(from_rel)
823
bailout("can't rename: old name %r is not versioned" % from_rel)
825
if inv.path2id(to_rel):
826
bailout("can't rename: new name %r is already versioned" % to_rel)
828
to_dir, to_tail = os.path.split(to_rel)
829
to_dir_id = inv.path2id(to_dir)
830
if to_dir_id == None and to_dir != '':
831
bailout("can't determine destination directory id for %r" % to_dir)
833
mutter("rename_one:")
834
mutter(" file_id {%s}" % file_id)
835
mutter(" from_rel %r" % from_rel)
836
mutter(" to_rel %r" % to_rel)
837
mutter(" to_dir %r" % to_dir)
838
mutter(" to_dir_id {%s}" % to_dir_id)
840
inv.rename(file_id, to_dir_id, to_tail)
842
print "%s => %s" % (from_rel, to_rel)
844
from_abs = self.abspath(from_rel)
845
to_abs = self.abspath(to_rel)
847
os.rename(from_abs, to_abs)
849
bailout("failed to rename %r to %r: %s"
850
% (from_abs, to_abs, e[1]),
851
["rename rolled back"])
853
self._write_inventory(inv)
857
def move(self, from_paths, to_name):
860
to_name must exist as a versioned directory.
862
If to_name exists and is a directory, the files are moved into
863
it, keeping their old names. If it is a directory,
865
Note that to_name is only the last component of the new name;
866
this doesn't change the directory.
868
self._need_writelock()
869
## TODO: Option to move IDs only
870
assert not isinstance(from_paths, basestring)
871
tree = self.working_tree()
873
to_abs = self.abspath(to_name)
874
if not isdir(to_abs):
875
bailout("destination %r is not a directory" % to_abs)
876
if not tree.has_filename(to_name):
877
bailout("destination %r not in working directory" % to_abs)
878
to_dir_id = inv.path2id(to_name)
879
if to_dir_id == None and to_name != '':
880
bailout("destination %r is not a versioned directory" % to_name)
881
to_dir_ie = inv[to_dir_id]
882
if to_dir_ie.kind not in ('directory', 'root_directory'):
883
bailout("destination %r is not a directory" % to_abs)
885
to_idpath = Set(inv.get_idpath(to_dir_id))
888
if not tree.has_filename(f):
889
bailout("%r does not exist in working tree" % f)
890
f_id = inv.path2id(f)
892
bailout("%r is not versioned" % f)
893
name_tail = splitpath(f)[-1]
894
dest_path = appendpath(to_name, name_tail)
895
if tree.has_filename(dest_path):
896
bailout("destination %r already exists" % dest_path)
897
if f_id in to_idpath:
898
bailout("can't move %r to a subdirectory of itself" % f)
900
# OK, so there's a race here, it's possible that someone will
901
# create a file in this interval and then the rename might be
902
# left half-done. But we should have caught most problems.
905
name_tail = splitpath(f)[-1]
906
dest_path = appendpath(to_name, name_tail)
907
print "%s => %s" % (f, dest_path)
908
inv.rename(inv.path2id(f), to_dir_id, name_tail)
910
os.rename(self.abspath(f), self.abspath(dest_path))
912
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
913
["rename rolled back"])
915
self._write_inventory(inv)
919
def show_status(self, show_all=False, file_list=None):
701
920
"""Display single-line status for non-ignored working files.
703
922
The list is show sorted in order by file name.
730
944
# Interesting case: the old ID for a file has been removed,
731
945
# but a new file has been created under that name.
733
old = branch.basis_tree()
734
old_inv = old.inventory
735
new = branch.working_tree()
736
new_inv = new.inventory
738
for fs, fid, oldname, newname, kind in diff_trees(old, new):
947
old = self.basis_tree()
948
new = self.working_tree()
950
items = diff_trees(old, new)
951
# We want to filter out only if any file was provided in the file_list.
952
if isinstance(file_list, list) and len(file_list):
953
items = [item for item in items if item[3] in file_list]
955
for fs, fid, oldname, newname, kind in items:
740
957
show_status(fs, kind,
741
958
oldname + ' => ' + newname)