15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
19
import traceback, socket, fnmatch, difflib, time
20
from binascii import hexlify
21
from bzrlib.trace import mutter, note
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
23
sha_file, appendpath, file_kind
24
from bzrlib.errors import BzrError
23
from inventory import Inventory
24
from trace import mutter, note
25
from tree import Tree, EmptyTree, RevisionTree
26
from inventory import InventoryEntry, Inventory
27
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
28
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
29
joinpath, sha_file, sha_string, file_kind, local_time_offset, appendpath
30
from store import ImmutableStore
31
from revision import Revision
32
from errors import BzrError
33
from textui import show_status
26
35
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
27
36
## TODO: Maybe include checks for common corruption of newlines, etc?
36
45
return Branch(f, **args)
39
def find_cached_branch(f, cache_root, **args):
40
from remotebranch import RemoteBranch
41
br = find_branch(f, **args)
42
def cacheify(br, store_name):
43
from meta_store import CachedStore
44
cache_path = os.path.join(cache_root, store_name)
46
new_store = CachedStore(getattr(br, store_name), cache_path)
47
setattr(br, store_name, new_store)
49
if isinstance(br, RemoteBranch):
50
cacheify(br, 'inventory_store')
51
cacheify(br, 'text_store')
52
cacheify(br, 'revision_store')
56
49
def _relpath(base, path):
57
50
"""Return path relative to base, or raise exception.
258
250
def controlfilename(self, file_or_path):
259
251
"""Return location relative to branch."""
260
if isinstance(file_or_path, basestring):
252
if isinstance(file_or_path, types.StringTypes):
261
253
file_or_path = [file_or_path]
262
254
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
292
284
def _make_control(self):
293
from bzrlib.inventory import Inventory
294
from bzrlib.xml import pack_xml
296
285
os.mkdir(self.controlfilename([]))
297
286
self.controlfile('README', 'w').write(
298
287
"This is a Bazaar-NG control directory.\n"
306
295
self.controlfile(f, 'w').write('')
307
296
mutter('created control directory in ' + self.base)
309
pack_xml(Inventory(), self.controlfile('inventory','w'))
297
Inventory().write_xml(self.controlfile('inventory','w'))
312
300
def _check_format(self):
332
320
def read_working_inventory(self):
333
321
"""Read the working inventory."""
334
from bzrlib.inventory import Inventory
335
from bzrlib.xml import unpack_xml
336
from time import time
323
# ElementTree does its own conversion from UTF-8, so open in
340
# ElementTree does its own conversion from UTF-8, so open in
342
inv = unpack_xml(Inventory,
343
self.controlfile('inventory', 'rb'))
327
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
344
328
mutter("loaded inventory of %d items in %f"
345
% (len(inv), time() - before))
329
% (len(inv), time.time() - before))
354
338
That is to say, the inventory describing changes underway, that
355
339
will be committed to the next revision.
357
from bzrlib.atomicfile import AtomicFile
358
from bzrlib.xml import pack_xml
362
f = AtomicFile(self.controlfilename('inventory'), 'wb')
341
## TODO: factor out to atomicfile? is rename safe on windows?
342
## TODO: Maybe some kind of clean/dirty marker on inventory?
343
tmpfname = self.controlfilename('inventory.tmp')
344
tmpf = file(tmpfname, 'wb')
347
inv_fname = self.controlfilename('inventory')
348
if sys.platform == 'win32':
350
os.rename(tmpfname, inv_fname)
371
351
mutter('wrote working inventory')
401
381
add all non-ignored children. Perhaps do that in a
402
382
higher-level method.
404
from bzrlib.textui import show_status
405
384
# TODO: Re-adding a file that is removed in the working copy
406
385
# should probably put it back with the previous ID.
407
if isinstance(files, basestring):
408
assert(ids is None or isinstance(ids, basestring))
386
if isinstance(files, types.StringTypes):
387
assert(ids is None or isinstance(ids, types.StringTypes))
410
389
if ids is not None:
443
422
inv.add_path(f, kind=kind, file_id=file_id)
446
print 'added', quotefn(f)
425
show_status('A', kind, quotefn(f))
448
427
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
480
459
is the opposite of add. Removing it is consistent with most
481
460
other tools. Maybe an option.
483
from bzrlib.textui import show_status
484
462
## TODO: Normalize names
485
463
## TODO: Remove nested loops; better scalability
486
if isinstance(files, basestring):
464
if isinstance(files, types.StringTypes):
489
467
self.lock_write()
515
493
# FIXME: this doesn't need to be a branch method
516
494
def set_inventory(self, new_inventory_list):
517
from bzrlib.inventory import Inventory, InventoryEntry
518
495
inv = Inventory()
519
496
for path, file_id, parent, kind in new_inventory_list:
520
497
name = os.path.basename(path)
546
523
def append_revision(self, revision_id):
547
from bzrlib.atomicfile import AtomicFile
549
524
mutter("add {%s} to revision-history" % revision_id)
550
rev_history = self.revision_history() + [revision_id]
552
f = AtomicFile(self.controlfilename('revision-history'))
554
for rev_id in rev_history:
525
rev_history = self.revision_history()
527
tmprhname = self.controlfilename('revision-history.tmp')
528
rhname = self.controlfilename('revision-history')
530
f = file(tmprhname, 'wt')
531
rev_history.append(revision_id)
532
f.write('\n'.join(rev_history))
536
if sys.platform == 'win32':
538
os.rename(tmprhname, rhname)
561
542
def get_revision(self, revision_id):
562
543
"""Return the Revision object for a named revision"""
563
from bzrlib.revision import Revision
564
from bzrlib.xml import unpack_xml
568
if not revision_id or not isinstance(revision_id, basestring):
569
raise ValueError('invalid revision-id: %r' % revision_id)
570
r = unpack_xml(Revision, self.revision_store[revision_id])
544
if not revision_id or not isinstance(revision_id, basestring):
545
raise ValueError('invalid revision-id: %r' % revision_id)
546
r = Revision.read_xml(self.revision_store[revision_id])
574
547
assert r.revision_id == revision_id
578
550
def get_revision_sha1(self, revision_id):
579
551
"""Hash the stored value of a revision, and return it."""
592
564
TODO: Perhaps for this and similar methods, take a revision
593
565
parameter which can be either an integer revno or a
595
from bzrlib.inventory import Inventory
596
from bzrlib.xml import unpack_xml
598
return unpack_xml(Inventory, self.inventory_store[inventory_id])
567
i = Inventory.read_xml(self.inventory_store[inventory_id])
601
570
def get_inventory_sha1(self, inventory_id):
602
571
"""Return the sha1 hash of the inventory entry
782
750
from bzrlib.progress import ProgressBar
786
from sets import Set as set
788
752
pb = ProgressBar()
790
754
pb.update('comparing histories')
791
755
revision_ids = self.missing_revisions(other, stop_revision)
793
if hasattr(other.revision_store, "prefetch"):
794
other.revision_store.prefetch(revision_ids)
795
if hasattr(other.inventory_store, "prefetch"):
796
inventory_ids = [other.get_revision(r).inventory_id
797
for r in revision_ids]
798
other.inventory_store.prefetch(inventory_ids)
757
needed_texts = sets.Set()
803
759
for rev_id in revision_ids:
851
808
`revision_id` may be None for the null revision, in which case
852
809
an `EmptyTree` is returned."""
853
from bzrlib.tree import EmptyTree, RevisionTree
854
810
# TODO: refactor this to use an existing revision object
855
811
# so we don't need to read it in twice.
856
812
if revision_id == None:
998
def revert(self, filenames, old_tree=None, backups=True):
999
"""Restore selected files to the versions from a previous tree.
1002
If true (default) backups are made of files before
1005
from bzrlib.errors import NotVersionedError, BzrError
1006
from bzrlib.atomicfile import AtomicFile
1007
from bzrlib.osutils import backup_file
1009
inv = self.read_working_inventory()
1010
if old_tree is None:
1011
old_tree = self.basis_tree()
1012
old_inv = old_tree.inventory
1015
for fn in filenames:
1016
file_id = inv.path2id(fn)
1018
raise NotVersionedError("not a versioned file", fn)
1019
if not old_inv.has_id(file_id):
1020
raise BzrError("file not present in old tree", fn, file_id)
1021
nids.append((fn, file_id))
1023
# TODO: Rename back if it was previously at a different location
1025
# TODO: If given a directory, restore the entire contents from
1026
# the previous version.
1028
# TODO: Make a backup to a temporary file.
1030
# TODO: If the file previously didn't exist, delete it?
1031
for fn, file_id in nids:
1034
f = AtomicFile(fn, 'wb')
1036
f.write(old_tree.get_file(file_id).read())
1043
954
class ScratchBranch(Branch):
1044
955
"""Special test class: a branch that cleans up after itself.
1059
970
If any files are listed, they are created in the working copy.
1061
from tempfile import mkdtemp
1063
973
if base is None:
974
base = tempfile.mkdtemp()
1066
976
Branch.__init__(self, base, init=init)
1080
990
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1083
from shutil import copytree
1084
from tempfile import mkdtemp
993
base = tempfile.mkdtemp()
1087
copytree(self.base, base, symlinks=True)
995
shutil.copytree(self.base, base, symlinks=True)
1088
996
return ScratchBranch(base=base)
1090
998
def __del__(self):
1093
1001
def destroy(self):
1094
1002
"""Destroy the test branch, removing the scratch directory."""
1095
from shutil import rmtree
1098
1005
mutter("delete ScratchBranch %s" % self.base)
1006
shutil.rmtree(self.base)
1100
1007
except OSError, e:
1101
1008
# Work around for shutil.rmtree failing on Windows when
1102
1009
# readonly files are encountered
1154
1059
name = re.sub(r'[^\w.]', '', name)
1156
1061
s = hexlify(rand_bytes(8))
1157
return '-'.join((name, compact_date(time()), s))
1062
return '-'.join((name, compact_date(time.time()), s))