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. May be a file:// url.
176
base -- Base directory for the branch.
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://"):
197
195
self.base = os.path.realpath(base)
198
196
if not isdir(self.controlfilename('.')):
199
197
from errors import NotBranchError
259
262
self._lock = None
260
263
self._lock_mode = self._lock_count = None
262
266
def abspath(self, name):
263
267
"""Return absolute filename for something in the branch"""
264
268
return os.path.join(self.base, name)
266
271
def relpath(self, path):
267
272
"""Return path relative to this branch of something inside it.
269
274
Raises an error if path is not in this branch."""
270
275
return _relpath(self.base, path)
272
278
def controlfilename(self, file_or_path):
273
279
"""Return location relative to branch."""
274
280
if isinstance(file_or_path, basestring):
405
414
"""Inventory for the working copy.""")
408
def add(self, files, ids=None):
417
def add(self, files, verbose=False, ids=None):
409
418
"""Make files versioned.
411
Note that the command line normally calls smart_add instead,
412
which can automatically recurse.
420
Note that the command line normally calls smart_add instead.
414
422
This puts the files in the Added state, so that they will be
415
423
recorded by the next commit.
425
433
TODO: Perhaps have an option to add the ids even if the files do
428
TODO: Perhaps yield the ids and paths as they're added.
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
430
443
# TODO: Re-adding a file that is removed in the working copy
431
444
# should probably put it back with the previous ID.
467
480
file_id = gen_file_id(f)
468
481
inv.add_path(f, kind=kind, file_id=file_id)
484
print 'added', quotefn(f)
470
486
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
472
488
self._write_inventory(inv)
816
835
self.append_revision(*revision_ids)
817
836
## note("Added %d revisions." % count)
820
839
def install_revisions(self, other, revision_ids, pb):
821
840
if hasattr(other.revision_store, "prefetch"):
822
841
other.revision_store.prefetch(revision_ids)
854
873
count, cp_fail = self.text_store.copy_multi(other.text_store,
856
#print "Added %d texts." % count
875
print "Added %d texts." % count
857
876
inventory_ids = [ f.inventory_id for f in revisions ]
858
877
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
860
#print "Added %d inventories." % count
879
print "Added %d inventories." % count
861
880
revision_ids = [ f.revision_id for f in revisions]
863
882
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
875
894
def lookup_revision(self, revision):
876
895
"""Return the revision identifier for a given revision information."""
877
revno, info = self._get_revision_info(revision)
896
revno, info = self.get_revision_info(revision)
895
914
revision can also be a string, in which case it is parsed for something like
896
915
'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.
926
917
if revision is None:
933
924
revs = self.revision_history()
934
925
if isinstance(revision, int):
928
# Mabye we should do this first, but we don't need it if revision == 0
936
930
revno = len(revs) + revision + 1
939
rev_id = self.get_rev_id(revno, revs)
940
933
elif isinstance(revision, basestring):
941
934
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
942
935
if revision.startswith(prefix):
943
result = func(self, revs, revision)
945
revno, rev_id = result
948
rev_id = self.get_rev_id(revno, revs)
936
revno = func(self, revs, revision)
951
raise BzrError('No namespace registered for string: %r' %
954
raise TypeError('Unhandled revision type %s' % revision)
939
raise BzrError('No namespace registered for string: %r' % revision)
958
raise bzrlib.errors.NoSuchRevision(self, 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]
961
945
def _namespace_revno(self, revs, revision):
962
946
"""Lookup a revision by revision number"""
963
947
assert revision.startswith('revno:')
965
return (int(revision[6:]),)
949
return int(revision[6:])
966
950
except ValueError:
968
952
REVISION_NAMESPACES['revno:'] = _namespace_revno
970
954
def _namespace_revid(self, revs, revision):
971
955
assert revision.startswith('revid:')
972
rev_id = revision[len('revid:'):]
974
return revs.index(rev_id) + 1, rev_id
957
return revs.index(revision[6:]) + 1
975
958
except ValueError:
977
960
REVISION_NAMESPACES['revid:'] = _namespace_revid
979
962
def _namespace_last(self, revs, revision):
982
965
offset = int(revision[5:])
983
966
except ValueError:
987
970
raise BzrError('You must supply a positive value for --revision last:XXX')
988
return (len(revs) - offset + 1,)
971
return len(revs) - offset + 1
989
972
REVISION_NAMESPACES['last:'] = _namespace_last
991
974
def _namespace_tag(self, revs, revision):
1066
1049
# TODO: Handle timezone.
1067
1050
dt = datetime.datetime.fromtimestamp(r.timestamp)
1068
1051
if first >= dt and (last is None or dt >= last):
1071
1054
for i in range(len(revs)):
1072
1055
r = self.get_revision(revs[i])
1073
1056
# TODO: Handle timezone.
1074
1057
dt = datetime.datetime.fromtimestamp(r.timestamp)
1075
1058
if first <= dt and (last is None or dt <= last):
1077
1060
REVISION_NAMESPACES['date:'] = _namespace_date
1079
1062
def revision_tree(self, revision_id):
1145
1128
inv.rename(file_id, to_dir_id, to_tail)
1130
print "%s => %s" % (from_rel, to_rel)
1147
1132
from_abs = self.abspath(from_rel)
1148
1133
to_abs = self.abspath(to_rel)
1169
1154
Note that to_name is only the last component of the new name;
1170
1155
this doesn't change the directory.
1172
This returns a list of (from_path, to_path) pairs for each
1173
entry that is moved.
1176
1157
self.lock_write()
1178
1159
## TODO: Option to move IDs only
1213
1194
for f in from_paths:
1214
1195
name_tail = splitpath(f)[-1]
1215
1196
dest_path = appendpath(to_name, name_tail)
1216
result.append((f, dest_path))
1197
print "%s => %s" % (f, dest_path)
1217
1198
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1219
1200
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)
1368
1297
class ScratchBranch(Branch):
1369
1298
"""Special test class: a branch that cleans up after itself.
1488
1415
"""Return a new tree-root file id."""
1489
1416
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))