173
173
def __init__(self, base, init=False, find_root=True):
174
174
"""Create new branch object at a particular location.
176
base -- Base directory for the branch.
176
base -- Base directory for the branch. May be a file:// url.
178
178
init -- If True, create new control files in a previously
179
179
unversioned directory. If False, the branch must already
193
193
self.base = find_branch_root(base)
195
if base.startswith("file://"):
195
197
self.base = os.path.realpath(base)
196
198
if not isdir(self.controlfilename('.')):
197
199
from errors import NotBranchError
262
259
self._lock = None
263
260
self._lock_mode = self._lock_count = None
266
262
def abspath(self, name):
267
263
"""Return absolute filename for something in the branch"""
268
264
return os.path.join(self.base, name)
271
266
def relpath(self, path):
272
267
"""Return path relative to this branch of something inside it.
274
269
Raises an error if path is not in this branch."""
275
270
return _relpath(self.base, path)
278
272
def controlfilename(self, file_or_path):
279
273
"""Return location relative to branch."""
280
274
if isinstance(file_or_path, basestring):
414
405
"""Inventory for the working copy.""")
417
def add(self, files, verbose=False, ids=None):
408
def add(self, files, ids=None):
418
409
"""Make files versioned.
420
Note that the command line normally calls smart_add instead.
411
Note that the command line normally calls smart_add instead,
412
which can automatically recurse.
422
414
This puts the files in the Added state, so that they will be
423
415
recorded by the next commit.
433
425
TODO: Perhaps have an option to add the ids even if the files do
436
TODO: Perhaps return the ids of the files? But then again it
437
is easy to retrieve them if they're needed.
439
TODO: Adding a directory should optionally recurse down and
440
add all non-ignored children. Perhaps do that in a
428
TODO: Perhaps yield the ids and paths as they're added.
443
430
# TODO: Re-adding a file that is removed in the working copy
444
431
# should probably put it back with the previous ID.
480
467
file_id = gen_file_id(f)
481
468
inv.add_path(f, kind=kind, file_id=file_id)
484
print 'added', quotefn(f)
486
470
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
488
472
self._write_inventory(inv)
835
816
self.append_revision(*revision_ids)
836
817
## note("Added %d revisions." % count)
839
820
def install_revisions(self, other, revision_ids, pb):
840
821
if hasattr(other.revision_store, "prefetch"):
841
822
other.revision_store.prefetch(revision_ids)
873
854
count, cp_fail = self.text_store.copy_multi(other.text_store,
875
print "Added %d texts." % count
856
#print "Added %d texts." % count
876
857
inventory_ids = [ f.inventory_id for f in revisions ]
877
858
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
879
print "Added %d inventories." % count
860
#print "Added %d inventories." % count
880
861
revision_ids = [ f.revision_id for f in revisions]
882
863
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
894
875
def lookup_revision(self, revision):
895
876
"""Return the revision identifier for a given revision information."""
896
revno, info = self.get_revision_info(revision)
877
revno, info = self._get_revision_info(revision)
914
895
revision can also be a string, in which case it is parsed for something like
915
896
'date:' or 'revid:' etc.
898
revno, rev_id = self._get_revision_info(revision)
900
raise bzrlib.errors.NoSuchRevision(self, revision)
903
def get_rev_id(self, revno, history=None):
904
"""Find the revision id of the specified revno."""
908
history = self.revision_history()
909
elif revno <= 0 or revno > len(history):
910
raise bzrlib.errors.NoSuchRevision(self, revno)
911
return history[revno - 1]
913
def _get_revision_info(self, revision):
914
"""Return (revno, revision id) for revision specifier.
916
revision can be an integer, in which case it is assumed to be revno
917
(though this will translate negative values into positive ones)
918
revision can also be a string, in which case it is parsed for something
919
like 'date:' or 'revid:' etc.
921
A revid is always returned. If it is None, the specifier referred to
922
the null revision. If the revid does not occur in the revision
923
history, revno will be None.
917
926
if revision is None:
924
933
revs = self.revision_history()
925
934
if isinstance(revision, int):
928
# Mabye we should do this first, but we don't need it if revision == 0
930
936
revno = len(revs) + revision + 1
939
rev_id = self.get_rev_id(revno, revs)
933
940
elif isinstance(revision, basestring):
934
941
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
935
942
if revision.startswith(prefix):
936
revno = func(self, revs, revision)
943
result = func(self, revs, revision)
945
revno, rev_id = result
948
rev_id = self.get_rev_id(revno, revs)
939
raise BzrError('No namespace registered for string: %r' % revision)
951
raise BzrError('No namespace registered for string: %r' %
954
raise TypeError('Unhandled revision type %s' % revision)
941
if revno is None or revno <= 0 or revno > len(revs):
942
raise BzrError("no such revision %s" % revision)
943
return revno, revs[revno-1]
958
raise bzrlib.errors.NoSuchRevision(self, revision)
945
961
def _namespace_revno(self, revs, revision):
946
962
"""Lookup a revision by revision number"""
947
963
assert revision.startswith('revno:')
949
return int(revision[6:])
965
return (int(revision[6:]),)
950
966
except ValueError:
952
968
REVISION_NAMESPACES['revno:'] = _namespace_revno
954
970
def _namespace_revid(self, revs, revision):
955
971
assert revision.startswith('revid:')
972
rev_id = revision[len('revid:'):]
957
return revs.index(revision[6:]) + 1
974
return revs.index(rev_id) + 1, rev_id
958
975
except ValueError:
960
977
REVISION_NAMESPACES['revid:'] = _namespace_revid
962
979
def _namespace_last(self, revs, revision):
965
982
offset = int(revision[5:])
966
983
except ValueError:
970
987
raise BzrError('You must supply a positive value for --revision last:XXX')
971
return len(revs) - offset + 1
988
return (len(revs) - offset + 1,)
972
989
REVISION_NAMESPACES['last:'] = _namespace_last
974
991
def _namespace_tag(self, revs, revision):
1049
1066
# TODO: Handle timezone.
1050
1067
dt = datetime.datetime.fromtimestamp(r.timestamp)
1051
1068
if first >= dt and (last is None or dt >= last):
1054
1071
for i in range(len(revs)):
1055
1072
r = self.get_revision(revs[i])
1056
1073
# TODO: Handle timezone.
1057
1074
dt = datetime.datetime.fromtimestamp(r.timestamp)
1058
1075
if first <= dt and (last is None or dt <= last):
1060
1077
REVISION_NAMESPACES['date:'] = _namespace_date
1062
1079
def revision_tree(self, revision_id):
1128
1145
inv.rename(file_id, to_dir_id, to_tail)
1130
print "%s => %s" % (from_rel, to_rel)
1132
1147
from_abs = self.abspath(from_rel)
1133
1148
to_abs = self.abspath(to_rel)
1154
1169
Note that to_name is only the last component of the new name;
1155
1170
this doesn't change the directory.
1172
This returns a list of (from_path, to_path) pairs for each
1173
entry that is moved.
1157
1176
self.lock_write()
1159
1178
## TODO: Option to move IDs only
1194
1213
for f in from_paths:
1195
1214
name_tail = splitpath(f)[-1]
1196
1215
dest_path = appendpath(to_name, name_tail)
1197
print "%s => %s" % (f, dest_path)
1216
result.append((f, dest_path))
1198
1217
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1200
1219
os.rename(self.abspath(f), self.abspath(dest_path))
1317
def get_parent(self):
1318
"""Return the parent location of the branch.
1320
This is the default location for push/pull/missing. The usual
1321
pattern is that the user can override it by specifying a
1325
_locs = ['parent', 'pull', 'x-pull']
1328
return self.controlfile(l, 'r').read().strip('\n')
1330
if e.errno != errno.ENOENT:
1335
def set_parent(self, url):
1336
# TODO: Maybe delete old location files?
1337
from bzrlib.atomicfile import AtomicFile
1340
f = AtomicFile(self.controlfilename('parent'))
1349
def check_revno(self, revno):
1351
Check whether a revno corresponds to any revision.
1352
Zero (the NULL revision) is considered valid.
1355
self.check_real_revno(revno)
1357
def check_real_revno(self, revno):
1359
Check whether a revno corresponds to a real revision.
1360
Zero (the NULL revision) is considered invalid
1362
if revno < 1 or revno > self.revno():
1363
raise InvalidRevisionNumber(revno)
1297
1368
class ScratchBranch(Branch):
1298
1369
"""Special test class: a branch that cleans up after itself.
1415
1488
"""Return a new tree-root file id."""
1416
1489
return gen_file_id('TREE_ROOT')
1492
def pull_loc(branch):
1493
# TODO: Should perhaps just make attribute be 'base' in
1494
# RemoteBranch and Branch?
1495
if hasattr(branch, "baseurl"):
1496
return branch.baseurl
1501
def copy_branch(branch_from, to_location, revision=None):
1502
"""Copy branch_from into the existing directory to_location.
1505
If not None, only revisions up to this point will be copied.
1506
The head of the new branch will be that revision.
1509
The name of a local directory that exists but is empty.
1511
from bzrlib.merge import merge
1512
from bzrlib.branch import Branch
1514
assert isinstance(branch_from, Branch)
1515
assert isinstance(to_location, basestring)
1517
br_to = Branch(to_location, init=True)
1518
br_to.set_root_id(branch_from.get_root_id())
1519
if revision is None:
1520
revno = branch_from.revno()
1522
revno, rev_id = branch_from.get_revision_info(revision)
1523
br_to.update_revisions(branch_from, stop_revision=revno)
1524
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1525
check_clean=False, ignore_zero=True)
1527
from_location = pull_loc(branch_from)
1528
br_to.set_parent(pull_loc(branch_from))