50
50
from __future__ import absolute_import
53
from cStringIO import StringIO
54
from itertools import (
56
60
from warnings import (
60
from .lazy_import import lazy_import
64
from bzrlib.lazy_import import lazy_import
61
65
lazy_import(globals(), """
68
73
repository as _mod_repository,
69
74
revision as _mod_revision,
72
from breezy.i18n import gettext, ngettext
78
from bzrlib.i18n import gettext, ngettext
81
from .osutils import (
85
from bzrlib.osutils import (
83
87
format_date_with_offset_in_original_timezone,
84
88
get_diff_header_encoding,
85
89
get_terminal_encoding,
93
from .tree import find_previous_path
96
def find_touching_revisions(repository, last_revision, last_tree, last_path):
94
def find_touching_revisions(branch, file_id):
97
95
"""Yield a description of revisions which affect the file_id.
99
97
Each returned element is (revno, revision_id, description)
104
102
TODO: Perhaps some way to limit this to only particular revisions,
105
103
or to traverse a non-mainline set of revisions?
107
last_verifier = last_tree.get_file_verifier(last_path)
108
graph = repository.get_graph()
109
history = list(graph.iter_lefthand_ancestry(last_revision, []))
111
for revision_id in history:
112
this_tree = repository.revision_tree(revision_id)
113
this_path = find_previous_path(last_tree, this_tree, last_path)
108
graph = branch.repository.get_graph()
109
history = list(graph.iter_lefthand_ancestry(branch.last_revision(),
110
[_mod_revision.NULL_REVISION]))
111
for revision_id in reversed(history):
112
this_inv = branch.repository.get_inventory(revision_id)
113
if this_inv.has_id(file_id):
114
this_ie = this_inv[file_id]
115
this_path = this_inv.id2path(file_id)
117
this_ie = this_path = None
115
119
# now we know how it was last time, and how it is in this revision.
116
120
# are those two states effectively the same or not?
117
if this_path is not None and last_path is None:
118
yield revno, revision_id, "deleted " + this_path
119
this_verifier = this_tree.get_file_verifier(this_path)
120
elif this_path is None and last_path is not None:
121
yield revno, revision_id, "added " + last_path
122
if not this_ie and not last_ie:
123
# not present in either
125
elif this_ie and not last_ie:
126
yield revno, revision_id, "added " + this_path
127
elif not this_ie and last_ie:
129
yield revno, revision_id, "deleted " + last_path
122
130
elif this_path != last_path:
123
yield revno, revision_id, ("renamed %s => %s" % (this_path, last_path))
124
this_verifier = this_tree.get_file_verifier(this_path)
126
this_verifier = this_tree.get_file_verifier(this_path)
127
if (this_verifier != last_verifier):
128
yield revno, revision_id, "modified " + this_path
131
yield revno, revision_id, ("renamed %s => %s" % (last_path, this_path))
132
elif (this_ie.text_size != last_ie.text_size
133
or this_ie.text_sha1 != last_ie.text_sha1):
134
yield revno, revision_id, "modified " + this_path
130
last_verifier = this_verifier
131
137
last_path = this_path
132
last_tree = this_tree
133
if last_path is None:
138
141
def show_log(branch,
322
def format_signature_validity(rev_id, branch):
325
def format_signature_validity(rev_id, repo):
323
326
"""get the signature validity
325
328
:param rev_id: revision id to validate
326
:param branch: branch of revision
329
:param repo: repository of revision
327
330
:return: human readable string to print to log
329
from breezy import gpg
332
from bzrlib import gpg
331
gpg_strategy = gpg.GPGStrategy(branch.get_config_stack())
332
result = branch.repository.verify_revision_signature(rev_id, gpg_strategy)
334
gpg_strategy = gpg.GPGStrategy(None)
335
result = repo.verify_revision_signature(rev_id, gpg_strategy)
333
336
if result[0] == gpg.SIGNATURE_VALID:
334
337
return u"valid signature from {0}".format(result[1])
335
338
if result[0] == gpg.SIGNATURE_KEY_MISSING:
406
409
# Find and print the interesting revisions
407
410
generator = self._generator_factory(self.branch, rqst)
409
for lr in generator.iter_log_revisions():
411
except errors.GhostRevisionUnusableHere:
412
raise errors.BzrCommandError(
413
gettext('Further revision history missing.'))
411
for lr in generator.iter_log_revisions():
416
415
def _generator_factory(self, branch, rqst):
457
456
if omit_merges and len(rev.parent_ids) > 1:
460
raise errors.GhostRevisionUnusableHere(rev_id)
461
458
if diff_type is None:
464
461
diff = self._format_diff(rev, rev_id, diff_type)
465
462
if show_signature:
466
signature = format_signature_validity(rev_id, self.branch)
463
signature = format_signature_validity(rev_id,
464
self.branch.repository)
469
467
yield LogRevision(rev, revno, merge_depth, delta,
663
661
# shown naturally, i.e. just like it is for linear logging. We can easily
664
662
# make forward the exact opposite display, but showing the merge revisions
665
663
# indented at the end seems slightly nicer in that case.
666
view_revisions = itertools.chain(iter(initial_revisions),
664
view_revisions = chain(iter(initial_revisions),
667
665
_graph_view_revisions(branch, start_rev_id, end_rev_id,
668
666
rebase_initial_depths=(direction == 'reverse'),
669
667
exclude_common_ancestry=exclude_common_ancestry))
733
730
graph = repo.get_graph()
734
731
if start_rev_id is None and end_rev_id is None:
735
732
cur_revno = br_revno
736
graph_iter = graph.iter_lefthand_ancestry(br_rev_id,
737
(_mod_revision.NULL_REVISION,))
740
revision_id = next(graph_iter)
741
except errors.RevisionNotPresent as e:
743
yield e.revision_id, None, None
746
yield revision_id, str(cur_revno), 0
733
for revision_id in graph.iter_lefthand_ancestry(br_rev_id,
734
(_mod_revision.NULL_REVISION,)):
735
yield revision_id, str(cur_revno), 0
749
738
if end_rev_id is None:
750
739
end_rev_id = br_rev_id
751
740
found_start = start_rev_id is None
752
graph_iter = graph.iter_lefthand_ancestry(end_rev_id,
753
(_mod_revision.NULL_REVISION,))
756
revision_id = next(graph_iter)
757
except StopIteration:
759
except errors.RevisionNotPresent as e:
761
yield e.revision_id, None, None
764
revno_str = _compute_revno_str(branch, revision_id)
765
if not found_start and revision_id == start_rev_id:
766
if not exclude_common_ancestry:
767
yield revision_id, revno_str, 0
741
for revision_id in graph.iter_lefthand_ancestry(end_rev_id,
742
(_mod_revision.NULL_REVISION,)):
743
revno_str = _compute_revno_str(branch, revision_id)
744
if not found_start and revision_id == start_rev_id:
745
if not exclude_common_ancestry:
771
746
yield revision_id, revno_str, 0
773
raise _StartNotLinearAncestor()
750
yield revision_id, revno_str, 0
753
raise _StartNotLinearAncestor()
776
756
def _graph_view_revisions(branch, start_rev_id, end_rev_id,
821
801
"""Adjust depths upwards so the top level is 0."""
822
802
# If either the first or last revision have a merge_depth of 0, we're done
823
803
if view_revisions and view_revisions[0][2] and view_revisions[-1][2]:
824
min_depth = min([d for r, n, d in view_revisions])
804
min_depth = min([d for r,n,d in view_revisions])
825
805
if min_depth != 0:
826
view_revisions = [(r, n, d-min_depth) for r, n, d in view_revisions]
806
view_revisions = [(r,n,d-min_depth) for r,n,d in view_revisions]
827
807
return view_revisions
845
825
# Convert view_revisions into (view, None, None) groups to fit with
846
826
# the standard interface here.
847
if isinstance(view_revisions, list):
827
if type(view_revisions) == list:
848
828
# A single batch conversion is faster than many incremental ones.
849
829
# As we have all the data, do a batch conversion.
850
830
nones = [None] * len(view_revisions)
851
log_rev_iterator = iter([list(zip(view_revisions, nones, nones))])
831
log_rev_iterator = iter([zip(view_revisions, nones, nones)])
854
834
for view in view_revisions:
880
860
:return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
884
864
return log_rev_iterator
885
865
searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
886
for k, v in match.items()]
866
for (k,v) in match.iteritems()]
887
867
return _filter_re(searchRE, log_rev_iterator)
900
880
'author': (rev.get_apparent_authors()),
901
881
'bugs': list(rev.iter_bugs())
903
strings[''] = [item for inner_list in strings.values()
883
strings[''] = [item for inner_list in strings.itervalues()
904
884
for item in inner_list]
905
for (k, v) in searchRE:
885
for (k,v) in searchRE:
906
886
if k in strings and not _match_any_filter(strings[k], v):
910
890
def _match_any_filter(strings, res):
911
return any(re.search(s) for re in res for s in strings)
891
return any([filter(None, map(re.search, strings)) for re in res])
913
893
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
914
894
fileids=None, direction='reverse'):
960
940
if delta_type == 'full' and not check_fileids:
961
941
deltas = repository.get_deltas_for_revisions(revisions)
962
for rev, delta in zip(revs, deltas):
942
for rev, delta in izip(revs, deltas):
963
943
new_revs.append((rev[0], rev[1], delta))
965
945
deltas = repository.get_deltas_for_revisions(revisions, fileid_set)
966
for rev, delta in zip(revs, deltas):
946
for rev, delta in izip(revs, deltas):
967
947
if check_fileids:
968
948
if delta is None or not delta.has_changed():
1017
997
for revs in log_rev_iterator:
1018
998
# r = revision_id, n = revno, d = merge depth
1019
999
revision_ids = [view[0] for view, _, _ in revs]
1020
revisions = dict(repository.iter_revisions(revision_ids))
1021
yield [(rev[0], revisions[rev[0][0]], rev[2]) for rev in revs]
1000
revisions = repository.get_revisions(revision_ids)
1001
revs = [(rev[0], revision, rev[2]) for rev, revision in
1002
izip(revs, revisions)]
1024
1006
def _make_batch_filter(branch, generate_delta, search, log_rev_iterator):
1230
1212
# rate). This particular access is clustered with a low success rate.
1231
1213
modified_text_revisions = set()
1232
1214
chunk_size = 1000
1233
for start in range(0, len(text_keys), chunk_size):
1215
for start in xrange(0, len(text_keys), chunk_size):
1234
1216
next_keys = text_keys[start:start + chunk_size]
1235
1217
# Only keep the revision_id portion of the key
1236
1218
modified_text_revisions.update(
1603
1585
to_file.write("%s%s\n" % (indent, ('\n' + indent).join(lines)))
1604
1586
if revision.delta is not None:
1605
1587
# Use the standard status output to display changes
1606
from breezy.delta import report_delta
1588
from bzrlib.delta import report_delta
1607
1589
report_delta(to_file, revision.delta, short_status=False,
1608
1590
show_ids=self.show_ids, indent=indent)
1609
1591
if revision.diff is not None:
1676
1658
if revision.delta is not None:
1677
1659
# Use the standard status output to display changes
1678
from breezy.delta import report_delta
1660
from bzrlib.delta import report_delta
1679
1661
report_delta(to_file, revision.delta,
1680
1662
short_status=self.delta_format==1,
1681
1663
show_ids=self.show_ids, indent=indent + offset)
1765
1747
show_offset=False)
1766
1748
committer_str = self.authors(revision.rev, 'first', sep=', ')
1767
1749
committer_str = committer_str.replace(' <', ' <')
1768
to_file.write('%s %s\n\n' % (date_str, committer_str))
1750
to_file.write('%s %s\n\n' % (date_str,committer_str))
1770
1752
if revision.delta is not None and revision.delta.has_changed():
1771
1753
for c in revision.delta.added + revision.delta.removed + revision.delta.modified:
1773
1755
to_file.write('\t* %s:\n' % (path,))
1774
1756
for c in revision.delta.renamed:
1775
oldpath, newpath = c[:2]
1757
oldpath,newpath = c[:2]
1776
1758
# For renamed files, show both the old and the new path
1777
to_file.write('\t* %s:\n\t* %s:\n' % (oldpath, newpath))
1759
to_file.write('\t* %s:\n\t* %s:\n' % (oldpath,newpath))
1778
1760
to_file.write('\n')
1780
1762
if not revision.rev.message:
2031
2014
kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
2032
2015
branch will be read-locked.
2034
from breezy.builtins import _get_revision_range
2017
from bzrlib.builtins import _get_revision_range
2035
2018
tree, b, path = controldir.ControlDir.open_containing_tree_or_branch(
2037
2020
add_cleanup(b.lock_read().unlock)
2072
2055
tree1 = b.repository.revision_tree(rev1)
2074
2057
file_id = tree1.path2id(fp)
2075
kind = _get_kind_for_file_id(tree1, fp, file_id)
2058
kind = _get_kind_for_file_id(tree1, file_id)
2076
2059
info_list.append((fp, file_id, kind))
2078
2061
elif start_rev_info == end_rev_info:
2080
2063
tree = b.repository.revision_tree(end_rev_info.rev_id)
2081
2064
for fp in relpaths:
2082
2065
file_id = tree.path2id(fp)
2083
kind = _get_kind_for_file_id(tree, fp, file_id)
2066
kind = _get_kind_for_file_id(tree, file_id)
2084
2067
info_list.append((fp, file_id, kind))
2105
2088
tree1 = b.repository.revision_tree(rev_id)
2106
2089
file_id = tree1.path2id(fp)
2107
kind = _get_kind_for_file_id(tree1, fp, file_id)
2090
kind = _get_kind_for_file_id(tree1, file_id)
2108
2091
info_list.append((fp, file_id, kind))
2109
2092
return b, info_list, start_rev_info, end_rev_info
2112
def _get_kind_for_file_id(tree, path, file_id):
2095
def _get_kind_for_file_id(tree, file_id):
2113
2096
"""Return the kind of a file-id or None if it doesn't exist."""
2114
2097
if file_id is not None:
2115
return tree.kind(path, file_id)
2098
return tree.kind(file_id)
2122
2105
# Use the properties handlers to print out bug information if available
2123
2106
def _bugs_properties_handler(revision):
2124
if 'bugs' in revision.properties:
2107
if revision.properties.has_key('bugs'):
2125
2108
bug_lines = revision.properties['bugs'].split('\n')
2126
2109
bug_rows = [line.split(' ', 1) for line in bug_lines]
2127
2110
fixed_bug_urls = [row[0] for row in bug_rows if