11 self.author_email = ''
18 self.lustre_commit = ''
19 self.lustre_change = ''
20 self.lustre_change_number = 0
27 if self._parent != self:
28 self._parent = self._parent._find()
35 if r1._rank > r2._rank:
37 elif r1._rank < r2._rank:
44 GIT_LOG_FIELDS = ['commit', 'author_name', 'author_email', 'author_date', 'subject', 'body']
45 GIT_LOG_KEYS = ['%H', '%an', '%ae', '%at', '%s', '%b']
46 GIT_LOG_FORMAT = '%x1f'.join(GIT_LOG_KEYS) + '%x1e'
48 def _change_from_record(rec):
50 change.__dict__.update(dict(zip(GIT_LOG_FIELDS, rec.split('\x1f'))))
51 change.author_date = long(change.author_date)
52 for line in change.body.splitlines():
53 # Sometimes we have 'key : value' so we strip both sides.
54 lis = line.split(':', 1)
58 if key in ['Change-Id', 'Reviewed-on', 'Lustre-commit', 'Lustre-change', 'Cray-bug-id', 'HPE-bug-id']:
59 change.__dict__[key.replace('-', '_').lower()] = val
61 obj = re.match(r'[A-Za-z]+://[\w\.]+/(\d+)$', change.reviewed_on)
63 change.number = int(obj.group(1))
65 obj = re.match(r'[A-Za-z]+://[\w\.]+/(\d+)$', change.lustre_change)
67 change.lustre_change_number = int(obj.group(1))
80 def __init__(self, name, paths):
83 self.log = [] # Oldest commit is first.
84 self.by_commit = {} # str -> change
85 self.by_subject = {} # str -> list of changes
86 self.by_change_id = {} # str -> list of changes
87 self.by_number = {} # str -> list of changes
89 def _add_change_from_record(self, rec):
90 # TODO Handle reverted commits.
91 change = _change_from_record(rec)
92 self.log.append(change)
94 assert change.commit not in self.by_commit
95 self.by_commit[change.commit] = change
98 lis = self.by_subject.setdefault(change.subject, [])
99 # XXX Do we want this?
101 # lis[0]._union(change)
104 for bug_id in (change.cray_bug_id, change.hpe_bug_id):
105 if bug_id and (' ' in change.subject):
106 # Split subject in to issue and rest.
107 issue, rest = change.subject.split(None, 1)
108 # Make new subject using external bug id
109 subject = ' '.join((bug_id, rest))
110 lis = self.by_subject.setdefault(subject, [])
113 # Equivalate by change_id.
115 lis = self.by_change_id.setdefault(change.change_id, [])
117 lis[0]._union(change)
120 # Equivalate by number (from reviewed_on).
122 lis = self.by_number.setdefault(change.number, [])
124 lis[0]._union(change)
131 self.by_change_id = {}
134 git_base = ['git'] # [, '--git-dir=' + self.path + '/.git']
135 # rc = subprocess.call(git_base + ['fetch', 'origin'])
138 pipe = subprocess.Popen(git_base + ['log',
139 '--format=' + GIT_LOG_FORMAT,
143 stdout=subprocess.PIPE)
144 out, _ = pipe.communicate()
148 for rec in out.split('\x1e\n'):
150 self._add_change_from_record(rec)
152 def find_port(self, change):
153 # Try to find a port of change in this branch. change may or
154 # may not belong to branch.
156 # TODO Return oldest member of equivalence class.
157 port = (self.by_commit.get(change.commit) or
158 self.by_commit.get(change.lustre_commit) or
159 self.by_commit.get(change.lustre_change) or # Handle misuse.
160 _head(self.by_change_id.get(change.change_id)) or
161 _head(self.by_change_id.get(change.lustre_commit)) or # ...
162 _head(self.by_change_id.get(change.lustre_change)) or
163 _head(self.by_number.get(change.number)) or # Do we need this?
164 _head(self.by_number.get(change.lustre_change_number)) or
165 _head(self.by_subject.get(change.subject))) # Do we want this?
172 def branch_comm(b1, b2):
177 printed = set() # commits
179 def change_is_printed(c):
180 return (c.commit in printed) or (c.lustre_commit in printed)
182 def change_set_printed(c):
183 printed.add(c.commit)
185 printed.add(c.lustre_commit)
187 # Suppress initial common commits.
188 while i1 < n1 and i2 < n2:
189 # XXX Should we use _find() on c1 and c2 here?
190 # XXX Or c1 = b1.find_port(c1)?
193 if c1.commit == c2.commit:
200 while i1 < n1 and i2 < n2:
202 if change_is_printed(c1):
207 if change_is_printed(c2):
211 p1 = b1.find_port(c2)
212 if p1 and change_is_printed(p1):
213 change_set_printed(c2)
217 p2 = b2.find_port(c1)
218 if p2 and change_is_printed(p2):
219 change_set_printed(c1)
223 # Neither of c1 and c2 has been printed, nor has any port or either.
225 # XXX Do we need c1._find() here?
226 if c1 == p1 or c2 == p2:
227 # c1 and c2 are ports of the same change.
228 change_set_printed(c1)
229 change_set_printed(c2)
231 change_set_printed(p1)
233 change_set_printed(p2)
236 # c1 is common to both branches.
237 print '\t\t%s\t%s' % (c1.commit, c1.subject) # TODO Add a '*' if subjects different...
241 # b1 has c2, b2 does not have c1, (port of c2 must be after c1).
242 change_set_printed(c1)
244 # c1 is unique to b1.
245 print '%s\t\t\t%s' % (c1.commit, c1.subject)
249 # b2 has c1, b1 does not have c2, (port of c1 must be after c2).
250 change_set_printed(c2)
252 # c2 is unique to b2.
253 print '\t%s\t\t%s' % (c2.commit, c2.subject)
256 # Now neither is ported or both are ported (and the order is weird).
258 change_set_printed(c1)
259 change_set_printed(p2)
261 # c1 is common to both branches.
262 print '\t\t%s\t%s' % (c1.commit, c1.subject)
265 change_set_printed(c1)
267 # c1 is unique to b1.
268 print '%s\t\t\t%s' % (c1.commit, c1.subject)
271 for c1 in b1.log[i1:]:
272 if change_is_printed(c1):
276 # All commits from b2 have been printed. Therefore if c1 has
277 # been ported to b2 then the port has already been printed. So
278 # c1 is unique to b1 and must be printed.
280 change_set_printed(c1)
281 print '%s\t\t\t%s' % (c1.commit, c1.subject)
283 for c2 in b2.log[i2:]:
284 if change_is_printed(c2):
289 change_set_printed(c2)
290 print '\t%s\t\t%s' % (c2.commit, c2.subject)
293 USAGE = """usage: '_PROGNAME_ BRANCH1 BRANCH2 [PATH]...'
295 Compare commits to Lustre branches.
297 Prints commits unique to BRANCH1 in column 1.
298 Prints commits unique to BRANCH2 in column 2.
299 Prints commits common to both branches in column 3.
300 Prints commit subject in column 4.
301 Skips initial common commits.
303 The output format is inspired by comm(1). To filter commits by branch,
304 pipe the output to awk. For example:
305 $ ... | awk -F'\\t' '$1 != ""' # only commits unique to BRANCH1
306 $ ... | awk -F'\\t' '$2 != ""' # only commits unique to BRANCH2
307 $ ... | awk -F'\\t' '$3 != ""' # only common commits
308 $ ... | awk -F'\\t' '$3 == ""' # exclude common commmits
310 This assumes that both branches are in the repository that contains
311 the current directory. To compare branches from different upstream
312 repositories (for example 'origin/master' and 'other/b_post_cmd3') do:
314 $ cd fs/lustre-release
316 $ git remote add other ...
318 $ _PROGNAME_ origin/master other/b_post_cmd3"""
322 if len(sys.argv) < 3:
323 print >> sys.stderr, USAGE.replace('_PROGNAME_', sys.argv[0])
328 b1 = Branch(sys.argv[1], paths)
331 b2 = Branch(sys.argv[2], paths)
337 if __name__ == '__main__':