1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
# Copyright (C) 2010 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""Logic to create commit templates."""
from __future__ import absolute_import
import patiencediff
from ... import bugtracker, osutils
import re
_BUG_MATCH = re.compile(r'lp:(\d+)')
class CommitTemplate(object):
def __init__(self, commit, message, filespec):
"""Create a commit template for commit with initial message message.
:param commit: A Commit object for the in progress commit.
:param message: The current message (which may be None).
:param filespec: List of files to match
"""
self.commit = commit
self.message = message
self.filespec = filespec
def make(self):
"""Make the template.
If NEWS is missing or not not modified, the original template is
returned unaltered. Otherwise the changes from NEWS are concatenated
with whatever message was provided to __init__.
"""
delta = self.commit.builder.get_basis_delta()
found_old_path = None
found_entry = None
for old_path, new_path, fileid, entry in delta:
if new_path in self.filespec:
found_entry = entry
found_old_path = old_path
break
if not found_entry:
return self.message
if found_old_path is None:
# New file
_, new_chunks = list(
self.commit.builder.repository.iter_files_bytes(
[(found_entry.file_id, found_entry.revision, None)]))[0]
content = b''.join(new_chunks).decode('utf-8')
return self.merge_message(content)
else:
# Get a diff. XXX Is this hookable? I thought it was, can't find it
# though.... add DiffTree.diff_factories. Sadly thats not at the
# right level: we want to identify the changed lines, not have the
# final diff: because we want to grab the sections for regions
# changed in new version of the file. So for now a direct diff
# using patiencediff is done.
old_revision = self.commit.basis_tree.get_file_revision(old_path)
needed = [(found_entry.file_id, found_entry.revision, 'new'),
(found_entry.file_id, old_revision, 'old')]
contents = self.commit.builder.repository.iter_files_bytes(needed)
lines = {}
for name, chunks in contents:
lines[name] = osutils.chunks_to_lines(chunks)
new = lines['new']
sequence_matcher = patiencediff.PatienceSequenceMatcher(
None, lines['old'], new)
new_lines = []
for group in sequence_matcher.get_opcodes():
tag, i1, i2, j1, j2 = group
if tag == 'equal':
continue
if tag == 'delete':
continue
new_lines.extend([l.decode('utf-8') for l in new[j1:j2]])
if not self.commit.revprops.get('bugs'):
# TODO: Allow the user to configure the bug tracker to use
# rather than hardcoding Launchpad.
bt = bugtracker.tracker_registry.get('launchpad')
bugids = []
for line in new_lines:
bugids.extend(_BUG_MATCH.findall(line))
self.commit.revprops['bugs'] = \
bugtracker.encode_fixes_bug_urls(
[(bt.get_bug_url(bugid), bugtracker.FIXED)
for bugid in bugids])
return self.merge_message(''.join(new_lines))
def merge_message(self, new_message):
"""Merge new_message with self.message.
:param new_message: A string message to merge with self.message.
:return: A string with the merged messages.
"""
if self.message is None:
return new_message
return self.message + new_message
|