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(list(zip(GIT_LOG_FIELDS, rec.split('\x1f')))))
51 change.author_date = int(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,
145 out, _ = pipe.communicate()
149 for rec in out.split('\x1e\n'):
151 self._add_change_from_record(rec)
153 def find_port(self, change):
154 # Try to find a port of change in this branch. change may or
155 # may not belong to branch.
157 # TODO Return oldest member of equivalence class.
158 port = (self.by_commit.get(change.commit) or
159 self.by_commit.get(change.lustre_commit) or
160 self.by_commit.get(change.lustre_change) or # Handle misuse.
161 _head(self.by_change_id.get(change.change_id)) or
162 _head(self.by_change_id.get(change.lustre_commit)) or # ...
163 _head(self.by_change_id.get(change.lustre_change)) or
164 _head(self.by_number.get(change.number)) or # Do we need this?
165 _head(self.by_number.get(change.lustre_change_number)) or
166 _head(self.by_subject.get(change.subject))) # Do we want this?
173 def branch_comm(b1, b2):
178 printed = set() # commits
180 def change_is_printed(c):
181 return (c.commit in printed) or (c.lustre_commit in printed)
183 def change_set_printed(c):
184 printed.add(c.commit)
186 printed.add(c.lustre_commit)
188 # Suppress initial common commits.
189 while i1 < n1 and i2 < n2:
190 # XXX Should we use _find() on c1 and c2 here?
191 # XXX Or c1 = b1.find_port(c1)?
194 if c1.commit == c2.commit:
201 while i1 < n1 and i2 < n2:
203 if change_is_printed(c1):
208 if change_is_printed(c2):
212 p1 = b1.find_port(c2)
213 if p1 and change_is_printed(p1):
214 change_set_printed(c2)
218 p2 = b2.find_port(c1)
219 if p2 and change_is_printed(p2):
220 change_set_printed(c1)
224 # Neither of c1 and c2 has been printed, nor has any port or either.
226 # XXX Do we need c1._find() here?
227 if c1 == p1 or c2 == p2:
228 # c1 and c2 are ports of the same change.
229 change_set_printed(c1)
230 change_set_printed(c2)
232 change_set_printed(p1)
234 change_set_printed(p2)
237 # c1 is common to both branches.
238 print('\t\t%s\t%s' % (c1.commit, c1.subject)) # TODO Add a '*' if subjects different...
242 # b1 has c2, b2 does not have c1, (port of c2 must be after c1).
243 change_set_printed(c1)
245 # c1 is unique to b1.
246 print('%s\t\t\t%s' % (c1.commit, c1.subject))
250 # b2 has c1, b1 does not have c2, (port of c1 must be after c2).
251 change_set_printed(c2)
253 # c2 is unique to b2.
254 print('\t%s\t\t%s' % (c2.commit, c2.subject))
257 # Now neither is ported or both are ported (and the order is weird).
259 change_set_printed(c1)
260 change_set_printed(p2)
262 # c1 is common to both branches.
263 print('\t\t%s\t%s' % (c1.commit, c1.subject))
266 change_set_printed(c1)
268 # c1 is unique to b1.
269 print('%s\t\t\t%s' % (c1.commit, c1.subject))
272 for c1 in b1.log[i1:]:
273 if change_is_printed(c1):
277 # All commits from b2 have been printed. Therefore if c1 has
278 # been ported to b2 then the port has already been printed. So
279 # c1 is unique to b1 and must be printed.
281 change_set_printed(c1)
282 print('%s\t\t\t%s' % (c1.commit, c1.subject))
284 for c2 in b2.log[i2:]:
285 if change_is_printed(c2):
290 change_set_printed(c2)
291 print('\t%s\t\t%s' % (c2.commit, c2.subject))
294 USAGE = """usage: '_PROGNAME_ BRANCH1 BRANCH2 [PATH]...'
296 Compare commits to Lustre branches.
298 Prints commits unique to BRANCH1 in column 1.
299 Prints commits unique to BRANCH2 in column 2.
300 Prints commits common to both branches in column 3.
301 Prints commit subject in column 4.
302 Skips initial common commits.
304 The output format is inspired by comm(1). To filter commits by branch,
305 pipe the output to awk. For example:
306 $ ... | awk -F'\\t' '$1 != ""' # only commits unique to BRANCH1
307 $ ... | awk -F'\\t' '$2 != ""' # only commits unique to BRANCH2
308 $ ... | awk -F'\\t' '$3 != ""' # only common commits
309 $ ... | awk -F'\\t' '$3 == ""' # exclude common commmits
311 This assumes that both branches are in the repository that contains
312 the current directory. To compare branches from different upstream
313 repositories (for example 'origin/master' and 'other/b_post_cmd3') do:
315 $ cd fs/lustre-release
317 $ git remote add other ...
319 $ _PROGNAME_ origin/master other/b_post_cmd3"""
323 if len(sys.argv) < 3:
324 print(USAGE.replace('_PROGNAME_', sys.argv[0]), file=sys.stderr)
329 b1 = Branch(sys.argv[1], paths)
332 b2 = Branch(sys.argv[2], paths)
338 if __name__ == '__main__':