Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(484)

Side by Side Diff: git_cl.py

Issue 8715007: Improve git-cl, add unit test! (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Fixes Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/git_cl_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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:]))
OLDNEW
« no previous file with comments | « no previous file | tests/git_cl_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698