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

Side by Side Diff: depot_tools/git-cl.py

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

Powered by Google App Engine
This is Rietveld 408576698