91
106
def render_macro(self, req, name, content):
92
107
assert name == 'Branches'
93
# This is pretty braindead but adding an option for this is too.
94
manager = versioncontrol.RepositoryManager(self.env)
95
if manager.repository_type != 'bzr':
96
raise core.TracError('Configured repo is not a bzr repo')
97
temp_branch = bzrlib_branch.Branch.open(manager.repository_dir)
98
trans = temp_branch.repository.bzrdir.root_transport
99
branches = sorted(self._get_branches(trans))
100
# Slight hack. We know all these branches are in the same
101
# repo, so we can read lock that once.
102
repo = bzrdir.BzrDir.open_from_transport(trans).open_repository()
108
if trac_version >= (0,11):
109
chrome = Chrome(self.env)
110
dateinfo = chrome.populate_data(req, {})['dateinfo']
112
def dateinfo(timestamp):
113
from trac.util.datefmt import format_datetime, pretty_timedelta
114
date = format_datetime(timestamp)
115
age = pretty_timedelta(timestamp)
116
return html.SPAN(age, title=date)
117
repo = self.env.get_repository(req.authname)
118
if not isinstance(repo, BzrRepository):
119
raise core.TracError('Configured repository is not a bzr repository')
121
branches = sorted(repo._get_branches())
123
for loc, target in branches:
124
revid = target.last_revision()
125
revno = target.revision_id_to_revno(revid)
126
rev = repo.string_rev(target, revid)
127
revision = target.repository.get_revision(revid)
128
timestamp = revision.timestamp
129
timestamp = dateinfo(timestamp)
130
rows.append(html.TR([html.TD(x, class_=c) for c, x in [
131
('name', html.A(loc, class_='dir',
132
href=req.href.browser(loc))),
133
('nick', target.nick),
134
('rev', html.A('[%d]'%revno, href=req.href.changeset(rev))),
136
('change', revision.message),
105
138
return html.TABLE(class_='listing')(
107
html.TH('Path'), html.TH('Nick'),
108
html.TH('Last Change'))),
139
html.THEAD(html.TR([html.TH(x) for x in [
140
'Path', 'Nick', 'Revision', 'Age', 'Last Change'
148
def match_property(self, name, mode):
149
if name == 'parents' and mode == 'revprop':
153
def render_property(self, name, mode, context, props):
154
if name == 'parents' and mode == 'revprop':
156
if context.resource.realm == 'changeset':
158
path = context.req.args.get('new_path')
162
path = path.strip('/')
165
content = html.TABLE(class_='wiki parents')(
166
html.THEAD(html.TR([html.TH(h) for h in
167
['Rev', 'Tree', 'Chgset']])),
111
html.TD(html.A(loc, href=req.href.browser(
112
rev=':%s' % (urllib.quote(loc, ''),
114
html.TD(target.nick),
116
datetime.datetime.fromtimestamp(
118
target.last_revision()).timestamp
121
for loc, target in branches]))
170
html.TD(p.split(',')[-1]),
171
html.TD(html.A('@%s' % p,
172
href=context.href.browser(path, rev=p))),
173
html.TD(html.A('[%s]' % p,
174
href=context.href.changeset(p, path))))
175
for p in props[name].split('\n')]))
179
class LockedBranches(object):
181
"""Keep track of locks and ensure they get unlocked one day.
182
This deals with the fact that Trac doesn't always call the repository
183
close method, and that a __del__ method in the repository itself causes
184
trouble as described in https://bugs.launchpad.net/trac-bzr/+bug/484324"""
187
self._locked_branches = []
190
# XXX Eeeeeww. Unfortunately for us trac does not actually call the
191
# close method of BzrRepository. So we do this. Quite silly, since
192
# bzr does the same thing (printing a warning...)
195
def append(self, branch):
196
self._locked_branches.append(branch)
199
for branch in self._locked_branches:
201
self._locked_branches = []
126
204
class BzrRepository(versioncontrol.Repository):
186
276
branch is None or a bzr branch object.
188
278
Supported syntax:
279
- "123" is revno 123 in the default branch.
280
- "revid" is a revid in the default branch.
189
281
- "spork,123" is revno 123 in the spork branch.
190
282
- "spork,revid" is a revid in the spork branch.
191
283
(currently revid is assumed to be in the branch ancestry!)
284
- "spork," is latest revision in the spork branch.
285
- "spork,1.2.3" is either revno or revid 1.2.3 in the spork branch.
193
287
Branch paths and revids are urlencoded.
195
# Try integer revno to revid conversion.
197
raise versioncontrol.NoSuchChangeset(rev)
199
# Try path-to-branch-in-repo.
201
split = rev.split(',')
203
raise versioncontrol.NoSuchChangeset(rev)
290
# Make sure our rev is a string
293
# split branch,revid; no branch denotes branch at root of repository
294
split = rev.split(',')
298
elif len(split) == 2:
204
299
rev_branch, rev_rev = split
301
raise versioncontrol.NoSuchChangeset(rev)
303
# unquote revision part, and treat special cases of current: and null:
304
rev_rev = urllib.unquote(rev_rev)
305
if len(split) == 1 and rev_rev in (CURRENT_REVISION, NULL_REVISION):
309
branch = self.get_branch(rev_branch)
310
except errors.NotBranchError:
311
raise versioncontrol.NoSuchChangeset(rev)
314
revid = branch.last_revision()
315
elif rev_rev.isdigit():
206
branch = self.get_branch(rev_branch)
207
except errors.NotBranchError:
317
revid = branch.get_rev_id(int(rev_rev))
318
except errors.NoSuchRevision:
208
319
raise versioncontrol.NoSuchChangeset(rev)
211
if rev_rev.isdigit():
213
revid = branch.get_rev_id(int(rev_rev))
214
except errors.NoSuchRevision:
215
raise versioncontrol.NoSuchChangeset(rev)
217
dotted = rev_rev.split('.')
218
for segment in dotted:
219
if not segment.isdigit():
220
revid = urllib.unquote(rev_rev)
223
cache = self.get_branch_cache(branch)
224
revid = cache.revid_from_dotted(rev_rev)
227
revid = urllib.unquote(rev_rev)
321
dotted = rev_rev.split('.')
322
for segment in dotted:
323
if not segment.isdigit():
229
revid = branch.last_revision()
234
revid = urllib.unquote(rev)
235
if revid in ('current:', 'null:'):
238
if self.repo.has_revision(revid):
241
# Unsupported format.
242
raise versioncontrol.NoSuchChangeset(rev)
245
# XXX Eeeeeww. Unfortunately for us trac does not actually call the
246
# close method. So we do this. Quite silly, since bzr does the same
247
# thing (printing a warning...)
327
cache = self.get_branch_cache(branch)
328
revid = cache.revid_from_dotted(rev_rev)
330
self.log.warn('%r is no dotted revno, will interpret as revid.', rev_rev)
250
334
# Trac api methods.
253
337
"""Release our branches. Trac does not *have* to call this!"""
254
for branch in self._locked_branches:
338
self._locked_branches.unlock()
257
340
def get_branch(self, location):
258
341
if location in self._branch_cache:
531
612
new_branch, new_revid = self._parse_rev(new_rev)
532
613
old_tree = old_branch.repository.revision_tree(old_revid)
533
614
new_tree = new_branch.repository.revision_tree(new_revid)
534
delta = new_tree.changes_from(old_tree)
615
prefix = self.branch_path(new_branch)
616
subdir = new_path[len(prefix)+1:]
617
delta = new_tree.changes_from(old_tree, specific_files=[subdir])
535
618
for path, file_id, kind in delta.added:
619
path = osutils.pathjoin(prefix, path)
536
620
entry = new_tree.inventory[file_id]
537
621
node = NODE_MAP[kind](self, new_branch, new_tree, entry, path)
538
cur_path = new_tree.id2path(file_id)
539
node._history_cache[(new_revid, old_revid, file_id)] = \
540
cur_path, new_rev, versioncontrol.Changeset.ADD
541
622
yield None, node, node.kind, versioncontrol.Changeset.ADD
542
623
for path, file_id, kind in delta.removed:
624
path = osutils.pathjoin(prefix, path)
543
625
entry = old_tree.inventory[file_id]
544
626
node = NODE_MAP[kind](self, old_branch, old_tree, entry, path)
545
627
yield node, None, node.kind, versioncontrol.Changeset.DELETE
546
628
for oldpath, newpath, file_id, kind, textmod, metamod in delta.renamed:
629
oldpath = osutils.pathjoin(prefix, oldpath)
630
newpath = osutils.pathjoin(prefix, newpath)
547
631
oldnode = NODE_MAP[kind](self, old_branch, old_tree,
548
632
old_tree.inventory[file_id], oldpath)
549
633
newnode = NODE_MAP[kind](self, new_branch, new_tree,
688
759
result['executable'] = 'True'
691
def _merging_history(self):
692
"""Iterate through history revisions that merged changes to this node
694
This includes all revisions in which the revision_id changed.
695
It may also include a few revisions in which the revision_id did not
696
change, if the modification was subsequently undone.
698
weave = self.tree._get_weave(self.entry.file_id)
699
file_ancestry = weave.get_ancestry(self.entry.revision)
700
# Can't use None here, because it's a legitimate revision id.
701
last_yielded = 'bogus:'
702
for num, revision_id, depth, revno, eom in \
703
self.bzr_repo.sorted_revision_history(self.branch):
705
last_mainline = revision_id
706
if last_mainline == last_yielded:
708
if revision_id in file_ancestry:
710
last_yielded = last_mainline
713
def get_history(self, limit=None):
716
yields (path, revid, chg) tuples.
718
path is the path to this entry.
720
revid is the revid string. It is the revision in which the change
721
was applied to the branch, not necessarily the revision that originated
722
the change. In SVN terms, it is a changeset, not a file revision.
724
chg is a Changeset.ACTION thing.
726
First thing should be for the current revision.
728
limit is an int cap on how many entries to return.
730
history_iter = self._get_history()
734
return (y for x, y in izip(range(limit), history_iter))
736
def _get_history(self, limit=None):
737
file_id = self.entry.file_id
739
history = list(self._merging_history())
740
cache = self.bzr_repo.get_branch_cache(self.branch)
741
trees = cache.revision_trees(history)
742
for prev_tree in trees:
743
previous_revision = prev_tree.get_revision_id()
745
prev_file_revision = prev_tree.inventory[file_id].revision
746
except errors.NoSuchId:
747
prev_file_revision = None
748
if (revision is not None and
749
prev_file_revision != file_revision):
750
path, rev_str, chg = \
751
self.get_change(revision, previous_revision, file_id)
752
branch_revision = self.bzr_repo.string_rev(self.branch,
754
yield (osutils.pathjoin(self.root_path, path),
755
branch_revision, chg)
756
if prev_file_revision is None:
758
revision = previous_revision
759
file_revision = prev_file_revision
761
def get_change(self, revision, previous_revision, file_id):
762
key = (revision, previous_revision, file_id)
763
if key not in self._history_cache or False:
764
self._history_cache[key] = self.calculate_history(revision,
765
previous_revision, file_id)
766
return self._history_cache[key]
768
def calculate_history(self, revision, previous_revision, file_id):
769
cache = self.bzr_repo.get_branch_cache(self.branch)
770
tree = cache.revision_tree(revision)
771
current_entry = tree.inventory[file_id]
772
current_path = tree.id2path(file_id)
773
if previous_revision not in (None, 'null:'):
774
previous_tree = cache.revision_tree(previous_revision)
775
previous_entry = previous_tree.inventory[file_id]
777
previous_entry = None
778
# We should only get revisions in the ancestry for which
779
# we exist, so this should succeed..
780
return self.compare_entries(current_path, current_entry,
783
def compare_entries(self, current_path, current_entry, previous_entry):
784
diff = current_entry.describe_change(previous_entry, current_entry)
785
rev = self.bzr_repo.string_rev(self.branch, current_entry.revision)
787
return current_path, rev, self._diff_map[diff]
789
raise Exception('unknown describe_change %r' % (diff,))
791
def get_last_modified(self):
792
return self.tree.get_file_mtime(self.entry.file_id)
795
class BzrDirNode(BzrVersionedNode):
799
kind = versioncontrol.Node.DIRECTORY
801
def __init__(self, bzr_repo, branch, revisiontree, entry, path,
803
BzrVersionedNode.__init__(self, bzr_repo, branch, revisiontree, entry,
806
ancestry = self.repo.get_ancestry(revisiontree.get_revision_id())
809
best = self._get_cache(self.revcache, ancestry, entry)
810
self._orig_rev = ancestry[best]
811
self.rev = bzr_repo.string_rev(self.branch, (ancestry[best]))
813
self.revcache = revcache
814
self._orig_rev = revcache[entry.file_id]
815
self.rev = bzr_repo.string_rev(self.branch, self._orig_rev)
818
return 'BzrDirNode(path=%r, relpath=%r)' % (self.path, self.entry.name)
821
def _get_cache(cls, cache, ancestry, entry, ancestry_idx=None):
822
"""Populate a file_id <-> revision_id mapping.
824
This mapping is different from InventoryEntry.revision, but only for
825
directories. In this scheme, directories are considered modified
826
if their contents are modified.
828
The revision ids are not guaranteed to be in the mainline revision
831
cache: The cache to populate
832
ancestry: A topologically-sorted list of revisions, with more recent
833
revisions having lower indexes.
834
entry: The InventoryEntry to start at
835
ancestry_idx: A mapping of revision_id <-> ancestry index.
837
if ancestry_idx is None:
838
ancestry_idx = dict((r, n) for n, r in enumerate(ancestry))
839
# best ~= most recent revision to modify a child of this directory
840
best = ancestry_idx[entry.revision]
841
for child in entry.children.itervalues():
842
if child.kind == 'directory':
843
index = cls._get_cache(cache, ancestry, child, ancestry_idx)
844
cache[child.file_id] = ancestry[index]
846
index = ancestry_idx[child.revision]
847
best = min(best, index)
850
def get_content(self):
851
"""Return a file-like (read(length)) for a file, None for a dir."""
854
def get_entries(self):
855
"""Yield child Nodes if a dir, return None if a file."""
856
for name, entry in self.entry.children.iteritems():
857
childpath = '/'.join((self.path, name))
858
klass = NODE_MAP[entry.kind]
859
if klass is BzrDirNode:
860
yield klass(self.bzr_repo, self.branch, self.tree, entry,
861
childpath, self.revcache)
863
yield klass(self.bzr_repo, self.branch, self.tree, entry,
866
def get_content_length(self):
869
def get_content_type(self):
872
def _get_revision_history(self, limit=None):
873
history = self.branch.revision_history()
875
if limit is not None:
876
history = history[-limit:]
877
self.bzr_repo.get_branch_cache(self.branch).cache_revisions(history)
878
for rev_id in reversed(history):
880
operation = versioncontrol.Changeset.ADD
882
operation = versioncontrol.Changeset.EDIT
884
self.bzr_repo.string_rev(self.branch, rev_id),
887
762
def get_history(self, limit=None):
888
763
"""Backward history.
904
779
current_entry = self.entry
905
780
file_id = current_entry.file_id
906
if current_entry.parent_id == None:
907
for r in self._get_revision_history(limit):
912
783
# We need the rev we were created with, not the rev the entry
913
784
# specifies (our contents may have changed between that rev
914
785
# and our own current rev).
915
current_revid = self._orig_rev
917
if self.branch is not None:
918
history = self.branch.revision_history()
921
if current_revid == 'current:':
922
current_revid = history[-1]
923
# If the current_revid we start from is in the branch history,
924
# limit our view to just the history, not the full ancestry.
926
index = history.index(current_revid)
928
ancestry = self.repo.get_ancestry(current_revid)
929
# The last entry is this rev, skip it.
932
# The last entry is None, skip it.
935
ancestry = ['null:'] + history[:index]
786
current_revid = self.get_content_revision()
787
if current_revid == CURRENT_REVISION:
788
current_revid = self.branch.last_revision()
790
# Aways use a linear view of our ancestry.
791
repo = self.branch.repository
792
ancestry = repo.iter_reverse_revision_history(current_revid)
793
ancestry = iter(ancestry)
794
# ancestry now iterates from current_revid down to revno 1
796
# Optimize the special case that this is the root of the
797
# branch. In this case, all revisions from the ancestry apply
798
# to us, and we can simply return them without further checks.
799
if self.entry.parent_id is None:
800
for rev_id in ancestry:
802
self.bzr_repo.string_rev(self.branch, rev_id),
803
versioncontrol.Changeset.EDIT)
805
self.bzr_repo.string_rev(self.branch, NULL_REVISION),
806
versioncontrol.Changeset.ADD)
937
809
# Load a bunch of trees in one go. We do not know how many we
938
810
# need: we may end up skipping some trees because they do not
940
812
chunksize = limit or 100
814
ancestry.next() # drop current_revno, we already know about that
941
815
current_tree = self.tree
942
816
current_path = current_tree.id2path(file_id)
943
817
path_prefix = self.path[:-len(current_path)]
945
chunk, ancestry = ancestry[:chunksize], ancestry[chunksize:]
821
for i in range(chunksize):
822
chunk.append(ancestry.next())
823
except StopIteration:
825
yield (path_prefix+current_path,
826
self.bzr_repo.string_rev(self.branch,
828
versioncontrol.Changeset.ADD)
946
830
cache = self.bzr_repo.get_branch_cache(self.branch)
947
831
for previous_revid, previous_tree in izip(
948
832
chunk, cache.revision_trees(chunk)):
989
873
current_revid = previous_revid
990
874
current_tree = previous_tree
992
def get_previous(self):
993
"""Equivalent to i=iter(get_history(2));i.next();return i.next().
995
The default implementation does essentially that, but we specialcase
996
it because we can skip the loading of all the trees.
876
def get_content_revision(self):
877
"""For non-directory nodes, the content was last changed here."""
878
return self.entry.revision
880
def get_last_modified(self):
881
return self.tree.get_file_mtime(self.entry.file_id)
884
class BzrDirNode(BzrVersionedNode):
888
kind = versioncontrol.Node.DIRECTORY
890
def __init__(self, bzr_repo, branch, revisiontree, entry, path,
892
BzrVersionedNode.__init__(self, bzr_repo, branch, revisiontree, entry,
895
revcache = _FileToRevisionCache(bzr_repo, branch, revisiontree, entry)
896
self.revcache = revcache
897
self.rev = self.bzr_repo.string_rev(self.branch,
898
self.get_content_revision())
900
def get_content_revision(self):
901
"""Determine the most recent change to the directory or its children."""
902
if self.entry.parent_id is None:
903
return self.tree.get_revision_id()
904
return self.revcache[self.entry]
907
return 'BzrDirNode(path=%r, relpath=%r)' % (self.path, self.entry.name)
910
def _get_cache(cls, cache, ancestry, entry, ancestry_idx=None):
911
"""Populate a file_id <-> revision_id mapping.
913
This mapping is different from InventoryEntry.revision, but only for
914
directories. In this scheme, directories are considered modified
915
if their contents are modified.
917
The revision ids are not guaranteed to be in the mainline revision
920
cache: The cache to populate
921
ancestry: A topologically-sorted list of revisions, with more recent
922
revisions having lower indexes.
923
entry: The InventoryEntry to start at
924
ancestry_idx: A mapping of revision_id <-> ancestry index.
998
# Special case: if this is the root node it (well, its
999
# contents) change every revision.
1000
if not self.tree.id2path(self.entry.file_id):
1001
return self.path, self.rev, versioncontrol.Changeset.EDIT
1002
if self._orig_rev != self.entry.revision:
1003
# The last change did not affect this dir directly, it changed
1005
return self.path, self.rev, versioncontrol.Changeset.EDIT
1006
# We were affected directly. Get a delta to figure out how.
1007
delta = self.repo.get_revision_delta(self._orig_rev)
1008
for path, file_id, kind in delta.added:
1009
if file_id == self.entry.file_id:
1010
return path, self.rev, versioncontrol.Changeset.ADD
1011
for oldpath, newpath, file_id, kind, textmod, metamod in delta.renamed:
1012
if file_id == self.entry.file_id:
1013
return newpath, self.rev, versioncontrol.Changeset.MOVE
1014
# We were removed (which does not make any sense,
1015
# the tree we were constructed from is newer and has us)
1016
raise core.TracError('should not get here, %r %r %r' %
1017
(self.entry, delta, self._orig_rev))
926
if ancestry_idx is None:
927
ancestry_idx = dict((r, n) for n, r in enumerate(ancestry))
928
# best ~= most recent revision to modify a child of this directory
929
best = ancestry_idx[entry.revision]
930
for child in entry.children.itervalues():
931
if child.kind == 'directory':
932
index = cls._get_cache(cache, ancestry, child, ancestry_idx)
933
cache[child.file_id] = ancestry[index]
935
index = ancestry_idx[child.revision]
936
best = min(best, index)
939
def get_content(self):
940
"""Return a file-like (read(length)) for a file, None for a dir."""
943
def get_entries(self):
944
"""Yield child Nodes if a dir, return None if a file."""
945
for name, entry in self.entry.children.iteritems():
946
childpath = '/'.join((self.path, name))
947
klass = NODE_MAP[entry.kind]
948
if klass is BzrDirNode:
949
yield klass(self.bzr_repo, self.branch, self.tree, entry,
950
childpath, self.revcache)
952
yield klass(self.bzr_repo, self.branch, self.tree, entry,
955
def get_content_length(self):
958
def get_content_type(self):
1020
962
class BzrFileNode(BzrVersionedNode):
1086
1028
assert isinstance(revid, str), "revid is %r" % type(revid)
1088
1030
self.bzr_repo = bzr_repo
1031
self.branch = branch
1090
# Check trac version. trac 0.11 uses datetime objects for
1091
# timestamp information.
1092
if int(trac.__version__[2:4]) < 11:
1093
use_datetime = False
1098
if revid in ('current:', 'null:'):
1099
self.revision = revision.Revision(revid, committer='',
1100
message='', timezone='')
1101
revidstr = bzr_repo.string_rev(branch, revid)
1103
versioncontrol.Changeset.__init__(self, revidstr,
1104
'', '', datetime.datetime.now(utc))
1106
versioncontrol.Changeset.__init__(self, revidstr,
1107
'', '', time.time())
1109
assert isinstance(revid, str)
1110
raise errors.NoSuchRevision(None, revid)
1112
self.revision = bzr_repo.get_branch_cache(branch).get_revision(revid)
1114
timestamp = datetime.datetime.\
1115
fromtimestamp(self.revision.timestamp, utc)
1116
versioncontrol.Changeset.__init__(self,
1117
bzr_repo.string_rev(
1119
self.revision.message,
1120
self.revision.committer,
1123
versioncontrol.Changeset.__init__(self,
1124
bzr_repo.string_rev(
1126
self.revision.message,
1127
self.revision.committer,
1128
self.revision.timestamp)
1129
self.branch = branch
1033
if revid == CURRENT_REVISION and branch is not None:
1034
revid = branch.revision_history()
1035
if revid in (CURRENT_REVISION, NULL_REVISION):
1036
self.revision = revision.Revision(revid, committer='',
1037
message='', timezone='')
1038
revidstr = bzr_repo.string_rev(branch, revid)
1039
versioncontrol.Changeset.__init__(self, revidstr,
1040
'', '', trac_timestamp(time.time()))
1043
assert isinstance(revid, str)
1044
raise errors.NoSuchRevision(None, revid)
1046
self.revision = bzr_repo.get_branch_cache(branch).get_revision(revid)
1047
authors = ";".join(self.revision.get_apparent_authors())
1048
versioncontrol.Changeset.__init__(self,
1049
bzr_repo.string_rev(
1051
self.revision.message,
1053
trac_timestamp(self.revision.timestamp))
1131
1055
def __repr__(self):
1132
1056
return 'BzrChangeset(%r)' % (self.revision.revision_id)
1134
1058
def get_properties(self):
1135
1059
"""Return an iterator of (name, value, is wikitext, html class)."""
1060
if trac_version >= (0,11):
1062
def add_text(name, value):
1063
result[name] = value
1064
def add_parents(parents):
1065
result['parents'] = '\n'.join(parents)
1068
def add_text(name, value):
1069
result.append((name, value, False, ''))
1070
def add_parents(parents):
1071
for name, link in [('parent trees', ' * source:@%s'),
1072
('changesets', ' * [changeset:%s]')]:
1073
wikitext = '\n'.join(link % parent for parent in parents)
1074
result.append((name, wikitext, True, ''))
1075
add_text('revision id', self.revision.revision_id)
1137
1076
for name, value in self.revision.properties.iteritems():
1138
result[name] = value
1077
add_text(name, value)
1139
1078
if len(self.revision.parent_ids) > 1:
1140
for name, link in [('parent trees', ' * source:@%s'),
1141
('changesets', ' * [changeset:%s]')]:
1142
result[name] = '\n'.join(
1143
link % (self.bzr_repo.string_rev(self.branch, parent),)
1144
for parent in self.revision.parent_ids)
1079
add_parents([self.bzr_repo.string_rev(self.branch, parent)
1080
for parent in self.revision.parent_ids])
1147
1083
def get_changes(self):
1257
1160
def __init__(self, bzr_repo, branch):
1258
1161
self.bzr_repo = bzr_repo
1259
1162
self.branch = branch
1260
self._sorted_revision_history = None
1261
self._dotted_revno = None
1262
1163
self._revno_revid = None
1264
def sorted_revision_history(self):
1265
if self._sorted_revision_history is None:
1266
self._sorted_revision_history = \
1267
sorted_revision_history(self.branch, generate_revno=True)
1268
return self._sorted_revision_history
1270
def _populate_dotted_maps(self):
1271
if self._dotted_revno is None:
1272
self._dotted_revno = {}
1273
self._revno_revid = {}
1274
for s, revision_id, m, revno, e in \
1275
self.sorted_revision_history():
1276
dotted = '.'.join([str(s) for s in revno])
1277
self._dotted_revno[revision_id] = dotted
1278
self._revno_revid[dotted] = revision_id
1280
1165
def dotted_revno(self, revid):
1281
self._populate_dotted_maps()
1282
return self._dotted_revno.get(revid)
1167
revno = self.branch.revision_id_to_dotted_revno(revid)
1168
dotted = '.'.join([str(s) for s in revno])
1170
except errors.NoSuchRevision:
1284
1173
def revid_from_dotted(self, dotted_revno):
1285
self._populate_dotted_maps()
1286
return self._revno_revid.get(dotted_revno)
1175
revno = tuple([int(s) for s in dotted_revno.split('.')])
1176
revid = self.branch.dotted_revno_to_revision_id(revno)
1178
except errors.NoSuchRevision:
1288
1181
def revision_tree(self, revision_id):
1289
1182
if revision_id not in self.bzr_repo._tree_cache:
1290
1183
self.bzr_repo._tree_cache[revision_id] = \
1291
1184
self.branch.repository.revision_tree(revision_id)
1292
if revision_id == 'null:':
1185
if revision_id == NULL_REVISION:
1293
1186
self.bzr_repo._tree_cache[None] = \
1294
self.bzr_repo._tree_cache['null:']
1187
self.bzr_repo._tree_cache[NULL_REVISION]
1295
1188
return self.bzr_repo._tree_cache[revision_id]
1297
1190
def revision_trees(self, revision_ids):
1298
if None in revision_ids or 'null:' in revision_ids:
1299
self.revision_tree('null:')
1191
if None in revision_ids or NULL_REVISION in revision_ids:
1192
self.revision_tree(NULL_REVISION)
1300
1193
missing = [r for r in revision_ids if r not in
1301
1194
self.bzr_repo._tree_cache]
1302
1195
if len(missing) > 0:
1323
1216
except KeyError:
1324
1217
self.cache_revisions([revision_id])
1325
1218
return self.bzr_repo._revision_cache[revision_id]
1221
class _FileToRevisionCache(object):
1222
"""Map from file_id to revision_id.
1224
This is used to determine the last modification to a directory.
1227
def __init__(self, bzr_repo, branch, revisiontree, root):
1228
self.bzr_repo = bzr_repo
1229
self.branch = branch
1230
self.revisiontree = revisiontree
1232
self.revcache = None
1234
def __getitem__(self, entry):
1235
"""Lazily fill cache only when needed."""
1236
if self.revcache is None:
1237
revid = self.revisiontree.get_revision_id()
1238
ancestry = self.branch.repository.get_ancestry(revid)
1240
ancestry_idx = dict((r, n) for n, r in enumerate(ancestry))
1242
best = self._fill_cache(ancestry, ancestry_idx, self.root)
1243
return self.revcache[entry.file_id]
1245
def _fill_cache(self, ancestry, ancestry_idx, entry):
1246
"""Populate a file_id <-> revision_id mapping.
1248
This mapping is different from InventoryEntry.revision, but only for
1249
directories. In this scheme, directories are considered modified
1250
if their contents are modified.
1252
The revision ids are not guaranteed to be in the mainline revision
1255
ancestry: A topologically-sorted list of revisions, with more recent
1256
revisions having lower indexes.
1257
ancestry_idx: A mapping of revision_id <-> ancestry index.
1258
entry: The InventoryEntry to start at
1260
# best ~= most recent revision to modify a child of this directory
1261
best = ancestry_idx[entry.revision]
1262
for child in entry.children.itervalues():
1263
if child.kind == 'directory':
1264
index = self._fill_cache(ancestry, ancestry_idx, child)
1266
index = ancestry_idx[child.revision]
1267
best = min(best, index)
1268
self.revcache[entry.file_id] = ancestry[best]