Chromium Code Reviews

Side by Side Diff: find_owners.py

Issue 12712002: An interactive tool to help find owners covering current change list. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Remove files that * can review from the process Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | | Annotate | Revision Log
« no previous file with comments | « no previous file | git_cl.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5 '''A tool to help picking owners for reviewing.'''
6
7 import git_cl
8 import os.path
9 import sys
10 import copy
11 import glob
12 import owners as owners_module
13
14 def first(iterable):
15 for element in iterable:
16 return element
17
18 class FindOwners:
19 COLOR_LINK = '\033[4m'
20 COLOR_BOLD = '\033[1;32m'
21 COLOR_GREY = '\033[0;37m'
22 COLOR_RESET = '\033[0m'
23 def __init__(self, files, local_root, disable_color=False):
24 self.files = files
25 self.owners = owners = {}
scheib 2013/03/11 17:08:41 Perhaps comment what the expected structure of thi
Bei Zhang 2013/03/11 18:12:21 Done.
26 self.ownership = ownership = {}
27
28 if os.name == "nt" or disable_color:
29 self.COLOR_LINK = ''
30 self.COLOR_BOLD = ''
31 self.COLOR_GREY = ''
32 self.COLOR_RESET = ''
33
34 db = owners_module.Database(local_root,
35 fopen=file, os_path=os.path, glob=glob.glob)
36 db.reviewers_for(files, None)
37
38 for file_name in self.files:
39 owners_set = set()
40 if file_name in db.owners_for:
41 owners_set = owners_set | db.owners_for[file_name]
42 else:
43 dir_name = file_name
44 while dir_name != '':
45 if dir_name in db.stop_looking:
46 break
47 dir_name = os.path.dirname(dir_name)
48 if dir_name in db.owners_for:
49 if owners_module.EVERYONE in db.owners_for[dir_name]:
50 break
51 owners_set = owners_set | db.owners_for[dir_name]
52
53 # Eliminate files that EVERYONE can review
54 if owners_module.EVERYONE in owners_set:
55 continue
56 ownership[file_name] = owners_set
57
58 self.original_ownership = copy.deepcopy(ownership)
59
60 for owner_name in db.owned_by:
61 if owner_name == owners_module.EVERYONE:
62 continue
63 owned_by = set()
scheib 2013/03/11 17:08:41 Maybe name this to be a set of files?
Bei Zhang 2013/03/11 18:12:21 Done.
64 for file_name in self.files:
65 if owner_name in ownership[file_name]:
66 owned_by.add(file_name)
67 if len(owned_by) > 0:
68 owners[owner_name] = owned_by
69
70 self.comments = db.comments
71 self.ownership = copy.deepcopy(self.original_ownership)
72 self.unreviewedFiles = set(self.files)
73 self.usedOwners = set()
74 self.unusedOwners = set()
75
76 def reset(self):
77 self.ownership = copy.deepcopy(self.original_ownership)
78 self.unreviewedFiles = set(self.files)
79 self.usedOwners = set()
80 self.unusedOwners = set()
81
82 def bold(self, text):
83 return (self.COLOR_BOLD +
84 text + self.COLOR_RESET)
85
86 def bold_name(self, name):
87 return (self.COLOR_BOLD +
88 name.replace("@chromium.org", "") + self.COLOR_RESET)
89
90 def greyed(self, text):
91 return self.COLOR_GREY + text + self.COLOR_RESET
92
93 def unuseOwner(self, owner):
94 self.unusedOwners.add(owner)
95 for file_name in self.owners[owner]:
96 self.ownership[file_name].remove(owner)
97 self.findMustPickOwners()
98
99 def useOwner(self, owner):
100 self.usedOwners.add(owner)
101 for file_name in self.owners[owner]:
102 if file_name in self.unreviewedFiles:
103 self.unreviewedFiles.remove(file_name)
104 self.findMustPickOwners()
105
106 def findMustPickOwners(self):
107 continues = True
108 while continues:
109 continues = False
110 for file_name in self.unreviewedFiles:
111 if len(self.ownership[file_name]) == 1:
112 self.useOwner(first(self.ownership[file_name]))
113 continues = True
114 break
115
116 def printComments(self, owner):
117 if owner not in self.comments:
118 print self.bold_name(owner)
119 else:
120 print self.bold_name(owner) + " is commented as:"
121 for path in self.comments[owner]:
122 if len(self.comments[owner][path]) > 0:
123 print (self.greyed(" " + self.comments[owner][path]) + " (at " +
124 self.bold(path or "<root>") + ")")
125 else:
126 print (self.greyed(" [No comment] ") + " (at " +
127 self.bold(path or "<root>") + ")")
128
129 def printOwnedFiles(self, owner):
130 # Print owned files
131 self.printComments(owner)
132 print
133 print (self.bold_name(owner) + " owns " +
134 str(len(self.owners[owner])) + " file(s):")
135 for file_name in sorted(self.owners[owner]):
136 if file_name not in self.unreviewedFiles:
137 print " " + self.greyed(file_name)
138 else:
139 if len(self.ownership[file_name]) <= 3:
140 other_owners = []
141 for ow in self.ownership[file_name]:
142 if ow != owner:
143 other_owners.append(self.bold_name(ow))
144 print (" " + file_name +
145 " [" + (", ".join(other_owners)) + "]")
146 else:
147 print (" " + file_name +
148 " [" + self.bold(str(len(self.ownership[file_name]) - 1)) + "]")
149 print
150
151 def run(self):
152 self.reset()
153 owners_queue = sorted(self.owners.keys(),
154 key=lambda owner: len(self.owners[owner]),
155 reverse=True)
156
157 self.findMustPickOwners()
158 while len(owners_queue) > 0 and len(self.unreviewedFiles) > 0:
159 owner = owners_queue[0]
160 try:
161 if owner in self.usedOwners:
162 continue
163 if not any((file in self.unreviewedFiles)
164 for file in self.owners[owner]):
165 continue
166 print "====================="
167 print self.bold(str(len(self.unreviewedFiles))) + " file(s) left."
168 self.printOwnedFiles(owner)
169 while True:
170 print "Add " + self.bold_name(owner) + " as your reviewer? "
171 inp = raw_input("[" +
172 "/".join(["yes" ,
173 "no",
174 "Defer",
175 "pick",
176 "files" ,
177 "owners" ,
178 "quit"]) + "]")
179 inp = inp.lower()
180 if inp == "y" or inp == "yes":
181 self.useOwner(owner)
182 break
183 elif inp == "n" or inp == "no":
184 self.unuseOwner(owner)
185 break
186 elif inp == "" or inp == "d" or inp == "defer":
187 owners_queue.append(owner)
188 break
189 elif inp == "f" or inp == "files":
scheib 2013/03/11 17:08:41 Possibly factor out each of these sections into he
Bei Zhang 2013/03/11 18:12:21 Done.
190 if len(self.unreviewedFiles) > 5:
191 for file_name in sorted(self.unreviewedFiles):
192 print (file_name + " [" +
193 self.bold(str(len(self.ownership[file_name]))) + "]")
194 else:
195 for file_name in self.unreviewedFiles:
196 print file_name
197 for ow in sorted(self.ownership[file_name]):
198 if ow in self.unusedOwners:
199 print " " + self.bold_name(self.greyed(ow))
200 elif ow in self.usedOwners:
201 print " " + self.bold_name(self.greyed(ow))
202 else:
203 print " " + self.bold_name(ow)
204 elif inp == "o" or inp == "owners":
205 if (len(self.owners) - len(self.unusedOwners) -
206 len(self.usedOwners)) > 3:
207 for ow in owners_queue:
208 if ow not in self.unusedOwners and ow not in self.usedOwners:
209 self.printComments(ow)
210 else:
211 for ow in owners_queue:
212 if ow not in self.unusedOwners and ow not in self.usedOwners:
213 self.printOwnedFiles(ow)
214 elif inp == "p" or inp == "pick":
215 ow = raw_input("Pick an owner:")
216 if ow not in self.owners:
217 if ow + "@chromium.org" in self.owners:
218 ow += "@chromium.org"
219
220 do_pick = False
221 if ow in self.usedOwners or ow in self.unusedOwners:
222 print "You cannot pick " + self.bold_name(ow) + " manually."
223 elif ow in self.owners:
224 if not any(f in self.unreviewedFiles for f in self.owners[ow]):
225 while True:
226 inp = raw_input("Picking " + self.bold_name(ow) +
227 " does not cover any unreviewed file. "
228 "Are you sure? [y/N]").lower()
229 if inp == "y" or inp == "yes":
230 do_pick = True
231 break
232 elif inp == "" or inp == "n" or inp == "no":
233 break
234 else:
235 do_pick = True
236 else:
237 print "Invalid owner"
238
239 if do_pick:
240 self.useOwner(ow)
241 if owner in self.usedOwners or len(self.unreviewedFiles) == 0:
242 break
243
244 elif inp == "q" or inp == "quit":
245 # Exit with error
246 return 1
247
248 if len(self.unreviewedFiles) == 0:
249 print "Finished.\n\n"
250 break
251 finally:
252 owners_queue.pop(0)
253 # End of queue
254
255 # Print results
256 print "** You picked these owners **"
257 for owner in self.usedOwners:
258 print self.bold_name(owner) + ":"
259 for file_name in sorted(self.owners[owner]):
260 print " " + file_name
261 return 0
262
263 def main():
264 cl = git_cl.Changelist()
265 local_root = os.path.abspath(
266 git_cl.RunGit(['rev-parse', '--show-cdup']).strip() or '.')
267 base_branch = git_cl.RunGit(
268 ['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip()
269 changes = cl.GetChange(base_branch, None)
270 files = [f.LocalPath() for f in changes.AffectedFiles()]
271 return FindOwners(files, local_root).run()
272
273 if __name__ == "__main__":
274 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | git_cl.py » ('j') | no next file with comments »

Powered by Google App Engine