OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
Dirk Pranke
2013/04/03 00:54:16
2013.
Bei Zhang
2013/04/08 17:33:38
Done.
| |
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 owner_to_files 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 | |
15 def first(iterable): | |
Dirk Pranke
2013/04/03 00:54:16
I wouldn't use this function, it's easier to just
Bei Zhang
2013/04/08 17:33:38
It's for unordered set though. Maybe I should use
| |
16 for element in iterable: | |
17 return element | |
18 | |
19 | |
20 class FindOwners: | |
Dirk Pranke
2013/04/03 00:54:16
class FindOwners(object):
Bei Zhang
2013/04/08 17:33:38
Done.
| |
21 COLOR_LINK = '\033[4m' | |
22 COLOR_BOLD = '\033[1;32m' | |
23 COLOR_GREY = '\033[0;37m' | |
24 COLOR_RESET = '\033[0m' | |
25 def __init__(self, files, local_root, disable_color=False): | |
Dirk Pranke
2013/04/03 00:54:16
generally speaking, this method is too long, and i
Bei Zhang
2013/04/08 17:33:38
Done.
| |
26 # Files in a change list | |
27 self.owner_to_files = owner_to_files = {} | |
28 | |
29 self.file_to_owners = file_to_owners = {} | |
30 | |
31 if os.name == "nt" or disable_color: | |
32 self.COLOR_LINK = '' | |
33 self.COLOR_BOLD = '' | |
34 self.COLOR_GREY = '' | |
35 self.COLOR_RESET = '' | |
36 | |
37 db = owners_module.Database(local_root, | |
38 fopen=file, os_path=os.path, glob=glob.glob) | |
39 db.reviewers_for(files, None) | |
40 | |
41 for file_name in files: | |
42 owners_set = set() | |
43 if file_name in db.owners_for: | |
44 owners_set = owners_set | db.owners_for[file_name] | |
45 else: | |
46 dir_name = file_name | |
47 while dir_name != '': | |
48 if dir_name in db.stop_looking: | |
49 break | |
50 dir_name = os.path.dirname(dir_name) | |
51 if dir_name in db.owners_for: | |
52 owners_set = owners_set | db.owners_for[dir_name] | |
53 if owners_module.EVERYONE in owners_set: | |
54 break | |
Dirk Pranke
2013/04/03 00:54:16
can we just use db._all_possible_owners() here (yo
Bei Zhang
2013/04/08 17:33:38
I was a little bit scared of the leading underscor
| |
55 | |
56 if len(owners_set) == 0: | |
57 raise Exception("File '%s' has no owner" % file_name) | |
58 | |
59 # Eliminate files that EVERYONE can review | |
60 if owners_module.EVERYONE in owners_set: | |
61 continue | |
62 file_to_owners[file_name] = owners_set | |
63 | |
64 self.original_files_to_owners = copy.deepcopy(file_to_owners) | |
65 | |
66 for owner_name in db.owned_by: | |
67 if owner_name == owners_module.EVERYONE: | |
68 continue | |
69 files_set = set() | |
70 for file_name in file_to_owners: | |
71 if owner_name in file_to_owners[file_name]: | |
72 files_set.add(file_name) | |
73 if len(files_set) > 0: | |
74 owner_to_files[owner_name] = files_set | |
Dirk Pranke
2013/04/03 00:54:16
it's unfortunate that we can't use just db.owned_b
Bei Zhang
2013/04/24 00:13:08
Done.
| |
75 | |
76 self.comments = db.comments | |
77 self.unreviewedFiles = set() | |
78 self.reviewedBy = {} | |
79 self.usedOwners = set() | |
80 self.unusedOwners = set() | |
81 | |
82 def reset(self): | |
83 self.file_to_owners = copy.deepcopy(self.original_files_to_owners) | |
84 self.unreviewedFiles = set(self.file_to_owners.keys()) | |
85 self.reviewedBy = {} | |
86 self.usedOwners = set() | |
87 self.unusedOwners = set() | |
88 | |
89 def bold(self, text): | |
90 return (self.COLOR_BOLD + text + self.COLOR_RESET) | |
91 | |
92 def bold_name(self, name): | |
93 return (self.COLOR_BOLD + | |
94 name.replace("@chromium.org", "") + self.COLOR_RESET) | |
95 | |
96 def greyed(self, text): | |
97 return self.COLOR_GREY + text + self.COLOR_RESET | |
98 | |
99 def unuseOwner(self, owner): | |
100 self.unusedOwners.add(owner) | |
101 for file_name in self.owner_to_files[owner]: | |
102 if file_name in self.unreviewedFiles: | |
103 self.file_to_owners[file_name].remove(owner) | |
104 self.findMustPickOwners() | |
105 | |
106 def useOwner(self, owner): | |
107 self.usedOwners.add(owner) | |
108 for file_name in self.owner_to_files[owner]: | |
109 if file_name in self.unreviewedFiles: | |
110 self.unreviewedFiles.remove(file_name) | |
111 self.reviewedBy[file_name] = owner | |
112 self.findMustPickOwners() | |
113 | |
114 def findMustPickOwners(self): | |
Dirk Pranke
2013/04/03 00:54:16
so "findMustPickOwners" selects all the people tha
Bei Zhang
2013/04/08 17:33:38
Done. Sorry for my bad English.
On 2013/04/03 00:
| |
115 continues = True | |
116 while continues: | |
117 continues = False | |
118 for file_name in self.unreviewedFiles: | |
119 if len(self.file_to_owners[file_name]) == 1: | |
120 self.useOwner(first(self.file_to_owners[file_name])) | |
121 continues = True | |
122 break | |
Dirk Pranke
2013/04/03 00:54:16
Can you rewrite this as a loop over a filtered lis
Bei Zhang
2013/04/08 17:33:38
Done.
| |
123 | |
124 def printComments(self, owner): | |
125 if owner not in self.comments: | |
126 print self.bold_name(owner) | |
127 else: | |
128 print self.bold_name(owner) + " is commented as:" | |
129 for path in self.comments[owner]: | |
130 if len(self.comments[owner][path]) > 0: | |
131 print (self.greyed(" " + self.comments[owner][path]) + " (at " + | |
132 self.bold(path or "<root>") + ")") | |
133 else: | |
134 print (self.greyed(" [No comment] ") + " (at " + | |
135 self.bold(path or "<root>") + ")") | |
136 | |
137 def printOwnedFiles(self, owner): | |
138 # Print owned files | |
139 self.printComments(owner) | |
140 print | |
141 print (self.bold_name(owner) + " owns " + | |
142 str(len(self.owner_to_files[owner])) + " file(s):") | |
143 for file_name in sorted(self.owner_to_files[owner]): | |
144 if file_name not in self.unreviewedFiles: | |
145 print " " + self.greyed(file_name + ' (by ' + | |
146 self.bold_name(self.reviewedBy[file_name]) + | |
147 ')') | |
148 else: | |
149 if len(self.file_to_owners[file_name]) <= 3: | |
150 other_owners = [] | |
151 for ow in self.file_to_owners[file_name]: | |
152 if ow != owner: | |
153 other_owners.append(self.bold_name(ow)) | |
154 print (" " + file_name + | |
155 " [" + (", ".join(other_owners)) + "]") | |
156 else: | |
157 print (" " + file_name + | |
158 " [" + self.bold(str(len(self.file_to_owners[file_name]) - 1)) + "]") | |
159 print | |
160 | |
161 def listOwners(self, owners_queue): | |
162 if (len(self.owner_to_files) - len(self.unusedOwners) - | |
163 len(self.usedOwners)) > 3: | |
164 for ow in owners_queue: | |
165 if ow not in self.unusedOwners and ow not in self.usedOwners: | |
166 self.printComments(ow) | |
167 else: | |
168 for ow in owners_queue: | |
169 if ow not in self.unusedOwners and ow not in self.usedOwners: | |
170 self.printOwnedFiles(ow) | |
171 | |
172 def listFiles(self): | |
173 if len(self.unreviewedFiles) > 5: | |
174 for file_name in sorted(self.unreviewedFiles): | |
175 print (file_name + " [" + | |
176 self.bold(str(len(self.file_to_owners[file_name]))) + "]") | |
177 else: | |
178 for file_name in self.unreviewedFiles: | |
179 print file_name | |
180 for ow in sorted(self.file_to_owners[file_name]): | |
181 if ow in self.unusedOwners: | |
182 print " " + self.bold_name(self.greyed(ow)) | |
183 elif ow in self.usedOwners: | |
184 print " " + self.bold_name(self.greyed(ow)) | |
185 else: | |
186 print " " + self.bold_name(ow) | |
187 | |
188 def pickOwner(self, ow = False): | |
189 if not ow: | |
190 ow = raw_input("Pick an owner:") | |
191 | |
192 # Allowing to omit domain suffixes | |
193 if ow not in self.owner_to_files: | |
194 if ow + "@chromium.org" in self.owner_to_files: | |
195 ow += "@chromium.org" | |
196 | |
197 if ow not in self.owner_to_files: | |
198 print ("You cannot pick " + self.bold_name(ow) + " manually. " + | |
199 "It's an invalid name or not related to the change list.") | |
200 return False | |
201 elif ow in self.usedOwners: | |
202 print ("You cannot pick " + self.bold_name(ow) + " manually. " + | |
203 "It's already selected.") | |
204 return False | |
205 elif ow in self.unusedOwners: | |
206 print ("You cannot pick " + self.bold_name(ow) + " manually." + | |
207 "It's already unselected.") | |
208 return False | |
209 elif not any(f in self.unreviewedFiles for f in self.owner_to_files[ow]): | |
210 while True: | |
211 inp = raw_input("Picking " + self.bold_name(ow) + | |
212 " does not cover any unreviewed file. " | |
213 "Are you sure? [yes/No]").lower() | |
214 if inp == "" or inp == "n" or inp == "no": | |
215 return False | |
216 elif inp == "y" or inp == "yes": | |
217 break | |
218 | |
219 self.useOwner(ow) | |
220 return True | |
221 | |
222 def runOnce(self): | |
Dirk Pranke
2013/04/03 00:54:16
Can you document what runOnce() and run() are doin
Bei Zhang
2013/04/08 17:33:38
Done. It was for a hack to restart the run(). Remo
| |
223 self.reset() | |
224 | |
225 # Initialize owners queue, sort it by the number of files | |
226 # each owns | |
227 owners_queue = list(sorted(self.owner_to_files.keys(), | |
228 key=lambda owner: len(self.owner_to_files[owner]), | |
229 reverse=True)) | |
230 | |
231 self.findMustPickOwners() | |
232 while len(owners_queue) > 0 and len(self.unreviewedFiles) > 0: | |
233 owner = owners_queue[0] | |
234 try: | |
235 if owner in self.usedOwners: | |
236 continue | |
237 if len(self.unreviewedFiles) == 0: | |
238 print "Finished.\n\n" | |
239 break | |
240 if owner in self.unusedOwners: | |
241 # If this owner is already deseleted. | |
Dirk Pranke
2013/04/03 00:54:16
deselected?
Bei Zhang
2013/04/08 17:33:38
That is, when you select "no" on this owner.
So e
| |
242 continue | |
243 if not any((file_name in self.unreviewedFiles) | |
244 for file_name in self.owner_to_files[owner]): | |
245 self.unuseOwner(owner) | |
246 continue | |
247 print "=====================" | |
248 print self.bold(str(len(self.unreviewedFiles))) + " file(s) left." | |
249 self.printOwnedFiles(owner) | |
250 while True: | |
251 print "Add " + self.bold_name(owner) + " as your reviewer? " | |
252 inp = raw_input( | |
253 "[yes/no/Defer/pick/files/owners/quit/restart]: ").lower() | |
254 if inp == "y" or inp == "yes": | |
255 self.useOwner(owner) | |
256 break | |
257 elif inp == "n" or inp == "no": | |
258 self.unuseOwner(owner) | |
259 break | |
260 elif inp == "" or inp == "d" or inp == "defer": | |
261 owners_queue.append(owner) | |
262 break | |
263 elif inp == "f" or inp == "files": | |
264 self.listFiles() | |
265 elif inp == "o" or inp == "owners": | |
266 self.listOwners(owners_queue) | |
267 elif inp == "p" or inp == "pick": | |
268 self.pickOwner() | |
269 owners_queue.insert(0, owner) | |
270 break | |
271 elif inp.startswith("p ") or inp.startswith("pick "): | |
272 self.pickOwner(inp.split(' ', 2)[1]) | |
273 owners_queue.insert(0, owner) | |
274 break | |
275 elif inp == 'r' or inp == 'restart': | |
276 return -1 | |
277 elif inp == "q" or inp == "quit": | |
278 # Exit with error | |
279 return 1 | |
280 finally: | |
281 if len(owners_queue): | |
282 owners_queue.pop(0) | |
283 # End of queue | |
284 | |
285 # Print results | |
286 print "** You picked these owners **" | |
287 for owner in self.usedOwners: | |
288 print self.bold_name(owner) + ":" | |
289 for file_name in sorted(self.owner_to_files[owner]): | |
290 print " " + file_name | |
291 return 0 | |
292 | |
293 def run(self): | |
294 result = -1 | |
295 while result == -1: | |
296 result = self.runOnce() | |
297 return result | |
298 | |
299 | |
300 def main(): | |
Dirk Pranke
2013/04/03 00:54:16
Do you actually imagine this being used as a separ
Bei Zhang
2013/04/08 17:33:38
Done.
| |
301 cl = git_cl.Changelist() | |
302 local_root = os.path.abspath( | |
303 git_cl.RunGit(['rev-parse', '--show-cdup']).strip() or '.') | |
304 base_branch = git_cl.RunGit( | |
305 ['merge-base', cl.GetUpstreamBranch(), 'HEAD']).strip() | |
306 changes = cl.GetChange(base_branch, None) | |
307 files = [f.LocalPath() for f in changes.AffectedFiles()] | |
308 return FindOwners(files, local_root).run() | |
309 | |
310 | |
311 if __name__ == "__main__": | |
312 sys.exit(main()) | |
OLD | NEW |