OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # git-cl -- a git-command for integrating reviews on Rietveld | 2 # git-cl -- a git-command for integrating reviews on Rietveld |
3 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 3 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
4 | 4 |
5 import getpass | 5 import getpass |
6 import optparse | 6 import optparse |
7 import os | 7 import os |
8 import re | 8 import re |
9 import readline | |
10 import subprocess | 9 import subprocess |
11 import sys | 10 import sys |
12 import tempfile | 11 import tempfile |
13 import textwrap | 12 import textwrap |
14 import upload | 13 import upload |
15 import urllib2 | 14 import urllib2 |
16 | 15 |
| 16 try: |
| 17 import readline |
| 18 except ImportError: |
| 19 pass |
| 20 |
17 DEFAULT_SERVER = 'codereview.appspot.com' | 21 DEFAULT_SERVER = 'codereview.appspot.com' |
18 | 22 |
19 def DieWithError(message): | 23 def DieWithError(message): |
20 print >>sys.stderr, message | 24 print >>sys.stderr, message |
21 sys.exit(1) | 25 sys.exit(1) |
22 | 26 |
23 | 27 |
24 def RunGit(args, error_ok=False, error_message=None, exit_code=False): | 28 def RunGit(args, error_ok=False, error_message=None, exit_code=False, |
| 29 redirect_stdout=True): |
25 cmd = ['git'] + args | 30 cmd = ['git'] + args |
26 # Useful for debugging: | 31 # Useful for debugging: |
27 # print >>sys.stderr, ' '.join(cmd) | 32 # print >>sys.stderr, ' '.join(cmd) |
28 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) | 33 if redirect_stdout: |
| 34 stdout = subprocess.PIPE |
| 35 else: |
| 36 stdout = None |
| 37 proc = subprocess.Popen(cmd, stdout=stdout) |
29 output = proc.communicate()[0] | 38 output = proc.communicate()[0] |
30 if exit_code: | 39 if exit_code: |
31 return proc.returncode | 40 return proc.returncode |
32 if not error_ok and proc.returncode != 0: | 41 if not error_ok and proc.returncode != 0: |
33 DieWithError('Command "%s" failed.\n' % (' '.join(cmd)) + | 42 DieWithError('Command "%s" failed.\n' % (' '.join(cmd)) + |
34 (error_message or output)) | 43 (error_message or output)) |
35 return output | 44 return output |
36 | 45 |
37 | 46 |
38 class Settings: | 47 class Settings: |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
79 # 2) iterate through our branch history and match up the URLs. | 88 # 2) iterate through our branch history and match up the URLs. |
80 | 89 |
81 # regexp matching the git-svn line that contains the URL. | 90 # regexp matching the git-svn line that contains the URL. |
82 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE) | 91 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE) |
83 | 92 |
84 # Get the refname and svn url for all refs/remotes/*. | 93 # Get the refname and svn url for all refs/remotes/*. |
85 remotes = RunGit(['for-each-ref', '--format=%(refname)', | 94 remotes = RunGit(['for-each-ref', '--format=%(refname)', |
86 'refs/remotes']).splitlines() | 95 'refs/remotes']).splitlines() |
87 svn_refs = {} | 96 svn_refs = {} |
88 for ref in remotes: | 97 for ref in remotes: |
89 # git-svn remote refs are generally directly in the refs/remotes/dir, | |
90 # not a subdirectory (like refs/remotes/origin/master). | |
91 if '/' in ref[len('refs/remotes/'):]: | |
92 continue | |
93 match = git_svn_re.search(RunGit(['cat-file', '-p', ref])) | 98 match = git_svn_re.search(RunGit(['cat-file', '-p', ref])) |
94 if match: | 99 if match: |
95 svn_refs[match.group(1)] = ref | 100 svn_refs[match.group(1)] = ref |
96 | 101 |
97 if len(svn_refs) == 1: | 102 if len(svn_refs) == 1: |
98 # Only one svn branch exists -- seems like a good candidate. | 103 # Only one svn branch exists -- seems like a good candidate. |
99 self.svn_branch = svn_refs.values()[0] | 104 self.svn_branch = svn_refs.values()[0] |
100 elif len(svn_refs) > 1: | 105 elif len(svn_refs) > 1: |
101 # We have more than one remote branch available. We don't | 106 # We have more than one remote branch available. We don't |
102 # want to go through all of history, so read a line from the | 107 # want to go through all of history, so read a line from the |
(...skipping 267 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
370 | 375 |
371 file = tempfile.NamedTemporaryFile() | 376 file = tempfile.NamedTemporaryFile() |
372 filename = file.name | 377 filename = file.name |
373 file.write(starting_text) | 378 file.write(starting_text) |
374 file.flush() | 379 file.flush() |
375 | 380 |
376 ret = subprocess.call(editor + ' ' + filename, shell=True) | 381 ret = subprocess.call(editor + ' ' + filename, shell=True) |
377 if ret != 0: | 382 if ret != 0: |
378 return | 383 return |
379 | 384 |
380 text = open(filename).read() | 385 file.flush() |
| 386 file.seek(0) |
| 387 text = file.read() |
381 file.close() | 388 file.close() |
382 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) | 389 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE) |
383 return stripcomment_re.sub('', text).strip() | 390 return stripcomment_re.sub('', text).strip() |
384 | 391 |
385 | 392 |
386 def CmdUpload(args): | 393 def CmdUpload(args): |
387 parser = optparse.OptionParser( | 394 parser = optparse.OptionParser( |
388 usage='git cl upload [options] [args to "git diff"]') | 395 usage='git cl upload [options] [args to "git diff"]') |
389 parser.add_option('-m', dest='message', help='message for patch') | 396 parser.add_option('-m', dest='message', help='message for patch') |
390 parser.add_option('-r', '--reviewers', | 397 parser.add_option('-r', '--reviewers', |
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
514 | 521 |
515 # We want to squash all this branch's commits into one commit with the | 522 # We want to squash all this branch's commits into one commit with the |
516 # proper description. | 523 # proper description. |
517 # We do this by doing a "merge --squash" into a new commit branch, then | 524 # We do this by doing a "merge --squash" into a new commit branch, then |
518 # dcommitting that. | 525 # dcommitting that. |
519 MERGE_BRANCH = 'git-cl-commit' | 526 MERGE_BRANCH = 'git-cl-commit' |
520 # Delete the merge branch if it already exists. | 527 # Delete the merge branch if it already exists. |
521 if RunGit(['show-ref', '--quiet', '--verify', 'refs/heads/' + MERGE_BRANCH], | 528 if RunGit(['show-ref', '--quiet', '--verify', 'refs/heads/' + MERGE_BRANCH], |
522 exit_code=True) == 0: | 529 exit_code=True) == 0: |
523 RunGit(['branch', '-D', MERGE_BRANCH]) | 530 RunGit(['branch', '-D', MERGE_BRANCH]) |
| 531 |
| 532 # We might be in a directory that's present in this branch but not in the |
| 533 # trunk. Move up to the top of the tree so that git commands that expect a |
| 534 # valid CWD won't fail after we check out the merge branch. |
| 535 rel_base_path = RunGit(['rev-parse', '--show-cdup']).strip() |
| 536 if rel_base_path: |
| 537 os.chdir(rel_base_path) |
| 538 |
524 # Stuff our change into the merge branch. | 539 # Stuff our change into the merge branch. |
525 RunGit(['checkout', '-q', '-b', MERGE_BRANCH, base_branch]) | 540 RunGit(['checkout', '-q', '-b', MERGE_BRANCH, base_branch]) |
526 RunGit(['merge', '--squash', cl.GetBranchRef()]) | 541 RunGit(['merge', '--squash', cl.GetBranchRef()]) |
527 RunGit(['commit', '-m', description]) | 542 RunGit(['commit', '-m', description]) |
528 # dcommit the merge branch. | 543 # dcommit the merge branch. |
529 output = RunGit(['svn', 'dcommit']) | 544 output = RunGit(['svn', 'dcommit']) |
530 # And then swap back to the original branch and clean up. | 545 # And then swap back to the original branch and clean up. |
531 RunGit(['checkout', '-q', cl.GetBranch()]) | 546 RunGit(['checkout', '-q', cl.GetBranch()]) |
532 RunGit(['branch', '-D', MERGE_BRANCH]) | 547 RunGit(['branch', '-D', MERGE_BRANCH]) |
533 if output.find("Committed r") != -1: | 548 if output.find("Committed r") != -1: |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
611 RunGit(['commit', '-m', 'patch from issue %s' % issue]) | 626 RunGit(['commit', '-m', 'patch from issue %s' % issue]) |
612 cl = Changelist() | 627 cl = Changelist() |
613 cl.SetIssue(issue) | 628 cl.SetIssue(issue) |
614 print "Committed patch." | 629 print "Committed patch." |
615 else: | 630 else: |
616 print "Patch applied to index." | 631 print "Patch applied to index." |
617 | 632 |
618 def CmdRebase(args): | 633 def CmdRebase(args): |
619 # Provide a wrapper for git svn rebase to help avoid accidental | 634 # Provide a wrapper for git svn rebase to help avoid accidental |
620 # git svn dcommit. | 635 # git svn dcommit. |
621 RunGit(['svn', 'rebase']) | 636 RunGit(['svn', 'rebase'], redirect_stdout=False) |
622 | 637 |
623 def GetTreeStatus(): | 638 def GetTreeStatus(): |
624 """Fetches the tree status and returns either 'open', 'closed', | 639 """Fetches the tree status and returns either 'open', 'closed', |
625 'unknown' or 'unset'.""" | 640 'unknown' or 'unset'.""" |
626 url = settings.GetTreeStatusUrl(error_ok=True) | 641 url = settings.GetTreeStatusUrl(error_ok=True) |
627 if url: | 642 if url: |
628 status = urllib2.urlopen(url).read().lower() | 643 status = urllib2.urlopen(url).read().lower() |
629 if status.find('closed') != -1: | 644 if status.find('closed') != -1: |
630 return 'closed' | 645 return 'closed' |
631 elif status.find('open') != -1: | 646 elif status.find('open') != -1: |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
673 command = argv[1] | 688 command = argv[1] |
674 for name, _, func in COMMANDS: | 689 for name, _, func in COMMANDS: |
675 if name == command: | 690 if name == command: |
676 return func(argv[2:]) | 691 return func(argv[2:]) |
677 print 'unknown command: %s' % command | 692 print 'unknown command: %s' % command |
678 Usage(argv[0]) | 693 Usage(argv[0]) |
679 | 694 |
680 | 695 |
681 if __name__ == '__main__': | 696 if __name__ == '__main__': |
682 sys.exit(main(sys.argv)) | 697 sys.exit(main(sys.argv)) |
OLD | NEW |