154
153
self.cache_root = cache_root
157
cfg = self.tree_config()
158
return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
160
def _set_nick(self, nick):
161
cfg = self.tree_config()
162
cfg.set_option(nick, "nickname")
163
assert cfg.get_option("nickname") == nick
165
nick = property(_get_nick, _set_nick)
167
def push_stores(self, branch_to):
168
"""Copy the content of this branches store to branch_to."""
169
raise NotImplementedError('push_stores is abstract')
171
def get_transaction(self):
172
"""Return the current active transaction.
174
If no transaction is active, this returns a passthrough object
175
for which all data is immediately flushed and no caching happens.
177
raise NotImplementedError('get_transaction is abstract')
179
def lock_write(self):
180
raise NotImplementedError('lock_write is abstract')
183
raise NotImplementedError('lock_read is abstract')
186
raise NotImplementedError('unlock is abstract')
188
def abspath(self, name):
189
"""Return absolute filename for something in the branch
191
XXX: Robert Collins 20051017 what is this used for? why is it a branch
192
method and not a tree method.
194
raise NotImplementedError('abspath is abstract')
196
def controlfilename(self, file_or_path):
197
"""Return location relative to branch."""
198
raise NotImplementedError('controlfilename is abstract')
200
def controlfile(self, file_or_path, mode='r'):
201
"""Open a control file for this branch.
203
There are two classes of file in the control directory: text
204
and binary. binary files are untranslated byte streams. Text
205
control files are stored with Unix newlines and in UTF-8, even
206
if the platform or locale defaults are different.
208
Controlfiles should almost never be opened in write mode but
209
rather should be atomically copied and replaced using atomicfile.
211
raise NotImplementedError('controlfile is abstract')
213
def put_controlfile(self, path, f, encode=True):
214
"""Write an entry as a controlfile.
216
:param path: The path to put the file, relative to the .bzr control
218
:param f: A file-like or string object whose contents should be copied.
219
:param encode: If true, encode the contents as utf-8
221
raise NotImplementedError('put_controlfile is abstract')
223
def put_controlfiles(self, files, encode=True):
224
"""Write several entries as controlfiles.
226
:param files: A list of [(path, file)] pairs, where the path is the directory
227
underneath the bzr control directory
228
:param encode: If true, encode the contents as utf-8
230
raise NotImplementedError('put_controlfiles is abstract')
232
def get_root_id(self):
233
"""Return the id of this branches root"""
234
raise NotImplementedError('get_root_id is abstract')
236
def set_root_id(self, file_id):
237
raise NotImplementedError('set_root_id is abstract')
239
def print_file(self, file, revno):
240
"""Print `file` to stdout."""
241
raise NotImplementedError('print_file is abstract')
243
def append_revision(self, *revision_ids):
244
raise NotImplementedError('append_revision is abstract')
246
def set_revision_history(self, rev_history):
247
raise NotImplementedError('set_revision_history is abstract')
249
def has_revision(self, revision_id):
250
"""True if this branch has a copy of the revision.
252
This does not necessarily imply the revision is merge
253
or on the mainline."""
254
raise NotImplementedError('has_revision is abstract')
256
def get_revision_xml(self, revision_id):
257
raise NotImplementedError('get_revision_xml is abstract')
259
def get_revision(self, revision_id):
260
"""Return the Revision object for a named revision"""
261
raise NotImplementedError('get_revision is abstract')
263
def get_revision_delta(self, revno):
264
"""Return the delta for one revision.
266
The delta is relative to its mainline predecessor, or the
267
empty tree for revision 1.
269
assert isinstance(revno, int)
270
rh = self.revision_history()
271
if not (1 <= revno <= len(rh)):
272
raise InvalidRevisionNumber(revno)
274
# revno is 1-based; list is 0-based
276
new_tree = self.revision_tree(rh[revno-1])
278
old_tree = EmptyTree()
280
old_tree = self.revision_tree(rh[revno-2])
282
return compare_trees(old_tree, new_tree)
284
def get_revision_sha1(self, revision_id):
285
"""Hash the stored value of a revision, and return it."""
286
raise NotImplementedError('get_revision_sha1 is abstract')
288
def get_ancestry(self, revision_id):
289
"""Return a list of revision-ids integrated by a revision.
291
This currently returns a list, but the ordering is not guaranteed:
294
raise NotImplementedError('get_ancestry is abstract')
296
def get_inventory(self, revision_id):
297
"""Get Inventory object by hash."""
298
raise NotImplementedError('get_inventory is abstract')
300
def get_inventory_xml(self, revision_id):
301
"""Get inventory XML as a file object."""
302
raise NotImplementedError('get_inventory_xml is abstract')
304
def get_inventory_sha1(self, revision_id):
305
"""Return the sha1 hash of the inventory entry."""
306
raise NotImplementedError('get_inventory_sha1 is abstract')
308
def get_revision_inventory(self, revision_id):
309
"""Return inventory of a past revision."""
310
raise NotImplementedError('get_revision_inventory is abstract')
312
def revision_history(self):
313
"""Return sequence of revision hashes on to this branch."""
314
raise NotImplementedError('revision_history is abstract')
317
"""Return current revision number for this branch.
319
That is equivalent to the number of revisions committed to
322
return len(self.revision_history())
324
def last_revision(self):
325
"""Return last patch hash, or None if no history."""
326
ph = self.revision_history()
332
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
333
"""Return a list of new revisions that would perfectly fit.
335
If self and other have not diverged, return a list of the revisions
336
present in other, but missing from self.
338
>>> from bzrlib.commit import commit
339
>>> bzrlib.trace.silent = True
340
>>> br1 = ScratchBranch()
341
>>> br2 = ScratchBranch()
342
>>> br1.missing_revisions(br2)
344
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
345
>>> br1.missing_revisions(br2)
347
>>> br2.missing_revisions(br1)
349
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
350
>>> br1.missing_revisions(br2)
352
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
353
>>> br1.missing_revisions(br2)
355
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
356
>>> br1.missing_revisions(br2)
357
Traceback (most recent call last):
358
DivergedBranches: These branches have diverged.
360
self_history = self.revision_history()
361
self_len = len(self_history)
362
other_history = other.revision_history()
363
other_len = len(other_history)
364
common_index = min(self_len, other_len) -1
365
if common_index >= 0 and \
366
self_history[common_index] != other_history[common_index]:
367
raise DivergedBranches(self, other)
369
if stop_revision is None:
370
stop_revision = other_len
372
assert isinstance(stop_revision, int)
373
if stop_revision > other_len:
374
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
375
return other_history[self_len:stop_revision]
377
def update_revisions(self, other, stop_revision=None):
378
"""Pull in new perfect-fit revisions."""
379
raise NotImplementedError('update_revisions is abstract')
381
def pullable_revisions(self, other, stop_revision):
382
raise NotImplementedError('pullable_revisions is abstract')
384
def revision_id_to_revno(self, revision_id):
385
"""Given a revision id, return its revno"""
386
if revision_id is None:
388
history = self.revision_history()
390
return history.index(revision_id) + 1
392
raise bzrlib.errors.NoSuchRevision(self, revision_id)
394
def get_rev_id(self, revno, history=None):
395
"""Find the revision id of the specified revno."""
399
history = self.revision_history()
400
elif revno <= 0 or revno > len(history):
401
raise bzrlib.errors.NoSuchRevision(self, revno)
402
return history[revno - 1]
404
def revision_tree(self, revision_id):
405
"""Return Tree for a revision on this branch.
407
`revision_id` may be None for the null revision, in which case
408
an `EmptyTree` is returned."""
409
raise NotImplementedError('revision_tree is abstract')
411
def working_tree(self):
412
"""Return a `Tree` for the working copy if this is a local branch."""
413
raise NotImplementedError('working_tree is abstract')
415
def pull(self, source, overwrite=False):
416
raise NotImplementedError('pull is abstract')
418
def basis_tree(self):
419
"""Return `Tree` object for last revision.
421
If there are no revisions yet, return an `EmptyTree`.
423
return self.revision_tree(self.last_revision())
425
def rename_one(self, from_rel, to_rel):
428
This can change the directory or the filename or both.
430
raise NotImplementedError('rename_one is abstract')
432
def move(self, from_paths, to_name):
435
to_name must exist as a versioned directory.
437
If to_name exists and is a directory, the files are moved into
438
it, keeping their old names. If it is a directory,
440
Note that to_name is only the last component of the new name;
441
this doesn't change the directory.
443
This returns a list of (from_path, to_path) pairs for each
446
raise NotImplementedError('move is abstract')
448
def get_parent(self):
449
"""Return the parent location of the branch.
451
This is the default location for push/pull/missing. The usual
452
pattern is that the user can override it by specifying a
455
raise NotImplementedError('get_parent is abstract')
457
def get_push_location(self):
458
"""Return the None or the location to push this branch to."""
459
raise NotImplementedError('get_push_location is abstract')
461
def set_push_location(self, location):
462
"""Set a new push location for this branch."""
463
raise NotImplementedError('set_push_location is abstract')
465
def set_parent(self, url):
466
raise NotImplementedError('set_parent is abstract')
468
def check_revno(self, revno):
470
Check whether a revno corresponds to any revision.
471
Zero (the NULL revision) is considered valid.
474
self.check_real_revno(revno)
476
def check_real_revno(self, revno):
478
Check whether a revno corresponds to a real revision.
479
Zero (the NULL revision) is considered invalid
481
if revno < 1 or revno > self.revno():
482
raise InvalidRevisionNumber(revno)
484
def sign_revision(self, revision_id, gpg_strategy):
485
raise NotImplementedError('sign_revision is abstract')
487
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
488
raise NotImplementedError('store_revision_signature is abstract')
490
class BzrBranch(Branch):
156
class _Branch(Branch):
491
157
"""A branch stored in the actual filesystem.
493
159
Note that it's "local" in the context of the filesystem; it doesn't
817
516
'or remove the .bzr directory'
818
517
' and "bzr init" again'])
821
519
def get_root_id(self):
822
"""See Branch.get_root_id."""
823
inv = self.get_inventory(self.last_revision())
520
"""Return the id of this branches root"""
521
inv = self.read_working_inventory()
824
522
return inv.root.file_id
524
def set_root_id(self, file_id):
525
inv = self.read_working_inventory()
526
orig_root_id = inv.root.file_id
527
del inv._byid[inv.root.file_id]
528
inv.root.file_id = file_id
529
inv._byid[inv.root.file_id] = inv.root
532
if entry.parent_id in (None, orig_root_id):
533
entry.parent_id = inv.root.file_id
534
self._write_inventory(inv)
536
def read_working_inventory(self):
537
"""Read the working inventory."""
540
# ElementTree does its own conversion from UTF-8, so open in
542
f = self.controlfile('inventory', 'rb')
543
return bzrlib.xml5.serializer_v5.read_inventory(f)
548
def _write_inventory(self, inv):
549
"""Update the working inventory.
551
That is to say, the inventory describing changes underway, that
552
will be committed to the next revision.
554
from cStringIO import StringIO
558
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
560
# Transport handles atomicity
561
self.put_controlfile('inventory', sio)
565
mutter('wrote working inventory')
567
inventory = property(read_working_inventory, _write_inventory, None,
568
"""Inventory for the working copy.""")
570
def add(self, files, ids=None):
571
"""Make files versioned.
573
Note that the command line normally calls smart_add instead,
574
which can automatically recurse.
576
This puts the files in the Added state, so that they will be
577
recorded by the next commit.
580
List of paths to add, relative to the base of the tree.
583
If set, use these instead of automatically generated ids.
584
Must be the same length as the list of files, but may
585
contain None for ids that are to be autogenerated.
587
TODO: Perhaps have an option to add the ids even if the files do
590
TODO: Perhaps yield the ids and paths as they're added.
592
# TODO: Re-adding a file that is removed in the working copy
593
# should probably put it back with the previous ID.
594
if isinstance(files, basestring):
595
assert(ids is None or isinstance(ids, basestring))
601
ids = [None] * len(files)
603
assert(len(ids) == len(files))
607
inv = self.read_working_inventory()
608
for f,file_id in zip(files, ids):
609
if is_control_file(f):
610
raise BzrError("cannot add control file %s" % quotefn(f))
615
raise BzrError("cannot add top-level %r" % f)
617
fullpath = os.path.normpath(self.abspath(f))
620
kind = file_kind(fullpath)
622
# maybe something better?
623
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
625
if not InventoryEntry.versionable_kind(kind):
626
raise BzrError('cannot add: not a versionable file ('
627
'i.e. regular file, symlink or directory): %s' % quotefn(f))
630
file_id = gen_file_id(f)
631
inv.add_path(f, kind=kind, file_id=file_id)
633
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
635
self._write_inventory(inv)
827
640
def print_file(self, file, revno):
828
"""See Branch.print_file."""
829
tree = self.revision_tree(self.get_rev_id(revno))
830
# use inventory as it was in that revision
831
file_id = tree.inventory.path2id(file)
833
raise BzrError("%r is not present in revision %s" % (file, revno))
834
tree.print_file(file_id)
641
"""Print `file` to stdout."""
644
tree = self.revision_tree(self.get_rev_id(revno))
645
# use inventory as it was in that revision
646
file_id = tree.inventory.path2id(file)
648
raise BzrError("%r is not present in revision %s" % (file, revno))
649
tree.print_file(file_id)
654
def remove(self, files, verbose=False):
655
"""Mark nominated files for removal from the inventory.
657
This does not remove their text. This does not run on
659
TODO: Refuse to remove modified files unless --force is given?
661
TODO: Do something useful with directories.
663
TODO: Should this remove the text or not? Tough call; not
664
removing may be useful and the user can just use use rm, and
665
is the opposite of add. Removing it is consistent with most
666
other tools. Maybe an option.
668
## TODO: Normalize names
669
## TODO: Remove nested loops; better scalability
670
if isinstance(files, basestring):
676
tree = self.working_tree()
679
# do this before any modifications
683
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
684
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
686
# having remove it, it must be either ignored or unknown
687
if tree.is_ignored(f):
691
show_status(new_status, inv[fid].kind, quotefn(f))
694
self._write_inventory(inv)
698
# FIXME: this doesn't need to be a branch method
699
def set_inventory(self, new_inventory_list):
700
from bzrlib.inventory import Inventory, InventoryEntry
701
inv = Inventory(self.get_root_id())
702
for path, file_id, parent, kind in new_inventory_list:
703
name = os.path.basename(path)
706
# fixme, there should be a factory function inv,add_??
707
if kind == 'directory':
708
inv.add(inventory.InventoryDirectory(file_id, name, parent))
710
inv.add(inventory.InventoryFile(file_id, name, parent))
711
elif kind == 'symlink':
712
inv.add(inventory.InventoryLink(file_id, name, parent))
714
raise BzrError("unknown kind %r" % kind)
715
self._write_inventory(inv)
718
"""Return all unknown files.
720
These are files in the working directory that are not versioned or
721
control files or ignored.
723
>>> b = ScratchBranch(files=['foo', 'foo~'])
724
>>> list(b.unknowns())
727
>>> list(b.unknowns())
730
>>> list(b.unknowns())
733
return self.working_tree().unknowns()
837
736
def append_revision(self, *revision_ids):
838
"""See Branch.append_revision."""
839
737
for revision_id in revision_ids:
840
738
mutter("add {%s} to revision-history" % revision_id)
841
rev_history = self.revision_history()
842
rev_history.extend(revision_ids)
843
self.set_revision_history(rev_history)
846
def set_revision_history(self, rev_history):
847
"""See Branch.set_revision_history."""
848
self.put_controlfile('revision-history', '\n'.join(rev_history))
741
rev_history = self.revision_history()
742
rev_history.extend(revision_ids)
743
self.put_controlfile('revision-history', '\n'.join(rev_history))
850
747
def has_revision(self, revision_id):
851
"""See Branch.has_revision."""
748
"""True if this branch has a copy of the revision.
750
This does not necessarily imply the revision is merge
751
or on the mainline."""
852
752
return (revision_id is None
853
or self.revision_store.has_id(revision_id))
753
or revision_id in self.revision_store)
856
def _get_revision_xml_file(self, revision_id):
755
def get_revision_xml_file(self, revision_id):
756
"""Return XML file object for revision object."""
857
757
if not revision_id or not isinstance(revision_id, basestring):
858
raise InvalidRevisionId(revision_id=revision_id, branch=self)
758
raise InvalidRevisionId(revision_id)
860
return self.revision_store.get(revision_id)
861
except (IndexError, KeyError):
862
raise bzrlib.errors.NoSuchRevision(self, revision_id)
763
return self.revision_store[revision_id]
764
except (IndexError, KeyError):
765
raise bzrlib.errors.NoSuchRevision(self, revision_id)
770
get_revision_xml = get_revision_xml_file
864
772
def get_revision_xml(self, revision_id):
865
"""See Branch.get_revision_xml."""
866
return self._get_revision_xml_file(revision_id).read()
773
return self.get_revision_xml_file(revision_id).read()
868
776
def get_revision(self, revision_id):
869
"""See Branch.get_revision."""
870
xml_file = self._get_revision_xml_file(revision_id)
777
"""Return the Revision object for a named revision"""
778
xml_file = self.get_revision_xml_file(revision_id)
873
781
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
890
819
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
892
821
def get_ancestry(self, revision_id):
893
"""See Branch.get_ancestry."""
822
"""Return a list of revision-ids integrated by a revision.
824
This currently returns a list, but the ordering is not guaranteed:
894
827
if revision_id is None:
896
w = self._get_inventory_weave()
829
w = self.get_inventory_weave()
897
830
return [None] + map(w.idx_to_name,
898
831
w.inclusions([w.lookup(revision_id)]))
900
def _get_inventory_weave(self):
833
def get_inventory_weave(self):
901
834
return self.control_weaves.get_weave('inventory',
902
835
self.get_transaction())
904
837
def get_inventory(self, revision_id):
905
"""See Branch.get_inventory."""
838
"""Get Inventory object by hash."""
906
839
xml = self.get_inventory_xml(revision_id)
907
840
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
909
842
def get_inventory_xml(self, revision_id):
910
"""See Branch.get_inventory_xml."""
843
"""Get inventory XML as a file object."""
912
845
assert isinstance(revision_id, basestring), type(revision_id)
913
iw = self._get_inventory_weave()
846
iw = self.get_inventory_weave()
914
847
return iw.get_text(iw.lookup(revision_id))
915
848
except IndexError:
916
849
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
918
851
def get_inventory_sha1(self, revision_id):
919
"""See Branch.get_inventory_sha1."""
852
"""Return the sha1 hash of the inventory entry
920
854
return self.get_revision(revision_id).inventory_sha1
922
856
def get_revision_inventory(self, revision_id):
923
"""See Branch.get_revision_inventory."""
857
"""Return inventory of a past revision."""
924
858
# TODO: Unify this with get_inventory()
925
859
# bzr 0.0.6 and later imposes the constraint that the inventory_id
926
860
# must be the same as its revision, so this is trivial.
927
861
if revision_id == None:
928
# This does not make sense: if there is no revision,
929
# then it is the current tree inventory surely ?!
930
# and thus get_root_id() is something that looks at the last
931
# commit on the branch, and the get_root_id is an inventory check.
932
raise NotImplementedError
933
# return Inventory(self.get_root_id())
862
return Inventory(self.get_root_id())
935
864
return self.get_inventory(revision_id)
938
866
def revision_history(self):
939
"""See Branch.revision_history."""
940
transaction = self.get_transaction()
941
history = transaction.map.find_revision_history()
942
if history is not None:
943
mutter("cache hit for revision-history in %s", self)
867
"""Return sequence of revision hashes on to this branch."""
870
transaction = self.get_transaction()
871
history = transaction.map.find_revision_history()
872
if history is not None:
873
mutter("cache hit for revision-history in %s", self)
875
history = [l.rstrip('\r\n') for l in
876
self.controlfile('revision-history', 'r').readlines()]
877
transaction.map.add_revision_history(history)
878
# this call is disabled because revision_history is
879
# not really an object yet, and the transaction is for objects.
880
# transaction.register_clean(history, precious=True)
944
881
return list(history)
945
history = [l.rstrip('\r\n') for l in
946
self.controlfile('revision-history', 'r').readlines()]
947
transaction.map.add_revision_history(history)
948
# this call is disabled because revision_history is
949
# not really an object yet, and the transaction is for objects.
950
# transaction.register_clean(history, precious=True)
886
"""Return current revision number for this branch.
888
That is equivalent to the number of revisions committed to
891
return len(self.revision_history())
894
def last_revision(self):
895
"""Return last patch hash, or None if no history.
897
ph = self.revision_history()
904
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
905
"""Return a list of new revisions that would perfectly fit.
907
If self and other have not diverged, return a list of the revisions
908
present in other, but missing from self.
910
>>> from bzrlib.commit import commit
911
>>> bzrlib.trace.silent = True
912
>>> br1 = ScratchBranch()
913
>>> br2 = ScratchBranch()
914
>>> br1.missing_revisions(br2)
916
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
917
>>> br1.missing_revisions(br2)
919
>>> br2.missing_revisions(br1)
921
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
922
>>> br1.missing_revisions(br2)
924
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
925
>>> br1.missing_revisions(br2)
927
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
928
>>> br1.missing_revisions(br2)
929
Traceback (most recent call last):
930
DivergedBranches: These branches have diverged.
932
self_history = self.revision_history()
933
self_len = len(self_history)
934
other_history = other.revision_history()
935
other_len = len(other_history)
936
common_index = min(self_len, other_len) -1
937
if common_index >= 0 and \
938
self_history[common_index] != other_history[common_index]:
939
raise DivergedBranches(self, other)
941
if stop_revision is None:
942
stop_revision = other_len
944
assert isinstance(stop_revision, int)
945
if stop_revision > other_len:
946
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
947
return other_history[self_len:stop_revision]
953
949
def update_revisions(self, other, stop_revision=None):
954
"""See Branch.update_revisions."""
950
"""Pull in new perfect-fit revisions."""
951
# FIXME: If the branches have diverged, but the latest
952
# revision in this branch is completely merged into the other,
953
# then we should still be able to pull.
955
954
from bzrlib.fetch import greedy_fetch
955
from bzrlib.revision import get_intervening_revisions
956
956
if stop_revision is None:
957
957
stop_revision = other.last_revision()
958
### Should this be checking is_ancestor instead of revision_history?
959
958
if (stop_revision is not None and
960
959
stop_revision in self.revision_history()):
962
961
greedy_fetch(to_branch=self, from_branch=other,
963
962
revision=stop_revision)
964
pullable_revs = self.pullable_revisions(other, stop_revision)
965
if len(pullable_revs) > 0:
963
pullable_revs = self.missing_revisions(
964
other, other.revision_id_to_revno(stop_revision))
966
greedy_fetch(to_branch=self,
968
revision=pullable_revs[-1])
966
969
self.append_revision(*pullable_revs)
968
def pullable_revisions(self, other, stop_revision):
969
"""See Branch.pullable_revisions."""
970
other_revno = other.revision_id_to_revno(stop_revision)
972
def commit(self, *args, **kw):
973
from bzrlib.commit import Commit
974
Commit().commit(self, *args, **kw)
976
def revision_id_to_revno(self, revision_id):
977
"""Given a revision id, return its revno"""
978
if revision_id is None:
980
history = self.revision_history()
972
return self.missing_revisions(other, other_revno)
973
except DivergedBranches, e:
975
pullable_revs = get_intervening_revisions(self.last_revision(),
977
assert self.last_revision() not in pullable_revs
979
except bzrlib.errors.NotAncestor:
980
if is_ancestor(self.last_revision(), stop_revision, self):
982
return history.index(revision_id) + 1
984
raise bzrlib.errors.NoSuchRevision(self, revision_id)
986
def get_rev_id(self, revno, history=None):
987
"""Find the revision id of the specified revno."""
991
history = self.revision_history()
992
elif revno <= 0 or revno > len(history):
993
raise bzrlib.errors.NoSuchRevision(self, revno)
994
return history[revno - 1]
985
996
def revision_tree(self, revision_id):
986
"""See Branch.revision_tree."""
997
"""Return Tree for a revision on this branch.
999
`revision_id` may be None for the null revision, in which case
1000
an `EmptyTree` is returned."""
987
1001
# TODO: refactor this to use an existing revision object
988
1002
# so we don't need to read it in twice.
989
if revision_id == None or revision_id == NULL_REVISION:
1003
if revision_id == None:
990
1004
return EmptyTree()
992
1006
inv = self.get_revision_inventory(revision_id)
993
1007
return RevisionTree(self.weave_store, inv, revision_id)
995
1010
def working_tree(self):
996
"""See Branch.working_tree."""
1011
"""Return a `Tree` for the working copy."""
997
1012
from bzrlib.workingtree import WorkingTree
998
if self._transport.base.find('://') != -1:
999
raise NoWorkingTree(self.base)
1000
return WorkingTree(self.base, branch=self)
1003
def pull(self, source, overwrite=False):
1004
"""See Branch.pull."""
1007
old_count = len(self.revision_history())
1009
self.update_revisions(source)
1010
except DivergedBranches:
1013
self.set_revision_history(source.revision_history())
1014
new_count = len(self.revision_history())
1015
return new_count - old_count
1013
# TODO: In the future, WorkingTree should utilize Transport
1014
# RobertCollins 20051003 - I don't think it should - working trees are
1015
# much more complex to keep consistent than our careful .bzr subset.
1016
# instead, we should say that working trees are local only, and optimise
1018
return WorkingTree(self._transport.base, self.read_working_inventory())
1021
def basis_tree(self):
1022
"""Return `Tree` object for last revision.
1024
If there are no revisions yet, return an `EmptyTree`.
1026
return self.revision_tree(self.last_revision())
1029
def rename_one(self, from_rel, to_rel):
1032
This can change the directory or the filename or both.
1036
tree = self.working_tree()
1037
inv = tree.inventory
1038
if not tree.has_filename(from_rel):
1039
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1040
if tree.has_filename(to_rel):
1041
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1043
file_id = inv.path2id(from_rel)
1045
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1047
if inv.path2id(to_rel):
1048
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1050
to_dir, to_tail = os.path.split(to_rel)
1051
to_dir_id = inv.path2id(to_dir)
1052
if to_dir_id == None and to_dir != '':
1053
raise BzrError("can't determine destination directory id for %r" % to_dir)
1055
mutter("rename_one:")
1056
mutter(" file_id {%s}" % file_id)
1057
mutter(" from_rel %r" % from_rel)
1058
mutter(" to_rel %r" % to_rel)
1059
mutter(" to_dir %r" % to_dir)
1060
mutter(" to_dir_id {%s}" % to_dir_id)
1062
inv.rename(file_id, to_dir_id, to_tail)
1064
from_abs = self.abspath(from_rel)
1065
to_abs = self.abspath(to_rel)
1067
rename(from_abs, to_abs)
1069
raise BzrError("failed to rename %r to %r: %s"
1070
% (from_abs, to_abs, e[1]),
1071
["rename rolled back"])
1073
self._write_inventory(inv)
1078
def move(self, from_paths, to_name):
1081
to_name must exist as a versioned directory.
1083
If to_name exists and is a directory, the files are moved into
1084
it, keeping their old names. If it is a directory,
1086
Note that to_name is only the last component of the new name;
1087
this doesn't change the directory.
1089
This returns a list of (from_path, to_path) pairs for each
1090
entry that is moved.
1095
## TODO: Option to move IDs only
1096
assert not isinstance(from_paths, basestring)
1097
tree = self.working_tree()
1098
inv = tree.inventory
1099
to_abs = self.abspath(to_name)
1100
if not isdir(to_abs):
1101
raise BzrError("destination %r is not a directory" % to_abs)
1102
if not tree.has_filename(to_name):
1103
raise BzrError("destination %r not in working directory" % to_abs)
1104
to_dir_id = inv.path2id(to_name)
1105
if to_dir_id == None and to_name != '':
1106
raise BzrError("destination %r is not a versioned directory" % to_name)
1107
to_dir_ie = inv[to_dir_id]
1108
if to_dir_ie.kind not in ('directory', 'root_directory'):
1109
raise BzrError("destination %r is not a directory" % to_abs)
1111
to_idpath = inv.get_idpath(to_dir_id)
1113
for f in from_paths:
1114
if not tree.has_filename(f):
1115
raise BzrError("%r does not exist in working tree" % f)
1116
f_id = inv.path2id(f)
1118
raise BzrError("%r is not versioned" % f)
1119
name_tail = splitpath(f)[-1]
1120
dest_path = appendpath(to_name, name_tail)
1121
if tree.has_filename(dest_path):
1122
raise BzrError("destination %r already exists" % dest_path)
1123
if f_id in to_idpath:
1124
raise BzrError("can't move %r to a subdirectory of itself" % f)
1126
# OK, so there's a race here, it's possible that someone will
1127
# create a file in this interval and then the rename might be
1128
# left half-done. But we should have caught most problems.
1130
for f in from_paths:
1131
name_tail = splitpath(f)[-1]
1132
dest_path = appendpath(to_name, name_tail)
1133
result.append((f, dest_path))
1134
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1136
rename(self.abspath(f), self.abspath(dest_path))
1138
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1139
["rename rolled back"])
1141
self._write_inventory(inv)
1148
def revert(self, filenames, old_tree=None, backups=True):
1149
"""Restore selected files to the versions from a previous tree.
1152
If true (default) backups are made of files before
1155
from bzrlib.errors import NotVersionedError, BzrError
1156
from bzrlib.atomicfile import AtomicFile
1157
from bzrlib.osutils import backup_file
1159
inv = self.read_working_inventory()
1160
if old_tree is None:
1161
old_tree = self.basis_tree()
1162
old_inv = old_tree.inventory
1165
for fn in filenames:
1166
file_id = inv.path2id(fn)
1168
raise NotVersionedError("not a versioned file", fn)
1169
if not old_inv.has_id(file_id):
1170
raise BzrError("file not present in old tree", fn, file_id)
1171
nids.append((fn, file_id))
1173
# TODO: Rename back if it was previously at a different location
1175
# TODO: If given a directory, restore the entire contents from
1176
# the previous version.
1178
# TODO: Make a backup to a temporary file.
1180
# TODO: If the file previously didn't exist, delete it?
1181
for fn, file_id in nids:
1184
f = AtomicFile(fn, 'wb')
1186
f.write(old_tree.get_file(file_id).read())
1192
def pending_merges(self):
1193
"""Return a list of pending merges.
1195
These are revisions that have been merged into the working
1196
directory but not yet committed.
1198
cfn = self._rel_controlfilename('pending-merges')
1199
if not self._transport.has(cfn):
1202
for l in self.controlfile('pending-merges', 'r').readlines():
1203
p.append(l.rstrip('\n'))
1207
def add_pending_merge(self, *revision_ids):
1208
# TODO: Perhaps should check at this point that the
1209
# history of the revision is actually present?
1210
p = self.pending_merges()
1212
for rev_id in revision_ids:
1218
self.set_pending_merges(p)
1220
def set_pending_merges(self, rev_list):
1223
self.put_controlfile('pending-merges', '\n'.join(rev_list))
1019
1228
def get_parent(self):
1020
"""See Branch.get_parent."""
1229
"""Return the parent location of the branch.
1231
This is the default location for push/pull/missing. The usual
1232
pattern is that the user can override it by specifying a
1022
1236
_locs = ['parent', 'pull', 'x-pull']
1023
1237
for l in _locs: