38
39
# At the moment they may alias the inventory and have old copies of it in
39
40
# memory. (Now done? -- mbp 20060309)
42
from binascii import hexlify
41
43
from copy import deepcopy
42
44
from cStringIO import StringIO
49
52
from bzrlib.atomicfile import AtomicFile
50
53
from bzrlib.branch import (Branch,
55
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
52
56
import bzrlib.bzrdir as bzrdir
53
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
54
58
import bzrlib.errors as errors
55
59
from bzrlib.errors import (BzrCheckError,
58
63
WeaveRevisionNotPresent,
62
MergeModifiedFormatError)
67
MergeModifiedFormatError,
63
70
from bzrlib.inventory import InventoryEntry, Inventory
64
71
from bzrlib.lockable_files import LockableFiles, TransportLock
65
72
from bzrlib.lockdir import LockDir
85
92
from bzrlib.progress import DummyProgress, ProgressPhase
86
93
from bzrlib.revision import NULL_REVISION
87
from bzrlib.rio import RioReader, RioWriter, Stanza
94
from bzrlib.rio import RioReader, rio_file, Stanza
88
95
from bzrlib.symbol_versioning import *
89
96
from bzrlib.textui import show_status
91
from bzrlib.trace import mutter
92
98
from bzrlib.transform import build_tree
99
from bzrlib.trace import mutter, note
93
100
from bzrlib.transport import get_transport
94
101
from bzrlib.transport.local import LocalTransport
96
103
import bzrlib.xml5
106
# the regex here does the following:
107
# 1) remove any weird characters; we don't escape them but rather
109
# 2) match leading '.'s to make it not hidden
110
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
111
_gen_id_suffix = None
115
def _next_id_suffix():
116
"""Create a new file id suffix that is reasonably unique.
118
On the first call we combine the current time with 64 bits of randomness
119
to give a highly probably globally unique number. Then each call in the same
120
process adds 1 to a serial number we append to that unique value.
122
# XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather
123
# than having to move the id randomness out of the inner loop like this.
124
# XXX TODO: for the global randomness this uses we should add the thread-id
125
# before the serial #.
126
global _gen_id_suffix, _gen_id_serial
127
if _gen_id_suffix is None:
128
_gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
130
return _gen_id_suffix + str(_gen_id_serial)
99
133
def gen_file_id(name):
100
"""Return new file id.
102
This should probably generate proper UUIDs, but for the moment we
103
cope with just randomness because running uuidgen every time is
106
from binascii import hexlify
107
from time import time
110
idx = name.rfind('/')
112
name = name[idx+1 : ]
113
idx = name.rfind('\\')
115
name = name[idx+1 : ]
117
# make it not a hidden file
118
name = name.lstrip('.')
120
# remove any wierd characters; we don't escape them but rather
122
name = re.sub(r'[^\w.]', '', name)
124
s = hexlify(rand_bytes(8))
125
return '-'.join((name, compact_date(time()), s))
134
"""Return new file id for the basename 'name'.
136
The uniqueness is supplied from _next_id_suffix.
138
# XXX TODO: squash the filename to lowercase.
139
# XXX TODO: truncate the filename to something like 20 or 30 chars.
140
# XXX TODO: consider what to do with ids that look like illegal filepaths
141
# on platforms we support.
142
return _gen_file_id_re.sub('', name) + _next_id_suffix()
128
145
def gen_root_id():
234
251
if deprecated_passed(branch):
235
252
if not _internal:
236
253
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
237
" Please use bzrdir.open_workingtree() or WorkingTree.open().",
254
" Please use bzrdir.open_workingtree() or"
255
" WorkingTree.open().",
238
256
DeprecationWarning,
259
self._branch = branch
243
self.branch = self.bzrdir.open_branch()
261
self._branch = self.bzrdir.open_branch()
244
262
assert isinstance(self.branch, Branch), \
245
263
"branch %r is not a Branch" % self.branch
246
264
self.basedir = realpath(basedir)
277
295
self._set_inventory(_inventory)
298
fget=lambda self: self._branch,
299
doc="""The branch this WorkingTree is connected to.
301
This cannot be set - it is reflective of the actual disk structure
302
the working tree has been constructed from.
305
def break_lock(self):
306
"""Break a lock if one is present from another instance.
308
Uses the ui factory to ask for confirmation if the lock may be from
311
This will probe the repository for its lock as well.
313
self._control_files.break_lock()
314
self.branch.break_lock()
279
316
def _set_inventory(self, inv):
280
317
self._inventory = inv
281
318
self.path2id = self._inventory.path2id
283
320
def is_control_filename(self, filename):
284
321
"""True if filename is the name of a control file in this tree.
323
:param filename: A filename within the tree. This is a relative path
324
from the root of this tree.
286
326
This is true IF and ONLY IF the filename is part of the meta data
287
327
that bzr controls in this tree. I.E. a random .bzr directory placed
288
328
on disk will not be a control file for this tree.
291
self.bzrdir.transport.relpath(self.abspath(filename))
293
except errors.PathNotChild:
330
return self.bzrdir.is_control_filename(filename)
297
333
def open(path=None, _unsupported=False):
399
435
return bzrdir.BzrDir.create_standalone_workingtree(directory)
401
def relpath(self, abs):
402
"""Return the local path portion from a given absolute path."""
403
return relpath(self.basedir, abs)
437
def relpath(self, path):
438
"""Return the local path portion from a given path.
440
The path may be absolute or relative. If its a relative path it is
441
interpreted relative to the python current working directory.
443
return relpath(self.basedir, path)
405
445
def has_filename(self, filename):
406
446
return bzrlib.osutils.lexists(self.abspath(filename))
563
603
'i.e. regular file, symlink or directory): %s' % quotefn(f))
565
605
if file_id is None:
566
file_id = gen_file_id(f)
567
inv.add_path(f, kind=kind, file_id=file_id)
606
inv.add_path(f, kind=kind)
608
inv.add_path(f, kind=kind, file_id=file_id)
569
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
570
610
self._write_inventory(inv)
572
612
@needs_write_lock
608
648
@needs_write_lock
609
649
def set_merge_modified(self, modified_hashes):
611
my_file.write(MERGE_MODIFIED_HEADER_1 + '\n')
612
writer = RioWriter(my_file)
613
for file_id, hash in modified_hashes.iteritems():
614
s = Stanza(file_id=file_id, hash=hash)
615
writer.write_stanza(s)
617
self._control_files.put('merge-hashes', my_file)
651
for file_id, hash in modified_hashes.iteritems():
652
yield Stanza(file_id=file_id, hash=hash)
653
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
656
def _put_rio(self, filename, stanzas, header):
657
my_file = rio_file(stanzas, header)
658
self._control_files.put(filename, my_file)
620
661
def merge_modified(self):
861
904
if not self.is_ignored(subp):
907
@deprecated_method(zero_eight)
864
908
def iter_conflicts(self):
909
"""List all files in the tree that have text or content conflicts.
910
DEPRECATED. Use conflicts instead."""
911
return self._iter_conflicts()
913
def _iter_conflicts(self):
865
914
conflicted = set()
866
915
for path in (s[0] for s in self.list_files()):
867
916
stem = get_conflicted_stem(path)
932
981
subp = appendpath(path, subf)
984
def _translate_ignore_rule(self, rule):
985
"""Translate a single ignore rule to a regex.
987
There are two types of ignore rules. Those that do not contain a / are
988
matched against the tail of the filename (that is, they do not care
989
what directory the file is in.) Rules which do contain a slash must
990
match the entire path. As a special case, './' at the start of the
991
string counts as a slash in the string but is removed before matching
992
(e.g. ./foo.c, ./src/foo.c)
994
:return: The translated regex.
996
if rule[:2] in ('./', '.\\'):
998
result = fnmatch.translate(rule[2:])
999
elif '/' in rule or '\\' in rule:
1001
result = fnmatch.translate(rule)
1003
# default rule style.
1004
result = "(?:.*/)?(?!.*/)" + fnmatch.translate(rule)
1005
assert result[-1] == '$', "fnmatch.translate did not add the expected $"
1006
return "(" + result + ")"
1008
def _combine_ignore_rules(self, rules):
1009
"""Combine a list of ignore rules into a single regex object.
1011
Each individual rule is combined with | to form a big regex, which then
1012
has $ added to it to form something like ()|()|()$. The group index for
1013
each subregex's outermost group is placed in a dictionary mapping back
1014
to the rule. This allows quick identification of the matching rule that
1016
:return: a list of the compiled regex and the matching-group index
1017
dictionaries. We return a list because python complains if you try to
1018
combine more than 100 regexes.
1023
translated_rules = []
1025
translated_rule = self._translate_ignore_rule(rule)
1026
compiled_rule = re.compile(translated_rule)
1027
groups[next_group] = rule
1028
next_group += compiled_rule.groups
1029
translated_rules.append(translated_rule)
1030
if next_group == 99:
1031
result.append((re.compile("|".join(translated_rules)), groups))
1034
translated_rules = []
1035
if len(translated_rules):
1036
result.append((re.compile("|".join(translated_rules)), groups))
936
1039
def ignored_files(self):
937
1040
"""Yield list of PATH, IGNORE_PATTERN"""
954
1056
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
955
1057
l.extend([line.rstrip("\n\r") for line in f.readlines()])
956
1058
self._ignorelist = l
1059
self._ignore_regex = self._combine_ignore_rules(l)
1062
def _get_ignore_rules_as_regex(self):
1063
"""Return a regex of the ignore rules and a mapping dict.
1065
:return: (ignore rules compiled regex, dictionary mapping rule group
1066
indices to original rule.)
1068
if getattr(self, '_ignorelist', None) is None:
1069
self.get_ignore_list()
1070
return self._ignore_regex
960
1072
def is_ignored(self, filename):
961
1073
r"""Check whether the filename matches an ignore pattern.
975
1087
# treat dotfiles correctly and allows * to match /.
976
1088
# Eventually it should be replaced with something more
979
for pat in self.get_ignore_list():
980
if '/' in pat or '\\' in pat:
982
# as a special case, you can put ./ at the start of a
983
# pattern; this is good to match in the top-level
986
if (pat[:2] == './') or (pat[:2] == '.\\'):
990
if fnmatch.fnmatchcase(filename, newpat):
993
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
1091
rules = self._get_ignore_rules_as_regex()
1092
for regex, mapping in rules:
1093
match = regex.match(filename)
1094
if match is not None:
1095
# one or more of the groups in mapping will have a non-None group
1097
groups = match.groups()
1098
rules = [mapping[group] for group in
1099
mapping if groups[group] is not None]
998
1103
def kind(self, file_id):
999
1104
return file_kind(self.id2abspath(file_id))
1130
1241
def revert(self, filenames, old_tree=None, backups=True,
1131
1242
pb=DummyProgress()):
1132
1243
from transform import revert
1244
from conflicts import resolve
1133
1245
if old_tree is None:
1134
1246
old_tree = self.basis_tree()
1135
revert(self, old_tree, filenames, backups, pb)
1247
conflicts = revert(self, old_tree, filenames, backups, pb)
1136
1248
if not len(filenames):
1137
1249
self.set_pending_merges([])
1252
resolve(self, filenames, ignore_misses=True)
1255
# XXX: This method should be deprecated in favour of taking in a proper
1256
# new Inventory object.
1139
1257
@needs_write_lock
1140
1258
def set_inventory(self, new_inventory_list):
1141
1259
from bzrlib.inventory import (Inventory,
1272
1389
self._set_inventory(inv)
1273
1390
mutter('wrote working inventory')
1392
def set_conflicts(self, arg):
1393
raise UnsupportedOperation(self.set_conflicts, self)
1396
def conflicts(self):
1397
conflicts = ConflictList()
1398
for conflicted in self._iter_conflicts():
1401
if file_kind(self.abspath(conflicted)) != "file":
1404
if e.errno == errno.ENOENT:
1409
for suffix in ('.THIS', '.OTHER'):
1411
kind = file_kind(self.abspath(conflicted+suffix))
1413
if e.errno == errno.ENOENT:
1421
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1422
conflicts.append(Conflict.factory(ctype, path=conflicted,
1423
file_id=self.path2id(conflicted)))
1276
1427
class WorkingTree3(WorkingTree):
1277
1428
"""This is the Format 3 working tree.
1307
1458
self._control_files.put_utf8('last-revision', revision_id)
1311
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1462
def set_conflicts(self, conflicts):
1463
self._put_rio('conflicts', conflicts.to_stanzas(),
1467
def conflicts(self):
1469
confile = self._control_files.get('conflicts')
1471
return ConflictList()
1473
if confile.next() != CONFLICT_HEADER_1 + '\n':
1474
raise ConflictFormatError()
1475
except StopIteration:
1476
raise ConflictFormatError()
1477
return ConflictList.from_stanzas(RioReader(confile))
1312
1480
def get_conflicted_stem(path):
1313
1481
for suffix in CONFLICT_SUFFIXES:
1314
1482
if path.endswith(suffix):
1405
1577
This format modified the hash cache from the format 1 hash cache.
1580
def get_format_description(self):
1581
"""See WorkingTreeFormat.get_format_description()."""
1582
return "Working tree format 2"
1584
def stub_initialize_remote(self, control_files):
1585
"""As a special workaround create critical control files for a remote working tree
1587
This ensures that it can later be updated and dealt with locally,
1588
since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with
1589
no working tree. (See bug #43064).
1593
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1595
control_files.put('inventory', sio)
1597
control_files.put_utf8('pending-merges', '')
1408
1600
def initialize(self, a_bzrdir, revision_id=None):
1409
1601
"""See WorkingTreeFormat.initialize()."""
1410
1602
if not isinstance(a_bzrdir.transport, LocalTransport):
1492
1688
transport = a_bzrdir.get_workingtree_transport(self)
1493
1689
control_files = self._open_control_files(a_bzrdir)
1494
1690
control_files.create_lock()
1691
control_files.lock_write()
1495
1692
control_files.put_utf8('format', self.get_format_string())
1496
1693
branch = a_bzrdir.open_branch()
1497
1694
if revision_id is None:
1505
1702
_bzrdir=a_bzrdir,
1506
1703
_control_files=control_files)
1507
wt._write_inventory(inv)
1508
wt.set_root_id(inv.root.file_id)
1509
wt.set_last_revision(revision_id)
1510
wt.set_pending_merges([])
1511
build_tree(wt.basis_tree(), wt)
1706
wt._write_inventory(inv)
1707
wt.set_root_id(inv.root.file_id)
1708
wt.set_last_revision(revision_id)
1709
wt.set_pending_merges([])
1710
build_tree(wt.basis_tree(), wt)
1713
control_files.unlock()
1514
1716
def __init__(self):