27
28
import bzrlib.inventory as inventory
28
29
from bzrlib.trace import mutter, note
29
30
from bzrlib.osutils import (isdir, quotefn,
30
rename, splitpath, sha_file, appendpath,
31
rename, splitpath, sha_file,
32
file_kind, abspath, normpath, pathjoin)
32
33
import bzrlib.errors as errors
33
34
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
35
NoSuchRevision, HistoryMissing, NotBranchError,
487
488
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
488
489
raise NotImplementedError('store_revision_signature is abstract')
491
def fileid_involved_between_revs(self, from_revid, to_revid):
492
""" This function returns the file_id(s) involved in the
493
changes between the from_revid revision and the to_revid
496
raise NotImplementedError('fileid_involved_between_revs is abstract')
498
def fileid_involved(self, last_revid=None):
499
""" This function returns the file_id(s) involved in the
500
changes up to the revision last_revid
501
If no parametr is passed, then all file_id[s] present in the
502
repository are returned
504
raise NotImplementedError('fileid_involved is abstract')
506
def fileid_involved_by_set(self, changes):
507
""" This function returns the file_id(s) involved in the
508
changes present in the set 'changes'
510
raise NotImplementedError('fileid_involved_by_set is abstract')
512
def fileid_involved_between_revs(self, from_revid, to_revid):
513
""" This function returns the file_id(s) involved in the
514
changes between the from_revid revision and the to_revid
517
raise NotImplementedError('fileid_involved_between_revs is abstract')
519
def fileid_involved(self, last_revid=None):
520
""" This function returns the file_id(s) involved in the
521
changes up to the revision last_revid
522
If no parametr is passed, then all file_id[s] present in the
523
repository are returned
525
raise NotImplementedError('fileid_involved is abstract')
527
def fileid_involved_by_set(self, changes):
528
""" This function returns the file_id(s) involved in the
529
changes present in the set 'changes'
531
raise NotImplementedError('fileid_involved_by_set is abstract')
490
534
class BzrBranch(Branch):
491
535
"""A branch stored in the actual filesystem.
563
611
self._make_control()
564
612
self._check_format(relax_version_check)
566
615
def get_store(name, compressed=True, prefixed=False):
567
# FIXME: This approach of assuming stores are all entirely compressed
568
# or entirely uncompressed is tidy, but breaks upgrade from
569
# some existing branches where there's a mixture; we probably
570
# still want the option to look for both.
571
616
relpath = self._rel_controlfilename(unicode(name))
572
617
store = TextStore(self._transport.clone(relpath),
618
dir_mode=self._dir_mode,
619
file_mode=self._file_mode,
573
620
prefixed=prefixed,
574
621
compressed=compressed)
575
#if self._transport.should_cache():
576
# cache_path = os.path.join(self.cache_root, name)
577
# os.mkdir(cache_path)
578
# store = bzrlib.store.CachedStore(store, cache_path)
581
624
def get_weave(name, prefixed=False):
582
625
relpath = self._rel_controlfilename(unicode(name))
583
ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
626
ws = WeaveStore(self._transport.clone(relpath),
628
dir_mode=self._dir_mode,
629
file_mode=self._file_mode)
584
630
if self._transport.should_cache():
585
631
ws.enable_cache = True
753
799
f = codecs.getwriter('utf-8')(f, errors='replace')
754
800
path = self._rel_controlfilename(path)
755
801
ctrl_files.append((path, f))
756
self._transport.put_multi(ctrl_files)
802
self._transport.put_multi(ctrl_files, mode=self._file_mode)
804
def _find_modes(self, path=None):
805
"""Determine the appropriate modes for files and directories."""
808
path = self._rel_controlfilename('')
809
st = self._transport.stat(path)
810
except errors.TransportNotPossible:
811
self._dir_mode = 0755
812
self._file_mode = 0644
814
self._dir_mode = st.st_mode & 07777
815
# Remove the sticky and execute bits for files
816
self._file_mode = self._dir_mode & ~07111
817
if not self._set_dir_mode:
818
self._dir_mode = None
819
if not self._set_file_mode:
820
self._file_mode = None
758
822
def _make_control(self):
759
823
from bzrlib.inventory import Inventory
771
835
bzrlib.weavefile.write_weave_v5(Weave(), sio)
772
836
empty_weave = sio.getvalue()
774
dirs = [[], 'revision-store', 'weaves']
838
cfn = self._rel_controlfilename
839
# Since we don't have a .bzr directory, inherit the
840
# mode from the root directory
841
self._find_modes(u'.')
843
dirs = ['', 'revision-store', 'weaves']
775
844
files = [('README',
776
845
"This is a Bazaar-NG control directory.\n"
777
846
"Do not change any files in this directory.\n"),
1091
1158
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1092
1159
revision_id, "sig")
1161
def fileid_involved_between_revs(self, from_revid, to_revid):
1162
"""Find file_id(s) which are involved in the changes between revisions.
1164
This determines the set of revisions which are involved, and then
1165
finds all file ids affected by those revisions.
1167
# TODO: jam 20060119 This code assumes that w.inclusions will
1168
# always be correct. But because of the presence of ghosts
1169
# it is possible to be wrong.
1170
# One specific example from Robert Collins:
1171
# Two branches, with revisions ABC, and AD
1172
# C is a ghost merge of D.
1173
# Inclusions doesn't recognize D as an ancestor.
1174
# If D is ever merged in the future, the weave
1175
# won't be fixed, because AD never saw revision C
1176
# to cause a conflict which would force a reweave.
1177
w = self._get_inventory_weave()
1178
from_set = set(w.inclusions([w.lookup(from_revid)]))
1179
to_set = set(w.inclusions([w.lookup(to_revid)]))
1180
included = to_set.difference(from_set)
1181
changed = map(w.idx_to_name, included)
1182
return self._fileid_involved_by_set(changed)
1184
def fileid_involved(self, last_revid=None):
1185
"""Find all file_ids modified in the ancestry of last_revid.
1187
:param last_revid: If None, last_revision() will be used.
1189
w = self._get_inventory_weave()
1191
changed = set(w._names)
1193
included = w.inclusions([w.lookup(last_revid)])
1194
changed = map(w.idx_to_name, included)
1195
return self._fileid_involved_by_set(changed)
1197
def fileid_involved_by_set(self, changes):
1198
"""Find all file_ids modified by the set of revisions passed in.
1200
:param changes: A set() of revision ids
1202
# TODO: jam 20060119 This line does *nothing*, remove it.
1203
# or better yet, change _fileid_involved_by_set so
1204
# that it takes the inventory weave, rather than
1205
# pulling it out by itself.
1206
w = self._get_inventory_weave()
1207
return self._fileid_involved_by_set(changes)
1209
def _fileid_involved_by_set(self, changes):
1210
"""Find the set of file-ids affected by the set of revisions.
1212
:param changes: A set() of revision ids.
1213
:return: A set() of file ids.
1215
This peaks at the Weave, interpreting each line, looking to
1216
see if it mentions one of the revisions. And if so, includes
1217
the file id mentioned.
1218
This expects both the Weave format, and the serialization
1219
to have a single line per file/directory, and to have
1220
fileid="" and revision="" on that line.
1222
assert self._branch_format in (5, 6), \
1223
"fileid_involved only supported for branches which store inventory as xml"
1225
w = self._get_inventory_weave()
1227
for line in w._weave:
1229
# it is ugly, but it is due to the weave structure
1230
if not isinstance(line, basestring): continue
1232
start = line.find('file_id="')+9
1233
if start < 9: continue
1234
end = line.find('"', start)
1236
file_id = xml.sax.saxutils.unescape(line[start:end])
1238
# check if file_id is already present
1239
if file_id in file_ids: continue
1241
start = line.find('revision="')+10
1242
if start < 10: continue
1243
end = line.find('"', start)
1245
revision_id = xml.sax.saxutils.unescape(line[start:end])
1247
if revision_id in changes:
1248
file_ids.add(file_id)
1095
1253
class ScratchBranch(BzrBranch):
1096
1254
"""Special test class: a branch that cleans up after itself.