29
29
WorkingTree.open(dir).
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
33
# FIXME: I don't know if writing out the cache from the destructor is really a
34
# good idea, because destructors are considered poor taste in Python, and it's
35
# not predictable when it will be written out.
35
37
# TODO: Give the workingtree sole responsibility for the working inventory;
36
38
# remove the variable and references to it from the branch. This may require
37
39
# updating the commit code so as to update the inventory within the working
38
40
# copy, and making sure there's only one WorkingTree for any directory on disk.
39
# At the moment they may alias the inventory and have old copies of it in
40
# memory. (Now done? -- mbp 20060309)
41
# At the momenthey may alias the inventory and have old copies of it in memory.
42
from binascii import hexlify
44
43
from copy import deepcopy
45
44
from cStringIO import StringIO
54
from bzrlib import bzrdir, errors, osutils, urlutils
55
51
from bzrlib.atomicfile import AtomicFile
56
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
52
from bzrlib.branch import (Branch,
54
import bzrlib.bzrdir as bzrdir
57
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
56
import bzrlib.errors as errors
58
57
from bzrlib.errors import (BzrCheckError,
61
60
WeaveRevisionNotPresent,
65
MergeModifiedFormatError,
68
64
from bzrlib.inventory import InventoryEntry, Inventory
69
from bzrlib.lockable_files import LockableFiles, TransportLock
70
from bzrlib.lockdir import LockDir
65
from bzrlib.lockable_files import LockableFiles
71
66
from bzrlib.merge import merge_inner, transform_tree
72
from bzrlib.osutils import (
67
from bzrlib.osutils import (appendpath,
87
82
supports_executable,
89
from bzrlib.progress import DummyProgress, ProgressPhase
84
from bzrlib.progress import DummyProgress
90
85
from bzrlib.revision import NULL_REVISION
91
from bzrlib.rio import RioReader, rio_file, Stanza
92
from bzrlib.symbol_versioning import (deprecated_passed,
86
from bzrlib.symbol_versioning import *
99
87
from bzrlib.textui import show_status
100
88
import bzrlib.tree
89
from bzrlib.trace import mutter
101
90
from bzrlib.transform import build_tree
102
from bzrlib.trace import mutter, note
103
91
from bzrlib.transport import get_transport
104
92
from bzrlib.transport.local import LocalTransport
106
94
import bzrlib.xml5
109
# the regex here does the following:
110
# 1) remove any weird characters; we don't escape them but rather
112
# 2) match leading '.'s to make it not hidden
113
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
114
_gen_id_suffix = None
118
def _next_id_suffix():
119
"""Create a new file id suffix that is reasonably unique.
121
On the first call we combine the current time with 64 bits of randomness
122
to give a highly probably globally unique number. Then each call in the same
123
process adds 1 to a serial number we append to that unique value.
125
# XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather
126
# than having to move the id randomness out of the inner loop like this.
127
# XXX TODO: for the global randomness this uses we should add the thread-id
128
# before the serial #.
129
global _gen_id_suffix, _gen_id_serial
130
if _gen_id_suffix is None:
131
_gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
133
return _gen_id_suffix + str(_gen_id_serial)
136
97
def gen_file_id(name):
137
"""Return new file id for the basename 'name'.
139
The uniqueness is supplied from _next_id_suffix.
141
# XXX TODO: squash the filename to lowercase.
142
# XXX TODO: truncate the filename to something like 20 or 30 chars.
143
# XXX TODO: consider what to do with ids that look like illegal filepaths
144
# on platforms we support.
145
return _gen_file_id_re.sub('', name) + _next_id_suffix()
98
"""Return new file id.
100
This should probably generate proper UUIDs, but for the moment we
101
cope with just randomness because running uuidgen every time is
104
from binascii import hexlify
105
from time import time
108
idx = name.rfind('/')
110
name = name[idx+1 : ]
111
idx = name.rfind('\\')
113
name = name[idx+1 : ]
115
# make it not a hidden file
116
name = name.lstrip('.')
118
# remove any wierd characters; we don't escape them but rather
120
name = re.sub(r'[^\w.]', '', name)
122
s = hexlify(rand_bytes(8))
123
return '-'.join((name, compact_date(time()), s))
148
126
def gen_root_id():
233
211
self.bzrdir = _bzrdir
234
212
if not _internal:
235
213
# not created via open etc.
236
warnings.warn("WorkingTree() is deprecated as of bzr version 0.8. "
214
warn("WorkingTree() is deprecated as of bzr version 0.8. "
237
215
"Please use bzrdir.open_workingtree or WorkingTree.open().",
238
216
DeprecationWarning,
240
218
wt = WorkingTree.open(basedir)
241
self._branch = wt.branch
219
self.branch = wt.branch
242
220
self.basedir = wt.basedir
243
221
self._control_files = wt._control_files
244
222
self._hashcache = wt._hashcache
253
231
mutter("opening working tree %r", basedir)
254
232
if deprecated_passed(branch):
255
233
if not _internal:
256
warnings.warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
257
" Please use bzrdir.open_workingtree() or"
258
" WorkingTree.open().",
234
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
235
" Please use bzrdir.open_workingtree() or WorkingTree.open().",
259
236
DeprecationWarning,
262
self._branch = branch
264
self._branch = self.bzrdir.open_branch()
241
self.branch = self.bzrdir.open_branch()
242
assert isinstance(self.branch, Branch), \
243
"branch %r is not a Branch" % self.branch
265
244
self.basedir = realpath(basedir)
266
245
# if branch is at our basedir and is a format 6 or less
267
246
if isinstance(self._format, WorkingTreeFormat2):
268
247
# share control object
269
248
self._control_files = self.branch.control_files
249
elif _control_files is not None:
250
assert False, "not done yet"
251
# self._control_files = _control_files
271
253
# only ready for format 3
272
254
assert isinstance(self._format, WorkingTreeFormat3)
273
assert isinstance(_control_files, LockableFiles), \
274
"_control_files must be a LockableFiles, not %r" \
276
self._control_files = _control_files
255
self._control_files = LockableFiles(
256
self.bzrdir.get_workingtree_transport(None),
277
259
# update the whole cache up front and write to disk if anything changed;
278
260
# in the future we might want to do this more selectively
279
261
# two possible ways offer themselves : in self._unlock, write the cache
280
262
# if needed, or, when the cache sees a change, append it to the hash
281
263
# cache file, and have the parser take the most recent entry for a
282
264
# given path only.
283
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
265
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
284
266
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
286
268
# is this scan needed ? it makes things kinda slow.
289
271
if hc.needs_write:
290
272
mutter("write hc")
296
278
self._set_inventory(_inventory)
299
fget=lambda self: self._branch,
300
doc="""The branch this WorkingTree is connected to.
302
This cannot be set - it is reflective of the actual disk structure
303
the working tree has been constructed from.
306
def break_lock(self):
307
"""Break a lock if one is present from another instance.
309
Uses the ui factory to ask for confirmation if the lock may be from
312
This will probe the repository for its lock as well.
314
self._control_files.break_lock()
315
self.branch.break_lock()
317
280
def _set_inventory(self, inv):
318
281
self._inventory = inv
319
282
self.path2id = self._inventory.path2id
321
284
def is_control_filename(self, filename):
322
285
"""True if filename is the name of a control file in this tree.
324
:param filename: A filename within the tree. This is a relative path
325
from the root of this tree.
327
287
This is true IF and ONLY IF the filename is part of the meta data
328
288
that bzr controls in this tree. I.E. a random .bzr directory placed
329
289
on disk will not be a control file for this tree.
331
return self.bzrdir.is_control_filename(filename)
292
self.bzrdir.transport.relpath(self.abspath(filename))
294
except errors.PathNotChild:
334
298
def open(path=None, _unsupported=False):
350
314
run into /. If there isn't one, raises NotBranchError.
351
315
TODO: give this a new exception.
352
316
If there is one, it is returned, along with the unused portion of path.
354
:return: The WorkingTree that contains 'path', and the rest of path
357
path = osutils.getcwd()
358
320
control, relpath = bzrdir.BzrDir.open_containing(path)
360
321
return control.open_workingtree(), relpath
390
351
revision_id = self.last_revision()
391
352
if revision_id is not None:
393
xml = self.read_basis_inventory()
354
xml = self.read_basis_inventory(revision_id)
394
355
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
397
if inv is not None and inv.revision_id == revision_id:
398
356
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
400
# FIXME? RBC 20060403 should we cache the inventory here ?
401
360
return self.branch.repository.revision_tree(revision_id)
439
398
return bzrdir.BzrDir.create_standalone_workingtree(directory)
441
def relpath(self, path):
442
"""Return the local path portion from a given path.
444
The path may be absolute or relative. If its a relative path it is
445
interpreted relative to the python current working directory.
447
return relpath(self.basedir, path)
400
def relpath(self, abs):
401
"""Return the local path portion from a given absolute path."""
402
return relpath(self.basedir, abs)
449
404
def has_filename(self, filename):
450
405
return bzrlib.osutils.lexists(self.abspath(filename))
455
410
def get_file_byname(self, filename):
456
411
return file(self.abspath(filename), 'rb')
458
def get_parent_ids(self):
459
"""See Tree.get_parent_ids.
461
This implementation reads the pending merges list and last_revision
462
value and uses that to decide what the parents list should be.
464
last_rev = self.last_revision()
469
other_parents = self.pending_merges()
470
return parents + other_parents
472
413
def get_root_id(self):
473
414
"""Return the id of this trees root"""
474
415
inv = self.read_working_inventory()
512
453
tree.set_last_revision(revision_id)
514
455
@needs_write_lock
515
def commit(self, message=None, revprops=None, *args, **kwargs):
516
# avoid circular imports
456
def commit(self, *args, **kwargs):
517
457
from bzrlib.commit import Commit
520
if not 'branch-nick' in revprops:
521
revprops['branch-nick'] = self.branch.nick
522
458
# args for wt.commit start at message from the Commit.commit method,
523
459
# but with branch a kwarg now, passing in args as is results in the
524
460
#message being used for the branch
525
args = (DEPRECATED_PARAMETER, message, ) + args
526
committed_id = Commit().commit( working_tree=self, revprops=revprops,
461
args = (DEPRECATED_PARAMETER, ) + args
462
Commit().commit(working_tree=self, *args, **kwargs)
528
463
self._set_inventory(self.read_working_inventory())
531
465
def id2abspath(self, file_id):
532
466
return self.abspath(self.id2path(file_id))
550
484
return os.path.getsize(self.id2abspath(file_id))
553
def get_file_sha1(self, file_id, path=None):
555
path = self._inventory.id2path(file_id)
487
def get_file_sha1(self, file_id):
488
path = self._inventory.id2path(file_id)
556
489
return self._hashcache.get_sha1(path)
558
def get_file_mtime(self, file_id, path=None):
560
path = self._inventory.id2path(file_id)
561
return os.lstat(self.abspath(path)).st_mtime
563
if not supports_executable():
564
def is_executable(self, file_id, path=None):
491
def is_executable(self, file_id):
492
if not supports_executable():
565
493
return self._inventory[file_id].executable
567
def is_executable(self, file_id, path=None):
569
path = self._inventory.id2path(file_id)
495
path = self._inventory.id2path(file_id)
570
496
mode = os.lstat(self.abspath(path)).st_mode
571
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
497
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
573
499
@needs_write_lock
574
500
def add(self, files, ids=None):
617
543
raise BzrError("cannot add top-level %r" % f)
619
545
fullpath = normpath(self.abspath(f))
621
548
kind = file_kind(fullpath)
622
549
except OSError, e:
623
550
if e.errno == errno.ENOENT:
624
551
raise NoSuchFile(fullpath)
552
# maybe something better?
553
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
625
555
if not InventoryEntry.versionable_kind(kind):
626
raise errors.BadFileKindError(filename=f, kind=kind)
556
raise BzrError('cannot add: not a versionable file ('
557
'i.e. regular file, symlink or directory): %s' % quotefn(f))
627
559
if file_id is None:
628
inv.add_path(f, kind=kind)
630
inv.add_path(f, kind=kind, file_id=file_id)
560
file_id = gen_file_id(f)
561
inv.add_path(f, kind=kind, file_id=file_id)
563
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
632
564
self._write_inventory(inv)
634
566
@needs_write_lock
665
599
def set_pending_merges(self, rev_list):
666
600
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
669
def set_merge_modified(self, modified_hashes):
671
for file_id, hash in modified_hashes.iteritems():
672
yield Stanza(file_id=file_id, hash=hash)
673
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
676
def _put_rio(self, filename, stanzas, header):
677
my_file = rio_file(stanzas, header)
678
self._control_files.put(filename, my_file)
681
def merge_modified(self):
683
hashfile = self._control_files.get('merge-hashes')
688
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
689
raise MergeModifiedFormatError()
690
except StopIteration:
691
raise MergeModifiedFormatError()
692
for s in RioReader(hashfile):
693
file_id = s.get("file_id")
694
if file_id not in self.inventory:
697
if hash == self.get_file_sha1(file_id):
698
merge_hashes[file_id] = hash
701
602
def get_symlink_target(self, file_id):
702
603
return os.readlink(self.id2abspath(file_id))
720
621
Skips the control directory.
722
623
inv = self._inventory
723
# Convert these into local objects to save lookup times
724
pathjoin = bzrlib.osutils.pathjoin
725
file_kind = bzrlib.osutils.file_kind
727
# transport.base ends in a slash, we want the piece
728
# between the last two slashes
729
transport_base_dir = self.bzrdir.transport.base.rsplit('/', 2)[1]
731
fk_entries = {'directory':TreeDirectory, 'file':TreeFile, 'symlink':TreeLink}
733
# directory file_id, relative path, absolute path, reverse sorted children
734
children = os.listdir(self.basedir)
736
# jam 20060527 The kernel sized tree seems equivalent whether we
737
# use a deque and popleft to keep them sorted, or if we use a plain
738
# list and just reverse() them.
739
children = collections.deque(children)
740
stack = [(inv.root.file_id, u'', self.basedir, children)]
742
from_dir_id, from_dir_relpath, from_dir_abspath, children = stack[-1]
745
f = children.popleft()
625
def descend(from_dir_relpath, from_dir_id, dp):
746
629
## TODO: If we find a subdirectory with its own .bzr
747
630
## directory, then that is a separate tree and we
748
631
## should exclude it.
750
633
# the bzrdir for this tree
751
if transport_base_dir == f:
634
if self.bzrdir.transport.base.endswith(f + '/'):
754
# we know that from_dir_relpath and from_dir_abspath never end in a slash
755
# and 'f' doesn't begin with one, we can do a string op, rather
756
# than the checks of pathjoin(), all relative paths will have an extra slash
758
fp = from_dir_relpath + '/' + f
638
fp = appendpath(from_dir_relpath, f)
761
fap = from_dir_abspath + '/' + f
641
fap = appendpath(dp, f)
763
643
f_ie = inv.get_child(from_dir_id, f)
766
elif self.is_ignored(fp[1:]):
646
elif self.is_ignored(fp):
769
# we may not have found this file, because of a unicode issue
770
f_norm, can_access = osutils.normalized_filename(f)
771
if f == f_norm or not can_access:
772
# No change, so treat this file normally
775
# this file can be accessed by a normalized path
776
# check again if it is versioned
777
# these lines are repeated here for performance
779
fp = from_dir_relpath + '/' + f
780
fap = from_dir_abspath + '/' + f
781
f_ie = inv.get_child(from_dir_id, f)
784
elif self.is_ignored(fp[1:]):
789
651
fk = file_kind(fap)
797
659
# make a last minute entry
799
yield fp[1:], c, fk, f_ie.file_id, f_ie
802
yield fp[1:], c, fk, None, fk_entries[fk]()
804
yield fp[1:], c, fk, None, TreeEntry()
663
if fk == 'directory':
664
entry = TreeDirectory()
667
elif fk == 'symlink':
672
yield fp, c, fk, (f_ie and f_ie.file_id), entry
807
674
if fk != 'directory':
810
# But do this child first
811
new_children = os.listdir(fap)
813
new_children = collections.deque(new_children)
814
stack.append((f_ie.file_id, fp, fap, new_children))
815
# Break out of inner loop, so that we start outer loop with child
818
# if we finished all children, pop it off the stack
678
# don't descend unversioned directories
681
for ff in descend(fp, f_ie.file_id, fap):
684
for f in descend(u'', inv.root.file_id, self.basedir):
822
687
@needs_write_lock
823
688
def move(self, from_paths, to_name):
943
808
These are files in the working directory that are not versioned or
944
809
control files or ignored.
811
>>> from bzrlib.bzrdir import ScratchDir
812
>>> d = ScratchDir(files=['foo', 'foo~'])
813
>>> b = d.open_branch()
814
>>> tree = d.open_workingtree()
815
>>> map(str, tree.unknowns())
818
>>> list(b.unknowns())
820
>>> tree.remove('foo')
821
>>> list(b.unknowns())
946
824
for subp in self.extras():
947
825
if not self.is_ignored(subp):
950
@deprecated_method(zero_eight)
951
828
def iter_conflicts(self):
952
"""List all files in the tree that have text or content conflicts.
953
DEPRECATED. Use conflicts instead."""
954
return self._iter_conflicts()
956
def _iter_conflicts(self):
957
829
conflicted = set()
958
for info in self.list_files():
830
for path in (s[0] for s in self.list_files()):
960
831
stem = get_conflicted_stem(path)
967
838
@needs_write_lock
968
839
def pull(self, source, overwrite=False, stop_revision=None):
969
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
970
840
source.lock_read()
972
pp = ProgressPhase("Pull phase", 2, top_pb)
974
842
old_revision_history = self.branch.revision_history()
975
843
basis_tree = self.basis_tree()
976
844
count = self.branch.pull(source, overwrite, stop_revision)
977
845
new_revision_history = self.branch.revision_history()
978
846
if new_revision_history != old_revision_history:
980
847
if len(old_revision_history):
981
848
other_revision = old_revision_history[-1]
983
850
other_revision = None
984
851
repository = self.branch.repository
985
pb = bzrlib.ui.ui_factory.nested_progress_bar()
987
merge_inner(self.branch,
988
self.branch.basis_tree(),
852
merge_inner(self.branch,
853
self.branch.basis_tree(),
856
pb=bzrlib.ui.ui_factory.progress_bar())
994
857
self.set_last_revision(self.branch.last_revision())
1000
862
def extras(self):
1001
863
"""Yield all unknown files in this WorkingTree.
1018
880
for subf in os.listdir(dirabs):
1021
if subf not in dir_entry.children:
1022
subf_norm, can_access = osutils.normalized_filename(subf)
1023
if subf_norm != subf and can_access:
1024
if subf_norm not in dir_entry.children:
1025
fl.append(subf_norm)
882
and (subf not in dir_entry.children)):
1031
subp = pathjoin(path, subf)
887
subp = appendpath(path, subf)
1034
def _translate_ignore_rule(self, rule):
1035
"""Translate a single ignore rule to a regex.
1037
There are two types of ignore rules. Those that do not contain a / are
1038
matched against the tail of the filename (that is, they do not care
1039
what directory the file is in.) Rules which do contain a slash must
1040
match the entire path. As a special case, './' at the start of the
1041
string counts as a slash in the string but is removed before matching
1042
(e.g. ./foo.c, ./src/foo.c)
1044
:return: The translated regex.
1046
if rule[:2] in ('./', '.\\'):
1048
result = fnmatch.translate(rule[2:])
1049
elif '/' in rule or '\\' in rule:
1051
result = fnmatch.translate(rule)
1053
# default rule style.
1054
result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1055
assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1056
return "(" + result + ")"
1058
def _combine_ignore_rules(self, rules):
1059
"""Combine a list of ignore rules into a single regex object.
1061
Each individual rule is combined with | to form a big regex, which then
1062
has $ added to it to form something like ()|()|()$. The group index for
1063
each subregex's outermost group is placed in a dictionary mapping back
1064
to the rule. This allows quick identification of the matching rule that
1066
:return: a list of the compiled regex and the matching-group index
1067
dictionaries. We return a list because python complains if you try to
1068
combine more than 100 regexes.
1073
translated_rules = []
1075
translated_rule = self._translate_ignore_rule(rule)
1076
compiled_rule = re.compile(translated_rule)
1077
groups[next_group] = rule
1078
next_group += compiled_rule.groups
1079
translated_rules.append(translated_rule)
1080
if next_group == 99:
1081
result.append((re.compile("|".join(translated_rules)), groups))
1084
translated_rules = []
1085
if len(translated_rules):
1086
result.append((re.compile("|".join(translated_rules)), groups))
1089
891
def ignored_files(self):
1090
892
"""Yield list of PATH, IGNORE_PATTERN"""
1101
904
if hasattr(self, '_ignorelist'):
1102
905
return self._ignorelist
907
l = bzrlib.DEFAULT_IGNORE[:]
1105
908
if self.has_filename(bzrlib.IGNORE_FILENAME):
1106
909
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
1107
l.extend([line.rstrip("\n\r").decode('utf-8')
1108
for line in f.readlines()])
910
l.extend([line.rstrip("\n\r") for line in f.readlines()])
1109
911
self._ignorelist = l
1110
self._ignore_regex = self._combine_ignore_rules(l)
1113
def _get_ignore_rules_as_regex(self):
1114
"""Return a regex of the ignore rules and a mapping dict.
1116
:return: (ignore rules compiled regex, dictionary mapping rule group
1117
indices to original rule.)
1119
if getattr(self, '_ignorelist', None) is None:
1120
self.get_ignore_list()
1121
return self._ignore_regex
1123
915
def is_ignored(self, filename):
1124
916
r"""Check whether the filename matches an ignore pattern.
1138
930
# treat dotfiles correctly and allows * to match /.
1139
931
# Eventually it should be replaced with something more
1142
rules = self._get_ignore_rules_as_regex()
1143
for regex, mapping in rules:
1144
match = regex.match(filename)
1145
if match is not None:
1146
# one or more of the groups in mapping will have a non-None group
1148
groups = match.groups()
1149
rules = [mapping[group] for group in
1150
mapping if groups[group] is not None]
934
for pat in self.get_ignore_list():
935
if '/' in pat or '\\' in pat:
937
# as a special case, you can put ./ at the start of a
938
# pattern; this is good to match in the top-level
941
if (pat[:2] == './') or (pat[:2] == '.\\'):
945
if fnmatch.fnmatchcase(filename, newpat):
948
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
1154
953
def kind(self, file_id):
1155
954
return file_kind(self.id2abspath(file_id))
1185
981
self.branch.unlock()
1188
def get_physical_lock_status(self):
1189
return self._control_files.get_physical_lock_status()
1191
def _basis_inventory_name(self):
1192
return 'basis-inventory'
984
def _basis_inventory_name(self, revision_id):
985
return 'basis-inventory.%s' % revision_id
1194
987
@needs_write_lock
1195
def set_last_revision(self, new_revision):
988
def set_last_revision(self, new_revision, old_revision=None):
1196
989
"""Change the last revision in the working tree."""
990
self._remove_old_basis(old_revision)
1197
991
if self._change_last_revision(new_revision):
1198
992
self._cache_basis_inventory(new_revision)
1200
994
def _change_last_revision(self, new_revision):
1201
"""Template method part of set_last_revision to perform the change.
1203
This is used to allow WorkingTree3 instances to not affect branch
1204
when their last revision is set.
995
"""Template method part of set_last_revision to perform the change."""
1206
996
if new_revision is None:
1207
997
self.branch.set_revision_history([])
1218
1008
def _cache_basis_inventory(self, new_revision):
1219
1009
"""Cache new_revision as the basis inventory."""
1220
# TODO: this should allow the ready-to-use inventory to be passed in,
1221
# as commit already has that ready-to-use [while the format is the
1224
# this double handles the inventory - unpack and repack -
1225
# but is easier to understand. We can/should put a conditional
1226
# in here based on whether the inventory is in the latest format
1227
# - perhaps we should repack all inventories on a repository
1229
# the fast path is to copy the raw xml from the repository. If the
1230
# xml contains 'revision_id="', then we assume the right
1231
# revision_id is set. We must check for this full string, because a
1232
# root node id can legitimately look like 'revision_id' but cannot
1234
1011
xml = self.branch.repository.get_inventory_xml(new_revision)
1235
if not 'revision_id="' in xml.split('\n', 1)[0]:
1236
inv = self.branch.repository.deserialise_inventory(
1238
inv.revision_id = new_revision
1239
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1240
assert isinstance(xml, str), 'serialised xml must be bytestring.'
1241
path = self._basis_inventory_name()
1243
self._control_files.put(path, sio)
1012
path = self._basis_inventory_name(new_revision)
1013
self._control_files.put_utf8(path, xml)
1244
1014
except WeaveRevisionNotPresent:
1247
def read_basis_inventory(self):
1017
def _remove_old_basis(self, old_revision):
1018
"""Remove the old basis inventory 'old_revision'."""
1019
if old_revision is not None:
1021
path = self._basis_inventory_name(old_revision)
1022
path = self._control_files._escape(path)
1023
self._control_files._transport.delete(path)
1027
def read_basis_inventory(self, revision_id):
1248
1028
"""Read the cached basis inventory."""
1249
path = self._basis_inventory_name()
1250
return self._control_files.get(path).read()
1029
path = self._basis_inventory_name(revision_id)
1030
return self._control_files.get_utf8(path).read()
1252
1032
@needs_read_lock
1253
1033
def read_working_inventory(self):
1288
1068
# TODO: Perhaps make this just a warning, and continue?
1289
1069
# This tends to happen when
1290
1070
raise NotVersionedError(path=f)
1071
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1292
1073
# having remove it, it must be either ignored or unknown
1293
1074
if self.is_ignored(f):
1294
1075
new_status = 'I'
1296
1077
new_status = '?'
1297
show_status(new_status, inv[fid].kind, f, to_file=to_file)
1078
show_status(new_status, inv[fid].kind, quotefn(f))
1300
1081
self._write_inventory(inv)
1303
1084
def revert(self, filenames, old_tree=None, backups=True,
1304
1085
pb=DummyProgress()):
1305
1086
from transform import revert
1306
from conflicts import resolve
1307
1087
if old_tree is None:
1308
1088
old_tree = self.basis_tree()
1309
conflicts = revert(self, old_tree, filenames, backups, pb)
1089
revert(self, old_tree, filenames, backups, pb)
1310
1090
if not len(filenames):
1311
1091
self.set_pending_merges([])
1314
resolve(self, filenames, ignore_misses=True)
1317
# XXX: This method should be deprecated in favour of taking in a proper
1318
# new Inventory object.
1319
1093
@needs_write_lock
1320
1094
def set_inventory(self, new_inventory_list):
1321
1095
from bzrlib.inventory import (Inventory,
1367
1141
# of a nasty hack; probably it's better to have a transaction object,
1368
1142
# which can do some finalization when it's either successfully or
1369
1143
# unsuccessfully completed. (Denys's original patch did that.)
1370
# RBC 20060206 hooking into transaction will couple lock and transaction
1371
# wrongly. Hooking into unlock on the control files object is fine though.
1144
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1145
# wrongly. Hookinh into unllock on the control files object is fine though.
1373
1147
# TODO: split this per format so there is no ugly if block
1374
1148
if self._hashcache.needs_write and (
1379
1153
self._control_files._lock_count==3)):
1380
1154
self._hashcache.write()
1381
1155
# reverse order of locking.
1156
result = self._control_files.unlock()
1383
return self._control_files.unlock()
1158
self.branch.unlock()
1385
self.branch.unlock()
1387
1162
@needs_write_lock
1388
1163
def update(self):
1389
"""Update a working tree along its branch.
1391
This will update the branch if its bound too, which means we have multiple trees involved:
1392
The new basis tree of the master.
1393
The old basis tree of the branch.
1394
The old basis tree of the working tree.
1395
The current working tree state.
1396
pathologically all three may be different, and non ancestors of each other.
1397
Conceptually we want to:
1398
Preserve the wt.basis->wt.state changes
1399
Transform the wt.basis to the new master basis.
1400
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1401
Restore the wt.basis->wt.state changes.
1403
There isn't a single operation at the moment to do that, so we:
1404
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1405
Do a 'normal' merge of the old branch basis if it is relevant.
1407
old_tip = self.branch.update()
1408
if old_tip is not None:
1409
self.add_pending_merge(old_tip)
1410
1164
self.branch.lock_read()
1413
if self.last_revision() != self.branch.last_revision():
1414
# merge tree state up to new branch tip.
1415
basis = self.basis_tree()
1416
to_tree = self.branch.basis_tree()
1417
result += merge_inner(self.branch,
1421
self.set_last_revision(self.branch.last_revision())
1422
if old_tip and old_tip != self.last_revision():
1423
# our last revision was not the prior branch last revision
1424
# and we have converted that last revision to a pending merge.
1425
# base is somewhere between the branch tip now
1426
# and the now pending merge
1427
from bzrlib.revision import common_ancestor
1429
base_rev_id = common_ancestor(self.branch.last_revision(),
1431
self.branch.repository)
1432
except errors.NoCommonAncestor:
1434
base_tree = self.branch.repository.revision_tree(base_rev_id)
1435
other_tree = self.branch.repository.revision_tree(old_tip)
1436
result += merge_inner(self.branch,
1166
if self.last_revision() == self.branch.last_revision():
1168
basis = self.basis_tree()
1169
to_tree = self.branch.basis_tree()
1170
result = merge_inner(self.branch,
1174
self.set_last_revision(self.branch.last_revision())
1442
1177
self.branch.unlock()
1451
1186
self._set_inventory(inv)
1452
1187
mutter('wrote working inventory')
1454
def set_conflicts(self, arg):
1455
raise UnsupportedOperation(self.set_conflicts, self)
1457
def add_conflicts(self, arg):
1458
raise UnsupportedOperation(self.add_conflicts, self)
1461
def conflicts(self):
1462
conflicts = ConflictList()
1463
for conflicted in self._iter_conflicts():
1466
if file_kind(self.abspath(conflicted)) != "file":
1468
except errors.NoSuchFile:
1471
for suffix in ('.THIS', '.OTHER'):
1473
kind = file_kind(self.abspath(conflicted+suffix))
1476
except errors.NoSuchFile:
1480
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1481
conflicts.append(Conflict.factory(ctype, path=conflicted,
1482
file_id=self.path2id(conflicted)))
1486
1190
class WorkingTree3(WorkingTree):
1487
1191
"""This is the Format 3 working tree.
1517
1219
self._control_files.put_utf8('last-revision', revision_id)
1521
def set_conflicts(self, conflicts):
1522
self._put_rio('conflicts', conflicts.to_stanzas(),
1526
def add_conflicts(self, new_conflicts):
1527
conflict_set = set(self.conflicts())
1528
conflict_set.update(set(list(new_conflicts)))
1529
self.set_conflicts(ConflictList(sorted(conflict_set,
1530
key=Conflict.sort_key)))
1533
def conflicts(self):
1535
confile = self._control_files.get('conflicts')
1537
return ConflictList()
1539
if confile.next() != CONFLICT_HEADER_1 + '\n':
1540
raise ConflictFormatError()
1541
except StopIteration:
1542
raise ConflictFormatError()
1543
return ConflictList.from_stanzas(RioReader(confile))
1223
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1546
1224
def get_conflicted_stem(path):
1547
1225
for suffix in CONFLICT_SUFFIXES:
1548
1226
if path.endswith(suffix):
1643
1317
This format modified the hash cache from the format 1 hash cache.
1646
def get_format_description(self):
1647
"""See WorkingTreeFormat.get_format_description()."""
1648
return "Working tree format 2"
1650
def stub_initialize_remote(self, control_files):
1651
"""As a special workaround create critical control files for a remote working tree
1653
This ensures that it can later be updated and dealt with locally,
1654
since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with
1655
no working tree. (See bug #43064).
1659
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1661
control_files.put('inventory', sio)
1663
control_files.put_utf8('pending-merges', '')
1666
1320
def initialize(self, a_bzrdir, revision_id=None):
1667
1321
"""See WorkingTreeFormat.initialize()."""
1668
1322
if not isinstance(a_bzrdir.transport, LocalTransport):
1718
1372
class WorkingTreeFormat3(WorkingTreeFormat):
1719
1373
"""The second working tree format updated to record a format marker.
1722
- exists within a metadir controlling .bzr
1723
- includes an explicit version marker for the workingtree control
1724
files, separate from the BzrDir format
1725
- modifies the hash cache format
1727
- uses a LockDir to guard access to the repository
1375
This format modified the hash cache from the format 1 hash cache.
1730
1378
def get_format_string(self):
1731
1379
"""See WorkingTreeFormat.get_format_string()."""
1732
1380
return "Bazaar-NG Working Tree format 3"
1734
def get_format_description(self):
1735
"""See WorkingTreeFormat.get_format_description()."""
1736
return "Working tree format 3"
1738
_lock_file_name = 'lock'
1739
_lock_class = LockDir
1741
def _open_control_files(self, a_bzrdir):
1742
transport = a_bzrdir.get_workingtree_transport(None)
1743
return LockableFiles(transport, self._lock_file_name,
1746
1382
def initialize(self, a_bzrdir, revision_id=None):
1747
1383
"""See WorkingTreeFormat.initialize().
1749
revision_id allows creating a working tree at a different
1385
revision_id allows creating a working tree at a differnet
1750
1386
revision than the branch is at.
1752
1388
if not isinstance(a_bzrdir.transport, LocalTransport):
1753
1389
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1754
1390
transport = a_bzrdir.get_workingtree_transport(self)
1755
control_files = self._open_control_files(a_bzrdir)
1756
control_files.create_lock()
1757
control_files.lock_write()
1391
control_files = LockableFiles(transport, 'lock')
1758
1392
control_files.put_utf8('format', self.get_format_string())
1759
1393
branch = a_bzrdir.open_branch()
1760
1394
if revision_id is None:
1761
1395
revision_id = branch.last_revision()
1762
1396
inv = Inventory()
1763
wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1397
wt = WorkingTree3(a_bzrdir.root_transport.base,
1766
1400
_internal=True,
1769
_control_files=control_files)
1772
wt._write_inventory(inv)
1773
wt.set_root_id(inv.root.file_id)
1774
wt.set_last_revision(revision_id)
1775
wt.set_pending_merges([])
1776
build_tree(wt.basis_tree(), wt)
1779
control_files.unlock()
1403
wt._write_inventory(inv)
1404
wt.set_root_id(inv.root.file_id)
1405
wt.set_last_revision(revision_id)
1406
wt.set_pending_merges([])
1407
build_tree(wt.basis_tree(), wt)
1782
1410
def __init__(self):
1794
1422
raise NotImplementedError
1795
1423
if not isinstance(a_bzrdir.transport, LocalTransport):
1796
1424
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1797
control_files = self._open_control_files(a_bzrdir)
1798
return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1425
return WorkingTree3(a_bzrdir.root_transport.base,
1799
1426
_internal=True,
1802
_control_files=control_files)
1804
1430
def __str__(self):
1805
1431
return self.get_format_string()