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
43
def find_branch(f, **args):
44
if f.startswith('http://') or f.startswith('https://'):
46
return remotebranch.RemoteBranch(f, **args)
48
return Branch(f, **args)
43
51
def find_branch_root(f=None):
44
52
"""Find the branch root enclosing f, or pwd.
54
f may be a filename or a URL.
46
56
It is not necessary that f exists.
48
58
Basically we keep looking up until we find the control directory or
49
59
run into the root."""
52
62
elif hasattr(os.path, 'realpath'):
53
63
f = os.path.realpath(f)
55
65
f = os.path.abspath(f)
66
if not os.path.exists(f):
67
raise BzrError('%r does not exist' % f)
61
73
if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
63
75
head, tail = os.path.split(f)
65
77
# reached the root, whatever that may be
66
bailout('%r is not in a branch' % orig_f)
78
raise BzrError('%r is not in a branch' % orig_f)
75
87
"""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.
90
Base directory of the branch.
90
def __init__(self, base, init=False, find_root=True):
94
def __init__(self, base, init=False, find_root=True, lock_mode='w'):
91
95
"""Create new branch object at a particular location.
93
:param base: Base directory for the branch.
97
base -- Base directory for the branch.
95
:param init: If True, create new control files in a previously
99
init -- If True, create new control files in a previously
96
100
unversioned directory. If False, the branch must already
99
:param find_root: If true and init is false, find the root of the
103
find_root -- If true and init is false, find the root of the
100
104
existing branch containing base.
102
106
In the test suite, creation of new trees is tested using the
127
132
__repr__ = __str__
136
def lock(self, mode='w'):
137
"""Lock the on-disk branch, excluding other processes."""
143
om = os.O_WRONLY | os.O_CREAT
148
raise BzrError("invalid locking mode %r" % mode)
151
lockfile = os.open(self.controlfilename('branch-lock'), om)
153
if e.errno == errno.ENOENT:
154
# might not exist on branches from <0.0.4
155
self.controlfile('branch-lock', 'w').close()
156
lockfile = os.open(self.controlfilename('branch-lock'), om)
160
fcntl.lockf(lockfile, lm)
162
fcntl.lockf(lockfile, fcntl.LOCK_UN)
164
self._lockmode = None
166
self._lockmode = mode
168
warning("please write a locking method for platform %r" % sys.platform)
170
self._lockmode = None
172
self._lockmode = mode
175
def _need_readlock(self):
176
if self._lockmode not in ['r', 'w']:
177
raise BzrError('need read lock on branch, only have %r' % self._lockmode)
179
def _need_writelock(self):
180
if self._lockmode not in ['w']:
181
raise BzrError('need write lock on branch, only have %r' % self._lockmode)
130
184
def abspath(self, name):
131
185
"""Return absolute filename for something in the branch"""
132
186
return os.path.join(self.base, name)
155
209
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)
210
"""Open a control file for this branch.
212
There are two classes of file in the control directory: text
213
and binary. binary files are untranslated byte streams. Text
214
control files are stored with Unix newlines and in UTF-8, even
215
if the platform or locale defaults are different.
217
Controlfiles should almost never be opened in write mode but
218
rather should be atomically copied and replaced using atomicfile.
221
fn = self.controlfilename(file_or_path)
223
if mode == 'rb' or mode == 'wb':
224
return file(fn, mode)
225
elif mode == 'r' or mode == 'w':
226
# open in binary mode anyhow so there's no newline translation;
227
# codecs uses line buffering by default; don't want that.
229
return codecs.open(fn, mode + 'b', 'utf-8',
232
raise BzrError("invalid controlfile mode %r" % mode)
160
236
def _make_control(self):
541
643
## TODO: Also calculate and store the inventory SHA1
542
644
mutter("committing patch r%d" % (self.revno() + 1))
544
mutter("append to revision-history")
545
self.controlfile('revision-history', 'at').write(rev_id + '\n')
647
self.append_revision(rev_id)
650
note("commited r%d" % self.revno())
653
def append_revision(self, revision_id):
654
mutter("add {%s} to revision-history" % revision_id)
655
rev_history = self.revision_history()
657
tmprhname = self.controlfilename('revision-history.tmp')
658
rhname = self.controlfilename('revision-history')
660
f = file(tmprhname, 'wt')
661
rev_history.append(revision_id)
662
f.write('\n'.join(rev_history))
666
if sys.platform == 'win32':
668
os.rename(tmprhname, rhname)
550
672
def get_revision(self, revision_id):
551
673
"""Return the Revision object for a named revision"""
674
self._need_readlock()
552
675
r = Revision.read_xml(self.revision_store[revision_id])
553
676
assert r.revision_id == revision_id
664
def write_log(self, show_timezone='original'):
665
"""Write out human-readable log of commits to this branch
667
:param utc: If true, show dates in universal time, not local time."""
668
## TODO: Option to choose either original, utc or local timezone
671
for p in self.revision_history():
673
print 'revno:', revno
674
## TODO: Show hash if --id is given.
675
##print 'revision-hash:', p
676
rev = self.get_revision(p)
677
print 'committer:', rev.committer
678
print 'timestamp: %s' % (format_date(rev.timestamp, rev.timezone or 0,
681
## opportunistic consistency check, same as check_patch_chaining
682
if rev.precursor != precursor:
683
bailout("mismatched precursor!")
687
print ' (no message)'
689
for l in rev.message.split('\n'):
697
def show_status(branch, show_all=False):
814
def rename_one(self, from_rel, to_rel):
817
This can change the directory or the filename or both.
819
self._need_writelock()
820
tree = self.working_tree()
822
if not tree.has_filename(from_rel):
823
bailout("can't rename: old working file %r does not exist" % from_rel)
824
if tree.has_filename(to_rel):
825
bailout("can't rename: new working file %r already exists" % to_rel)
827
file_id = inv.path2id(from_rel)
829
bailout("can't rename: old name %r is not versioned" % from_rel)
831
if inv.path2id(to_rel):
832
bailout("can't rename: new name %r is already versioned" % to_rel)
834
to_dir, to_tail = os.path.split(to_rel)
835
to_dir_id = inv.path2id(to_dir)
836
if to_dir_id == None and to_dir != '':
837
bailout("can't determine destination directory id for %r" % to_dir)
839
mutter("rename_one:")
840
mutter(" file_id {%s}" % file_id)
841
mutter(" from_rel %r" % from_rel)
842
mutter(" to_rel %r" % to_rel)
843
mutter(" to_dir %r" % to_dir)
844
mutter(" to_dir_id {%s}" % to_dir_id)
846
inv.rename(file_id, to_dir_id, to_tail)
848
print "%s => %s" % (from_rel, to_rel)
850
from_abs = self.abspath(from_rel)
851
to_abs = self.abspath(to_rel)
853
os.rename(from_abs, to_abs)
855
bailout("failed to rename %r to %r: %s"
856
% (from_abs, to_abs, e[1]),
857
["rename rolled back"])
859
self._write_inventory(inv)
863
def move(self, from_paths, to_name):
866
to_name must exist as a versioned directory.
868
If to_name exists and is a directory, the files are moved into
869
it, keeping their old names. If it is a directory,
871
Note that to_name is only the last component of the new name;
872
this doesn't change the directory.
874
self._need_writelock()
875
## TODO: Option to move IDs only
876
assert not isinstance(from_paths, basestring)
877
tree = self.working_tree()
879
to_abs = self.abspath(to_name)
880
if not isdir(to_abs):
881
bailout("destination %r is not a directory" % to_abs)
882
if not tree.has_filename(to_name):
883
bailout("destination %r not in working directory" % to_abs)
884
to_dir_id = inv.path2id(to_name)
885
if to_dir_id == None and to_name != '':
886
bailout("destination %r is not a versioned directory" % to_name)
887
to_dir_ie = inv[to_dir_id]
888
if to_dir_ie.kind not in ('directory', 'root_directory'):
889
bailout("destination %r is not a directory" % to_abs)
891
to_idpath = Set(inv.get_idpath(to_dir_id))
894
if not tree.has_filename(f):
895
bailout("%r does not exist in working tree" % f)
896
f_id = inv.path2id(f)
898
bailout("%r is not versioned" % f)
899
name_tail = splitpath(f)[-1]
900
dest_path = appendpath(to_name, name_tail)
901
if tree.has_filename(dest_path):
902
bailout("destination %r already exists" % dest_path)
903
if f_id in to_idpath:
904
bailout("can't move %r to a subdirectory of itself" % f)
906
# OK, so there's a race here, it's possible that someone will
907
# create a file in this interval and then the rename might be
908
# left half-done. But we should have caught most problems.
911
name_tail = splitpath(f)[-1]
912
dest_path = appendpath(to_name, name_tail)
913
print "%s => %s" % (f, dest_path)
914
inv.rename(inv.path2id(f), to_dir_id, name_tail)
916
os.rename(self.abspath(f), self.abspath(dest_path))
918
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
919
["rename rolled back"])
921
self._write_inventory(inv)
925
def show_status(self, show_all=False, file_list=None):
698
926
"""Display single-line status for non-ignored working files.
700
928
The list is show sorted in order by file name.
727
950
# Interesting case: the old ID for a file has been removed,
728
951
# but a new file has been created under that name.
730
old = branch.basis_tree()
731
old_inv = old.inventory
732
new = branch.working_tree()
733
new_inv = new.inventory
735
for fs, fid, oldname, newname, kind in diff_trees(old, new):
953
old = self.basis_tree()
954
new = self.working_tree()
956
items = diff_trees(old, new)
957
# We want to filter out only if any file was provided in the file_list.
958
if isinstance(file_list, list) and len(file_list):
959
items = [item for item in items if item[3] in file_list]
961
for fs, fid, oldname, newname, kind in items:
737
963
show_status(fs, kind,
738
964
oldname + ' => ' + newname)