OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
7 | 7 |
8 """A git-command for integrating reviews on Rietveld.""" | 8 """A git-command for integrating reviews on Rietveld.""" |
9 | 9 |
10 import logging | 10 import logging |
(...skipping 30 matching lines...) Expand all Loading... | |
41 import scm | 41 import scm |
42 import subprocess2 | 42 import subprocess2 |
43 import watchlists | 43 import watchlists |
44 | 44 |
45 | 45 |
46 DEFAULT_SERVER = 'http://codereview.appspot.com' | 46 DEFAULT_SERVER = 'http://codereview.appspot.com' |
47 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' | 47 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' |
48 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' | 48 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' |
49 | 49 |
50 | 50 |
51 # Initialized in main() | |
52 settings = None | |
53 | |
54 | |
51 def DieWithError(message): | 55 def DieWithError(message): |
52 print >> sys.stderr, message | 56 print >> sys.stderr, message |
53 sys.exit(1) | 57 sys.exit(1) |
54 | 58 |
55 | 59 |
56 def RunCommand(args, error_ok=False, error_message=None, **kwargs): | 60 def RunCommand(args, error_ok=False, error_message=None, **kwargs): |
57 try: | 61 try: |
58 return subprocess2.check_output(args, shell=False, **kwargs) | 62 return subprocess2.check_output(args, shell=False, **kwargs) |
59 except subprocess2.CalledProcessError, e: | 63 except subprocess2.CalledProcessError, e: |
60 if not error_ok: | 64 if not error_ok: |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
144 class Settings(object): | 148 class Settings(object): |
145 def __init__(self): | 149 def __init__(self): |
146 self.default_server = None | 150 self.default_server = None |
147 self.cc = None | 151 self.cc = None |
148 self.root = None | 152 self.root = None |
149 self.is_git_svn = None | 153 self.is_git_svn = None |
150 self.svn_branch = None | 154 self.svn_branch = None |
151 self.tree_status_url = None | 155 self.tree_status_url = None |
152 self.viewvc_url = None | 156 self.viewvc_url = None |
153 self.updated = False | 157 self.updated = False |
158 self.did_migrate_check = False | |
154 | 159 |
155 def LazyUpdateIfNeeded(self): | 160 def LazyUpdateIfNeeded(self): |
156 """Updates the settings from a codereview.settings file, if available.""" | 161 """Updates the settings from a codereview.settings file, if available.""" |
157 if not self.updated: | 162 if not self.updated: |
158 cr_settings_file = FindCodereviewSettingsFile() | 163 cr_settings_file = FindCodereviewSettingsFile() |
159 if cr_settings_file: | 164 if cr_settings_file: |
160 LoadCodereviewSettingsFromFile(cr_settings_file) | 165 LoadCodereviewSettingsFromFile(cr_settings_file) |
161 self.updated = True | 166 self.updated = True |
162 | 167 |
163 def GetDefaultServerUrl(self, error_ok=False): | 168 def GetDefaultServerUrl(self, error_ok=False): |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
265 return self.viewvc_url | 270 return self.viewvc_url |
266 | 271 |
267 def GetDefaultCCList(self): | 272 def GetDefaultCCList(self): |
268 return self._GetConfig('rietveld.cc', error_ok=True) | 273 return self._GetConfig('rietveld.cc', error_ok=True) |
269 | 274 |
270 def _GetConfig(self, param, **kwargs): | 275 def _GetConfig(self, param, **kwargs): |
271 self.LazyUpdateIfNeeded() | 276 self.LazyUpdateIfNeeded() |
272 return RunGit(['config', param], **kwargs).strip() | 277 return RunGit(['config', param], **kwargs).strip() |
273 | 278 |
274 | 279 |
275 settings = Settings() | |
276 | |
277 | |
278 did_migrate_check = False | |
279 def CheckForMigration(): | 280 def CheckForMigration(): |
280 """Migrate from the old issue format, if found. | 281 """Migrate from the old issue format, if found. |
281 | 282 |
282 We used to store the branch<->issue mapping in a file in .git, but it's | 283 We used to store the branch<->issue mapping in a file in .git, but it's |
283 better to store it in the .git/config, since deleting a branch deletes that | 284 better to store it in the .git/config, since deleting a branch deletes that |
284 branch's entry there. | 285 branch's entry there. |
285 """ | 286 """ |
286 | 287 |
287 # Don't run more than once. | 288 # Don't run more than once. |
288 global did_migrate_check | 289 if settings.did_migrate_check: |
289 if did_migrate_check: | |
290 return | 290 return |
291 | 291 |
292 gitdir = RunGit(['rev-parse', '--git-dir']).strip() | 292 gitdir = RunGit(['rev-parse', '--git-dir']).strip() |
293 storepath = os.path.join(gitdir, 'cl-mapping') | 293 storepath = os.path.join(gitdir, 'cl-mapping') |
294 if os.path.exists(storepath): | 294 if os.path.exists(storepath): |
295 print "old-style git-cl mapping file (%s) found; migrating." % storepath | 295 print "old-style git-cl mapping file (%s) found; migrating." % storepath |
296 store = open(storepath, 'r') | 296 store = open(storepath, 'r') |
297 for line in store: | 297 for line in store: |
298 branch, issue = line.strip().split() | 298 branch, issue = line.strip().split() |
299 RunGit(['config', 'branch.%s.rietveldissue' % ShortBranchName(branch), | 299 RunGit(['config', 'branch.%s.rietveldissue' % ShortBranchName(branch), |
300 issue]) | 300 issue]) |
301 store.close() | 301 store.close() |
302 os.remove(storepath) | 302 os.remove(storepath) |
303 did_migrate_check = True | 303 settings.did_migrate_check = True |
304 | 304 |
305 | 305 |
306 def ShortBranchName(branch): | 306 def ShortBranchName(branch): |
307 """Convert a name like 'refs/heads/foo' to just 'foo'.""" | 307 """Convert a name like 'refs/heads/foo' to just 'foo'.""" |
308 return branch.replace('refs/heads/', '') | 308 return branch.replace('refs/heads/', '') |
309 | 309 |
310 | 310 |
311 class Changelist(object): | 311 class Changelist(object): |
312 def __init__(self, branchref=None): | 312 def __init__(self, branchref=None): |
313 # Poke settings so we get the "configure your server" message if necessary. | 313 # Poke settings so we get the "configure your server" message if necessary. |
(...skipping 321 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
635 self.log_desc = log_desc | 635 self.log_desc = log_desc |
636 self.reviewers = reviewers | 636 self.reviewers = reviewers |
637 self.description = self.log_desc | 637 self.description = self.log_desc |
638 | 638 |
639 def Update(self): | 639 def Update(self): |
640 initial_text = """# Enter a description of the change. | 640 initial_text = """# Enter a description of the change. |
641 # This will displayed on the codereview site. | 641 # This will displayed on the codereview site. |
642 # The first line will also be used as the subject of the review. | 642 # The first line will also be used as the subject of the review. |
643 """ | 643 """ |
644 initial_text += self.description | 644 initial_text += self.description |
645 if 'R=' not in self.description and self.reviewers: | 645 if ('\nR=' not in self.description and |
646 '\nTBR=' not in self.description and | |
647 self.reviewers): | |
646 initial_text += '\nR=' + self.reviewers | 648 initial_text += '\nR=' + self.reviewers |
647 if 'BUG=' not in self.description: | 649 if '\nBUG=' not in self.description: |
648 initial_text += '\nBUG=' | 650 initial_text += '\nBUG=' |
649 if 'TEST=' not in self.description: | 651 if '\nTEST=' not in self.description: |
650 initial_text += '\nTEST=' | 652 initial_text += '\nTEST=' |
653 initial_text = initial_text.rstrip('\n') + '\n' | |
651 content = gclient_utils.RunEditor(initial_text, True) | 654 content = gclient_utils.RunEditor(initial_text, True) |
652 if not content: | 655 if not content: |
653 DieWithError('Running editor failed') | 656 DieWithError('Running editor failed') |
654 content = re.compile(r'^#.*$', re.MULTILINE).sub('', content).strip() | 657 content = re.compile(r'^#.*$', re.MULTILINE).sub('', content).strip() |
655 if not content: | 658 if not content: |
656 DieWithError('No CL description, aborting') | 659 DieWithError('No CL description, aborting') |
657 self._ParseDescription(content) | 660 self._ParseDescription(content) |
658 | 661 |
659 def _ParseDescription(self, description): | 662 def _ParseDescription(self, description): |
663 """Updates the list of reviewers and subject from the description.""" | |
660 if not description: | 664 if not description: |
661 self.description = description | 665 self.description = description |
662 return | 666 return |
663 | 667 |
664 parsed_lines = [] | 668 self.description = description.strip('\n') + '\n' |
665 reviewers_regexp = re.compile('\s*R=(.+)') | 669 self.subject = description.split('\n', 1)[0] |
666 reviewers = '' | 670 # Retrieves all reviewer lines |
667 subject = '' | 671 regexp = re.compile(r'^\s*(TBR|R)=(.+)$', re.MULTILINE) |
Dirk Pranke
2011/11/29 23:51:57
Nit: I'm not sure you need or want re.MULTILINE he
M-A Ruel
2011/11/30 14:14:18
Yes it's necessary.
| |
668 for l in description.splitlines(): | 672 self.reviewers = ','.join( |
669 if not subject: | 673 i.group(2).strip() for i in regexp.finditer(self.description)) |
670 subject = l | |
671 matched_reviewers = reviewers_regexp.match(l) | |
672 if matched_reviewers: | |
673 reviewers = matched_reviewers.group(1) | |
674 parsed_lines.append(l) | |
675 | |
676 self.description = '\n'.join(parsed_lines) + '\n' | |
677 self.subject = subject | |
678 self.reviewers = reviewers | |
679 | 674 |
680 def IsEmpty(self): | 675 def IsEmpty(self): |
681 return not self.description | 676 return not self.description |
682 | 677 |
683 | 678 |
684 def FindCodereviewSettingsFile(filename='codereview.settings'): | 679 def FindCodereviewSettingsFile(filename='codereview.settings'): |
685 """Finds the given file starting in the cwd and going up. | 680 """Finds the given file starting in the cwd and going up. |
686 | 681 |
687 Only looks up to the top of the repository unless an | 682 Only looks up to the top of the repository unless an |
688 'inherit-review-settings-ok' file exists in the root of the repository. | 683 'inherit-review-settings-ok' file exists in the root of the repository. |
(...skipping 680 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1369 command = '<command>' | 1364 command = '<command>' |
1370 else: | 1365 else: |
1371 # OptParser.description prefer nicely non-formatted strings. | 1366 # OptParser.description prefer nicely non-formatted strings. |
1372 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__) | 1367 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__) |
1373 parser.set_usage('usage: %%prog %s [options] %s' % (command, more)) | 1368 parser.set_usage('usage: %%prog %s [options] %s' % (command, more)) |
1374 | 1369 |
1375 | 1370 |
1376 def main(argv): | 1371 def main(argv): |
1377 """Doesn't parse the arguments here, just find the right subcommand to | 1372 """Doesn't parse the arguments here, just find the right subcommand to |
1378 execute.""" | 1373 execute.""" |
1374 # Reload settings. | |
1375 global settings | |
1376 settings = Settings() | |
1377 | |
1379 # Do it late so all commands are listed. | 1378 # Do it late so all commands are listed. |
1380 CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join([ | 1379 CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join([ |
1381 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip()) | 1380 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip()) |
1382 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')])) | 1381 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')])) |
1383 | 1382 |
1384 # Create the option parse and add --verbose support. | 1383 # Create the option parse and add --verbose support. |
1385 parser = optparse.OptionParser() | 1384 parser = optparse.OptionParser() |
1386 parser.add_option( | 1385 parser.add_option( |
1387 '-v', '--verbose', action='count', default=0, | 1386 '-v', '--verbose', action='count', default=0, |
1388 help='Use 2 times for more debugging info') | 1387 help='Use 2 times for more debugging info') |
(...skipping 24 matching lines...) Expand all Loading... | |
1413 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) | 1412 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) |
1414 | 1413 |
1415 # Not a known command. Default to help. | 1414 # Not a known command. Default to help. |
1416 GenUsage(parser, 'help') | 1415 GenUsage(parser, 'help') |
1417 return CMDhelp(parser, argv) | 1416 return CMDhelp(parser, argv) |
1418 | 1417 |
1419 | 1418 |
1420 if __name__ == '__main__': | 1419 if __name__ == '__main__': |
1421 fix_encoding.fix_encoding() | 1420 fix_encoding.fix_encoding() |
1422 sys.exit(main(sys.argv[1:])) | 1421 sys.exit(main(sys.argv[1:])) |
OLD | NEW |