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

Side by Side Diff: git-cl.py

Issue 114076: Delete the git-cl and git-try copies (Closed)
Patch Set: Created 11 years, 6 months 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
« no previous file with comments | « no previous file | git-try.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 # git-cl -- a git-command for integrating reviews on Rietveld
3 # Copyright (C) 2008 Evan Martin <martine@danga.com>
4
5 import getpass
6 import optparse
7 import os
8 import re
9 import subprocess
10 import sys
11 import tempfile
12 import textwrap
13 import upload
14 import urllib2
15
16 try:
17 import readline
18 except ImportError:
19 pass
20
21 DEFAULT_SERVER = 'codereview.appspot.com'
22
23 def DieWithError(message):
24 print >>sys.stderr, message
25 sys.exit(1)
26
27
28 def RunGit(args, error_ok=False, error_message=None, exit_code=False,
29 redirect_stdout=True):
30 cmd = ['git'] + args
31 # Useful for debugging:
32 # print >>sys.stderr, ' '.join(cmd)
33 if redirect_stdout:
34 stdout = subprocess.PIPE
35 else:
36 stdout = None
37 proc = subprocess.Popen(cmd, stdout=stdout)
38 output = proc.communicate()[0]
39 if exit_code:
40 return proc.returncode
41 if not error_ok and proc.returncode != 0:
42 DieWithError('Command "%s" failed.\n' % (' '.join(cmd)) +
43 (error_message or output))
44 return output
45
46
47 class Settings:
48 def __init__(self):
49 self.server = None
50 self.cc = None
51 self.is_git_svn = None
52 self.svn_branch = None
53 self.tree_status_url = None
54 self.viewvc_url = None
55
56 def GetServer(self, error_ok=False):
57 if not self.server:
58 if not error_ok:
59 error_message = ('You must configure your review setup by running '
60 '"git cl config".')
61 self.server = self._GetConfig('rietveld.server',
62 error_message=error_message)
63 else:
64 self.server = self._GetConfig('rietveld.server', error_ok=True)
65 return self.server
66
67 def GetCCList(self):
68 if self.cc is None:
69 self.cc = self._GetConfig('rietveld.cc', error_ok=True)
70 return self.cc
71
72 def GetIsGitSvn(self):
73 """Return true if this repo looks like it's using git-svn."""
74 if self.is_git_svn is None:
75 # If you have any "svn-remote.*" config keys, we think you're using svn.
76 self.is_git_svn = RunGit(['config', '--get-regexp', r'^svn-remote\.'],
77 exit_code=True) == 0
78 return self.is_git_svn
79
80 def GetSVNBranch(self):
81 if self.svn_branch is None:
82 if not self.GetIsGitSvn():
83 raise "Repo doesn't appear to be a git-svn repo."
84
85 # Try to figure out which remote branch we're based on.
86 # Strategy:
87 # 1) find all git-svn branches and note their svn URLs.
88 # 2) iterate through our branch history and match up the URLs.
89
90 # regexp matching the git-svn line that contains the URL.
91 git_svn_re = re.compile(r'^\s*git-svn-id: (\S+)@', re.MULTILINE)
92
93 # Get the refname and svn url for all refs/remotes/*.
94 remotes = RunGit(['for-each-ref', '--format=%(refname)',
95 'refs/remotes']).splitlines()
96 svn_refs = {}
97 for ref in remotes:
98 match = git_svn_re.search(RunGit(['cat-file', '-p', ref]))
99 if match:
100 svn_refs[match.group(1)] = ref
101
102 if len(svn_refs) == 1:
103 # Only one svn branch exists -- seems like a good candidate.
104 self.svn_branch = svn_refs.values()[0]
105 elif len(svn_refs) > 1:
106 # We have more than one remote branch available. We don't
107 # want to go through all of history, so read a line from the
108 # pipe at a time.
109 # The -100 is an arbitrary limit so we don't search forever.
110 cmd = ['git', 'log', '-100']
111 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
112 for line in proc.stdout:
113 match = git_svn_re.match(line)
114 if match:
115 url = match.group(1)
116 if url in svn_refs:
117 self.svn_branch = svn_refs[url]
118 proc.stdout.close() # Cut pipe.
119 break
120
121 if not self.svn_branch:
122 raise "Can't guess svn branch -- try specifying it on the command line"
123
124 return self.svn_branch
125
126 def GetTreeStatusUrl(self, error_ok=False):
127 if not self.tree_status_url:
128 error_message = ('You must configure your tree status URL by running '
129 '"git cl config".')
130 self.tree_status_url = self._GetConfig('rietveld.tree-status-url',
131 error_ok=error_ok,
132 error_message=error_message)
133 return self.tree_status_url
134
135 def GetViewVCUrl(self):
136 if not self.viewvc_url:
137 self.viewvc_url = self._GetConfig('rietveld.viewvc-url', error_ok=True)
138 return self.viewvc_url
139
140 def _GetConfig(self, param, **kwargs):
141 return RunGit(['config', param], **kwargs).strip()
142
143
144 settings = Settings()
145
146
147 did_migrate_check = False
148 def CheckForMigration():
149 """Migrate from the old issue format, if found.
150
151 We used to store the branch<->issue mapping in a file in .git, but it's
152 better to store it in the .git/config, since deleting a branch deletes that
153 branch's entry there.
154 """
155
156 # Don't run more than once.
157 global did_migrate_check
158 if did_migrate_check:
159 return
160
161 gitdir = RunGit(['rev-parse', '--git-dir']).strip()
162 storepath = os.path.join(gitdir, 'cl-mapping')
163 if os.path.exists(storepath):
164 print "old-style git-cl mapping file (%s) found; migrating." % storepath
165 store = open(storepath, 'r')
166 for line in store:
167 branch, issue = line.strip().split()
168 RunGit(['config', 'branch.%s.rietveldissue' % ShortBranchName(branch),
169 issue])
170 store.close()
171 os.remove(storepath)
172 did_migrate_check = True
173
174
175 def IssueURL(issue):
176 """Get the URL for a particular issue."""
177 return 'http://%s/%s' % (settings.GetServer(), issue)
178
179
180 def ShortBranchName(branch):
181 """Convert a name like 'refs/heads/foo' to just 'foo'."""
182 return branch.replace('refs/heads/', '')
183
184
185 class Changelist:
186 def __init__(self, branchref=None):
187 # Poke settings so we get the "configure your server" message if necessary.
188 settings.GetServer()
189 self.branchref = branchref
190 if self.branchref:
191 self.branch = ShortBranchName(self.branchref)
192 else:
193 self.branch = None
194 self.upstream_branch = None
195 self.has_issue = False
196 self.issue = None
197 self.has_description = False
198 self.description = None
199
200 def GetBranch(self):
201 """Returns the short branch name, e.g. 'master'."""
202 if not self.branch:
203 self.branchref = RunGit(['symbolic-ref', 'HEAD']).strip()
204 self.branch = ShortBranchName(self.branchref)
205 return self.branch
206 def GetBranchRef(self):
207 """Returns the full branch name, e.g. 'refs/heads/master'."""
208 self.GetBranch() # Poke the lazy loader.
209 return self.branchref
210
211 def GetUpstreamBranch(self):
212 if self.upstream_branch is None:
213 branch = self.GetBranch()
214 upstream_branch = RunGit(['config', 'branch.%s.merge' % branch],
215 error_ok=True).strip()
216 if upstream_branch:
217 remote = RunGit(['config', 'branch.%s.remote' % branch]).strip()
218 # We have remote=origin and branch=refs/heads/foobar; convert to
219 # refs/remotes/origin/foobar.
220 self.upstream_branch = upstream_branch.replace('heads',
221 'remotes/' + remote)
222
223 if not self.upstream_branch:
224 # Fall back on trying a git-svn upstream branch.
225 if settings.GetIsGitSvn():
226 self.upstream_branch = settings.GetSVNBranch()
227
228 if not self.upstream_branch:
229 DieWithError("""Unable to determine default branch to diff against.
230 Either pass complete "git diff"-style arguments, like
231 git cl upload origin/master
232 or verify this branch is set up to track another (via the --track argument to
233 "git checkout -b ...").""")
234
235 return self.upstream_branch
236
237 def GetIssue(self):
238 if not self.has_issue:
239 CheckForMigration()
240 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip()
241 if issue:
242 self.issue = issue
243 else:
244 self.issue = None
245 self.has_issue = True
246 return self.issue
247
248 def GetIssueURL(self):
249 return IssueURL(self.GetIssue())
250
251 def GetDescription(self, pretty=False):
252 if not self.has_description:
253 if self.GetIssue():
254 url = self.GetIssueURL() + '/description'
255 self.description = urllib2.urlopen(url).read().strip()
256 self.has_description = True
257 if pretty:
258 wrapper = textwrap.TextWrapper()
259 wrapper.initial_indent = wrapper.subsequent_indent = ' '
260 return wrapper.fill(self.description)
261 return self.description
262
263 def GetPatchset(self):
264 if not self.has_patchset:
265 patchset = RunGit(['config', self._PatchsetSetting()],
266 error_ok=True).strip()
267 if patchset:
268 self.patchset = patchset
269 else:
270 self.patchset = None
271 self.has_patchset = True
272 return self.patchset
273
274 def SetPatchset(self, patchset):
275 """Set this branch's patchset. If patchset=0, clears the patchset."""
276 if patchset:
277 RunGit(['config', self._PatchsetSetting(), str(patchset)])
278 else:
279 RunGit(['config', '--unset', self._PatchsetSetting()])
280 self.has_patchset = False
281
282 def SetIssue(self, issue):
283 """Set this branch's issue. If issue=0, clears the issue."""
284 if issue:
285 RunGit(['config', self._IssueSetting(), str(issue)])
286 else:
287 RunGit(['config', '--unset', self._IssueSetting()])
288 self.SetPatchset(0)
289 self.has_issue = False
290
291 def CloseIssue(self):
292 def GetUserCredentials():
293 email = raw_input('Email: ').strip()
294 password = getpass.getpass('Password for %s: ' % email)
295 return email, password
296
297 rpc_server = upload.HttpRpcServer(settings.GetServer(),
298 GetUserCredentials,
299 host_override=settings.GetServer(),
300 save_cookies=True)
301 # You cannot close an issue with a GET.
302 # We pass an empty string for the data so it is a POST rather than a GET.
303 data = [("description", self.description),]
304 ctype, body = upload.EncodeMultipartFormData(data, [])
305 rpc_server.Send('/' + self.GetIssue() + '/close', body, ctype)
306
307 def _IssueSetting(self):
308 """Return the git setting that stores this change's issue."""
309 return 'branch.%s.rietveldissue' % self.GetBranch()
310
311 def _PatchsetSetting(self):
312 """Return the git setting that stores this change's most recent patchset."""
313 return 'branch.%s.rietveldpatchset' % self.GetBranch()
314
315
316 def CmdConfig(args):
317 server = settings.GetServer(error_ok=True)
318 prompt = 'Rietveld server (host[:port])'
319 prompt += ' [%s]' % (server or DEFAULT_SERVER)
320 newserver = raw_input(prompt + ': ')
321 if not server and not newserver:
322 newserver = DEFAULT_SERVER
323 if newserver and newserver != server:
324 RunGit(['config', 'rietveld.server', newserver])
325
326 def SetProperty(initial, caption, name):
327 prompt = caption
328 if initial:
329 prompt += ' ("x" to clear) [%s]' % initial
330 new_val = raw_input(prompt + ': ')
331 if new_val == 'x':
332 RunGit(['config', '--unset-all', 'rietveld.' + name], error_ok=True)
333 elif new_val and new_val != initial:
334 RunGit(['config', 'rietveld.' + name, new_val])
335
336 SetProperty(settings.GetCCList(), 'CC list', 'cc')
337 SetProperty(settings.GetTreeStatusUrl(error_ok=True), 'Tree status URL',
338 'tree-status-url')
339 SetProperty(settings.GetViewVCUrl(), 'ViewVC URL', 'viewvc-url')
340
341 # TODO: configure a default branch to diff against, rather than this
342 # svn-based hackery.
343
344
345 def CmdStatus(args):
346 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads'])
347 if branches:
348 print 'Branches associated with reviews:'
349 for branch in sorted(branches.splitlines()):
350 cl = Changelist(branchref=branch)
351 print " %10s: %s" % (cl.GetBranch(), cl.GetIssue())
352
353 cl = Changelist()
354 print
355 print 'Current branch:',
356 if not cl.GetIssue():
357 print 'no issue assigned.'
358 return 0
359 print cl.GetBranch()
360 print 'Issue number:', cl.GetIssue(), '(%s)' % cl.GetIssueURL()
361 print 'Issue description:'
362 print cl.GetDescription(pretty=True)
363
364
365 def CmdIssue(args):
366 cl = Changelist()
367 if len(args) > 0:
368 cl.SetIssue(int(args[0]))
369 print 'Issue number:', cl.GetIssue(), '(%s)' % cl.GetIssueURL()
370
371
372 def UserEditedLog(starting_text):
373 """Given some starting text, let the user edit it and return the result."""
374 editor = os.getenv('EDITOR', 'vi')
375
376 file = tempfile.NamedTemporaryFile()
377 filename = file.name
378 file.write(starting_text)
379 file.flush()
380
381 ret = subprocess.call(editor + ' ' + filename, shell=True)
382 if ret != 0:
383 return
384
385 file.flush()
386 file.seek(0)
387 text = file.read()
388 file.close()
389 stripcomment_re = re.compile(r'^#.*$', re.MULTILINE)
390 return stripcomment_re.sub('', text).strip()
391
392
393 def CmdUpload(args):
394 parser = optparse.OptionParser(
395 usage='git cl upload [options] [args to "git diff"]')
396 parser.add_option('-m', dest='message', help='message for patch')
397 parser.add_option('-r', '--reviewers',
398 help='reviewer email addresses')
399 parser.add_option('--send-mail', action='store_true',
400 help='send email to reviewer immediately')
401 (options, args) = parser.parse_args(args)
402
403 cl = Changelist()
404 if not args:
405 # Default to diffing against the "upstream" branch.
406 args = [cl.GetUpstreamBranch()]
407 # --no-ext-diff is broken in some versions of Git, so try to work around
408 # this by overriding the environment (but there is still a problem if the
409 # git config key "diff.external" is used).
410 env = os.environ.copy()
411 if 'GIT_EXTERNAL_DIFF' in env: del env['GIT_EXTERNAL_DIFF']
412 subprocess.call(['git', 'diff', '--no-ext-diff', '--stat', ] + args, env=env)
413
414 upload_args = ['--assume_yes'] # Don't ask about untracked files.
415 upload_args.extend(['--server', settings.GetServer()])
416 if options.reviewers:
417 upload_args.extend(['--reviewers', options.reviewers])
418 upload_args.extend(['--cc', settings.GetCCList()])
419 if options.message:
420 upload_args.extend(['--message', options.message])
421 if options.send_mail:
422 if not options.reviewers:
423 DieWithError("Must specify reviewers to send email.")
424 upload_args.append('--send_mail')
425 if cl.GetIssue():
426 upload_args.extend(['--issue', cl.GetIssue()])
427 print ("This branch is associated with issue %s. "
428 "Adding patch to that issue." % cl.GetIssue())
429 else:
430 # Construct a description for this change from the log.
431 # We need to convert diff options to log options.
432 log_args = []
433 if len(args) == 1 and not args[0].endswith('.'):
434 log_args = [args[0] + '..']
435 elif len(args) == 2:
436 log_args = [args[0] + '..' + args[1]]
437 else:
438 log_args = args[:] # Hope for the best!
439 desc = RunGit(['log', '--pretty=format:%s\n\n%b'] + log_args)
440 initial_text = """# Enter a description of the change.
441 # This will displayed on the codereview site.
442 # The first line will also be used as the subject of the review."""
443 desc = UserEditedLog(initial_text + '\n' + desc)
444 if not desc:
445 print "Description empty; aborting."
446 return 1
447 subject = desc.splitlines()[0]
448 upload_args.extend(['--message', subject])
449 upload_args.extend(['--description', desc])
450 issue, patchset = upload.RealMain(['upload'] + upload_args + args)
451 if not cl.GetIssue():
452 cl.SetIssue(issue)
453 cl.SetPatchset(patchset)
454
455
456 def CmdDCommit(args):
457 parser = optparse.OptionParser(
458 usage='git cl dcommit [options] [git-svn branch to apply against]')
459 parser.add_option('-f', action='store_true', dest='force',
460 help="force yes to questions (don't prompt)")
461 parser.add_option('-c', dest='contributor',
462 help="external contributor for patch (appended to " +
463 "description)")
464 (options, args) = parser.parse_args(args)
465
466 cl = Changelist()
467
468 if not args:
469 # Default to merging against our best guess of the upstream branch.
470 args = [cl.GetUpstreamBranch()]
471
472 base_branch = args[0]
473
474 # It is important to have these checks at the top. Not only for user
475 # convenience, but also because the cl object then caches the correct values
476 # of these fields even as we're juggling branches for setting up the commit.
477 if not cl.GetIssue():
478 print 'Current issue unknown -- has this branch been uploaded?'
479 return 1
480 if not cl.GetDescription():
481 print 'No description set.'
482 print 'Visit %s/edit to set it.' % (cl.GetIssueURL())
483 return 1
484
485 if RunGit(['diff-index', 'HEAD']):
486 print 'Cannot dcommit with a dirty tree. You must commit locally first.'
487 return 1
488
489 # This rev-list syntax means "show all commits not in my branch that
490 # are in base_branch".
491 upstream_commits = RunGit(['rev-list', '^' + cl.GetBranchRef(),
492 base_branch]).splitlines()
493 if upstream_commits:
494 print ('Base branch "%s" has %d commits '
495 'not in this branch.' % (base_branch, len(upstream_commits)))
496 print 'Run "git merge %s" before attempting to dcommit.' % base_branch
497 return 1
498
499 if not options.force:
500 # Check the tree status if the tree status URL is set.
501 status = GetTreeStatus()
502 if 'closed' == status:
503 print ('The tree is closed. Please wait for it to reopen. Use '
504 '"git cl dcommit -f" to commit on a closed tree.')
505 return 1
506 elif 'unknown' == status:
507 print ('Unable to determine tree status. Please verify manually and '
508 'use "git cl dcommit -f" to commit on a closed tree.')
509
510 description = cl.GetDescription()
511
512 description += "\n\nReview URL: %s" % cl.GetIssueURL()
513 if options.contributor:
514 description += "\nPatch from %s." % options.contributor
515 print 'Description:', repr(description)
516
517 branches = [base_branch, cl.GetBranchRef()]
518 if not options.force:
519 subprocess.call(['git', 'diff', '--stat'] + branches)
520 raw_input("About to commit; enter to confirm.")
521
522 # We want to squash all this branch's commits into one commit with the
523 # proper description.
524 # We do this by doing a "merge --squash" into a new commit branch, then
525 # dcommitting that.
526 MERGE_BRANCH = 'git-cl-commit'
527 # Delete the merge branch if it already exists.
528 if RunGit(['show-ref', '--quiet', '--verify', 'refs/heads/' + MERGE_BRANCH],
529 exit_code=True) == 0:
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
539 # Stuff our change into the merge branch.
540 RunGit(['checkout', '-q', '-b', MERGE_BRANCH, base_branch])
541 RunGit(['merge', '--squash', cl.GetBranchRef()])
542 RunGit(['commit', '-m', description])
543 # dcommit the merge branch.
544 output = RunGit(['svn', 'dcommit'])
545 # And then swap back to the original branch and clean up.
546 RunGit(['checkout', '-q', cl.GetBranch()])
547 RunGit(['branch', '-D', MERGE_BRANCH])
548 if output.find("Committed r") != -1:
549 print "Closing issue (you may be prompted for your codereview password)..."
550 if cl.has_issue:
551 viewvc_url = settings.GetViewVCUrl()
552 if viewvc_url:
553 revision = re.compile(".*?\nCommitted r(\d+)",
554 re.DOTALL).match(output).group(1)
555 cl.description = (cl.description +
556 "\n\nCommitted: " + viewvc_url + revision)
557 cl.CloseIssue()
558 cl.SetIssue(0)
559
560
561 def CmdPatch(args):
562 parser = optparse.OptionParser(usage=('git cl patch [options] '
563 '<patch url or issue id>'))
564 parser.add_option('-b', dest='newbranch',
565 help='create a new branch off trunk for the patch')
566 parser.add_option('-f', action='store_true', dest='force',
567 help='with -b, clobber any existing branch')
568 parser.add_option('--reject', action='store_true', dest='reject',
569 help='allow failed patches and spew .rej files')
570 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
571 help="don't commit after patch applies")
572 (options, args) = parser.parse_args(args)
573 if len(args) != 1:
574 return parser.print_help()
575 input = args[0]
576
577 if re.match(r'\d+', input):
578 # Input is an issue id. Figure out the URL.
579 issue = input
580 fetch = "curl --silent http://%s/%s" % (settings.GetServer(), issue)
581 grep = "grep -E -o '/download/issue[0-9]+_[0-9]+.diff'"
582 pipe = subprocess.Popen("%s | %s" % (fetch, grep), shell=True,
583 stdout=subprocess.PIPE)
584 path = pipe.stdout.read().strip()
585 url = 'http://%s%s' % (settings.GetServer(), path)
586 else:
587 # Assume it's a URL to the patch.
588 match = re.match(r'http://.*?/issue(\d+)_\d+.diff', input)
589 if match:
590 issue = match.group(1)
591 url = input
592 else:
593 print "Must pass an issue ID or full URL for 'Download raw patch set'"
594 return 1
595
596 if options.newbranch:
597 if options.force:
598 RunGit(['branch', '-D', options.newbranch], error_ok=True)
599 RunGit(['checkout', '-b', options.newbranch])
600
601 # Switch up to the top-level directory, if necessary, in preparation for
602 # applying the patch.
603 top = RunGit(['rev-parse', '--show-cdup']).strip()
604 if top:
605 os.chdir(top)
606
607 # Construct a pipeline to feed the patch into "git apply".
608 # We use "git apply" to apply the patch instead of "patch" so that we can
609 # pick up file adds.
610 # 1) Fetch the patch.
611 fetch = "curl --silent %s" % url
612 # 2) Munge the patch.
613 # Git patches have a/ at the beginning of source paths. We strip that out
614 # with a sed script rather than the -p flag to patch so we can feed either
615 # Git or svn-style patches into the same apply command.
616 gitsed = "sed -e 's|^--- a/|--- |; s|^+++ b/|+++ |'"
617 # 3) Apply the patch.
618 # The --index flag means: also insert into the index (so we catch adds).
619 apply = "git apply --index -p0"
620 if options.reject:
621 apply += " --reject"
622 subprocess.check_call(' | '.join([fetch, gitsed, apply]), shell=True)
623
624 # If we had an issue, commit the current state and register the issue.
625 if not options.nocommit:
626 RunGit(['commit', '-m', 'patch from issue %s' % issue])
627 cl = Changelist()
628 cl.SetIssue(issue)
629 print "Committed patch."
630 else:
631 print "Patch applied to index."
632
633 def CmdRebase(args):
634 # Provide a wrapper for git svn rebase to help avoid accidental
635 # git svn dcommit.
636 RunGit(['svn', 'rebase'], redirect_stdout=False)
637
638 def GetTreeStatus():
639 """Fetches the tree status and returns either 'open', 'closed',
640 'unknown' or 'unset'."""
641 url = settings.GetTreeStatusUrl(error_ok=True)
642 if url:
643 status = urllib2.urlopen(url).read().lower()
644 if status.find('closed') != -1:
645 return 'closed'
646 elif status.find('open') != -1:
647 return 'open'
648 return 'unknown'
649
650 return 'unset'
651
652 def CmdTreeStatus(args):
653 status = GetTreeStatus()
654 if 'unset' == status:
655 print 'You must configure your tree status URL by running "git cl config".'
656 else:
657 print "The tree is %s" % status
658
659 def CmdUpstream(args):
660 cl = Changelist()
661 print cl.GetUpstreamBranch()
662
663 COMMANDS = [
664 ('config', 'edit configuration for this tree', CmdConfig),
665 ('status', 'show status of changelists', CmdStatus),
666 ('issue', 'show/set current branch\'s issue number', CmdIssue),
667 ('upload', 'upload the current changelist to codereview', CmdUpload),
668 ('dcommit', 'commit the current changelist via git-svn', CmdDCommit),
669 ('patch', 'patch in a code review', CmdPatch),
670 ('rebase', 'rebase current branch on top of svn repo', CmdRebase),
671 ('tree', 'show the status of the tree', CmdTreeStatus),
672 ('upstream', 'print the name of the upstream branch, if any', CmdUpstream),
673 ]
674
675
676 def Usage(name):
677 print 'usage: %s <command>' % name
678 print 'commands are:'
679 for name, desc, _ in COMMANDS:
680 print ' %-10s %s' % (name, desc)
681 sys.exit(1)
682
683
684 def main(argv):
685 if len(argv) < 2:
686 Usage(argv[0])
687
688 command = argv[1]
689 for name, _, func in COMMANDS:
690 if name == command:
691 return func(argv[2:])
692 print 'unknown command: %s' % command
693 Usage(argv[0])
694
695
696 if __name__ == '__main__':
697 sys.exit(main(sys.argv))
OLDNEW
« no previous file with comments | « no previous file | git-try.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698