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

Side by Side Diff: git_cl.py

Issue 519563002: Make git-cl-land wait (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: fixins Created 6 years, 3 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 | no next file » | 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) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 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 from distutils.version import LooseVersion 10 from distutils.version import LooseVersion
(...skipping 1875 matching lines...) Expand 10 before | Expand all | Expand 10 after
1886 base_svn_head += '^1' 1886 base_svn_head += '^1'
1887 1887
1888 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head]) 1888 extra_commits = RunGit(['rev-list', '^' + svn_head, base_svn_head])
1889 if extra_commits: 1889 if extra_commits:
1890 print ('This branch has %d additional commits not upstreamed yet.' 1890 print ('This branch has %d additional commits not upstreamed yet.'
1891 % len(extra_commits.splitlines())) 1891 % len(extra_commits.splitlines()))
1892 print ('Upstream "%s" or rebase this branch on top of the upstream trunk ' 1892 print ('Upstream "%s" or rebase this branch on top of the upstream trunk '
1893 'before attempting to %s.' % (base_branch, cmd)) 1893 'before attempting to %s.' % (base_branch, cmd))
1894 return 1 1894 return 1
1895 1895
1896 base_branch = RunGit(['merge-base', base_branch, 'HEAD']).strip() 1896 merge_base = RunGit(['merge-base', base_branch, 'HEAD']).strip()
1897 if not options.bypass_hooks: 1897 if not options.bypass_hooks:
1898 author = None 1898 author = None
1899 if options.contributor: 1899 if options.contributor:
1900 author = re.search(r'\<(.*)\>', options.contributor).group(1) 1900 author = re.search(r'\<(.*)\>', options.contributor).group(1)
1901 hook_results = cl.RunHook( 1901 hook_results = cl.RunHook(
1902 committing=True, 1902 committing=True,
1903 may_prompt=not options.force, 1903 may_prompt=not options.force,
1904 verbose=options.verbose, 1904 verbose=options.verbose,
1905 change=cl.GetChange(base_branch, author)) 1905 change=cl.GetChange(merge_base, author))
1906 if not hook_results.should_continue(): 1906 if not hook_results.should_continue():
1907 return 1 1907 return 1
1908 1908
1909 # Check the tree status if the tree status URL is set. 1909 # Check the tree status if the tree status URL is set.
1910 status = GetTreeStatus() 1910 status = GetTreeStatus()
1911 if 'closed' == status: 1911 if 'closed' == status:
1912 print('The tree is closed. Please wait for it to reopen. Use ' 1912 print('The tree is closed. Please wait for it to reopen. Use '
1913 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd) 1913 '"git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
1914 return 1 1914 return 1
1915 elif 'unknown' == status: 1915 elif 'unknown' == status:
1916 print('Unable to determine tree status. Please verify manually and ' 1916 print('Unable to determine tree status. Please verify manually and '
1917 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd) 1917 'use "git cl %s --bypass-hooks" to commit on a closed tree.' % cmd)
1918 return 1 1918 return 1
1919 else: 1919 else:
1920 breakpad.SendStack( 1920 breakpad.SendStack(
1921 'GitClHooksBypassedCommit', 1921 'GitClHooksBypassedCommit',
1922 'Issue %s/%s bypassed hook when committing (tree status was "%s")' % 1922 'Issue %s/%s bypassed hook when committing (tree status was "%s")' %
1923 (cl.GetRietveldServer(), cl.GetIssue(), GetTreeStatus()), 1923 (cl.GetRietveldServer(), cl.GetIssue(), GetTreeStatus()),
1924 verbose=False) 1924 verbose=False)
1925 1925
1926 change_desc = ChangeDescription(options.message) 1926 change_desc = ChangeDescription(options.message)
1927 if not change_desc.description and cl.GetIssue(): 1927 if not change_desc.description and cl.GetIssue():
1928 change_desc = ChangeDescription(cl.GetDescription()) 1928 change_desc = ChangeDescription(cl.GetDescription())
1929 1929
1930 if not change_desc.description: 1930 if not change_desc.description:
1931 if not cl.GetIssue() and options.bypass_hooks: 1931 if not cl.GetIssue() and options.bypass_hooks:
1932 change_desc = ChangeDescription(CreateDescriptionFromLog([base_branch])) 1932 change_desc = ChangeDescription(CreateDescriptionFromLog([merge_base]))
1933 else: 1933 else:
1934 print 'No description set.' 1934 print 'No description set.'
1935 print 'Visit %s/edit to set it.' % (cl.GetIssueURL()) 1935 print 'Visit %s/edit to set it.' % (cl.GetIssueURL())
1936 return 1 1936 return 1
1937 1937
1938 # Keep a separate copy for the commit message, because the commit message 1938 # Keep a separate copy for the commit message, because the commit message
1939 # contains the link to the Rietveld issue, while the Rietveld message contains 1939 # contains the link to the Rietveld issue, while the Rietveld message contains
1940 # the commit viewvc url. 1940 # the commit viewvc url.
1941 # Keep a separate copy for the commit message. 1941 # Keep a separate copy for the commit message.
1942 if cl.GetIssue(): 1942 if cl.GetIssue():
1943 change_desc.update_reviewers(cl.GetApprovingReviewers()) 1943 change_desc.update_reviewers(cl.GetApprovingReviewers())
1944 1944
1945 commit_desc = ChangeDescription(change_desc.description) 1945 commit_desc = ChangeDescription(change_desc.description)
1946 if cl.GetIssue(): 1946 if cl.GetIssue():
1947 commit_desc.append_footer('Review URL: %s' % cl.GetIssueURL()) 1947 commit_desc.append_footer('Review URL: %s' % cl.GetIssueURL())
1948 if options.contributor: 1948 if options.contributor:
1949 commit_desc.append_footer('Patch from %s.' % options.contributor) 1949 commit_desc.append_footer('Patch from %s.' % options.contributor)
1950 1950
1951 print('Description:') 1951 print('Description:')
1952 print(commit_desc.description) 1952 print(commit_desc.description)
1953 1953
1954 branches = [base_branch, cl.GetBranchRef()] 1954 branches = [merge_base, cl.GetBranchRef()]
1955 if not options.force: 1955 if not options.force:
1956 print_stats(options.similarity, options.find_copies, branches) 1956 print_stats(options.similarity, options.find_copies, branches)
1957 1957
1958 # We want to squash all this branch's commits into one commit with the proper 1958 # We want to squash all this branch's commits into one commit with the proper
1959 # description. We do this by doing a "reset --soft" to the base branch (which 1959 # description. We do this by doing a "reset --soft" to the base branch (which
1960 # keeps the working copy the same), then dcommitting that. If origin/master 1960 # keeps the working copy the same), then dcommitting that. If origin/master
1961 # has a submodule merge commit, we'll also need to cherry-pick the squashed 1961 # has a submodule merge commit, we'll also need to cherry-pick the squashed
1962 # commit onto a branch based on the git-svn head. 1962 # commit onto a branch based on the git-svn head.
1963 MERGE_BRANCH = 'git-cl-commit' 1963 MERGE_BRANCH = 'git-cl-commit'
1964 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick' 1964 CHERRY_PICK_BRANCH = 'git-cl-cherry-pick'
1965 # Delete the branches if they exist. 1965 # Delete the branches if they exist.
1966 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]: 1966 for branch in [MERGE_BRANCH, CHERRY_PICK_BRANCH]:
1967 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch] 1967 showref_cmd = ['show-ref', '--quiet', '--verify', 'refs/heads/%s' % branch]
1968 result = RunGitWithCode(showref_cmd) 1968 result = RunGitWithCode(showref_cmd)
1969 if result[0] == 0: 1969 if result[0] == 0:
1970 RunGit(['branch', '-D', branch]) 1970 RunGit(['branch', '-D', branch])
1971 1971
1972 # We might be in a directory that's present in this branch but not in the 1972 # We might be in a directory that's present in this branch but not in the
1973 # trunk. Move up to the top of the tree so that git commands that expect a 1973 # trunk. Move up to the top of the tree so that git commands that expect a
1974 # valid CWD won't fail after we check out the merge branch. 1974 # valid CWD won't fail after we check out the merge branch.
1975 rel_base_path = settings.GetRelativeRoot() 1975 rel_base_path = settings.GetRelativeRoot()
1976 if rel_base_path: 1976 if rel_base_path:
1977 os.chdir(rel_base_path) 1977 os.chdir(rel_base_path)
1978 1978
1979 # Stuff our change into the merge branch. 1979 # Stuff our change into the merge branch.
1980 # We wrap in a try...finally block so if anything goes wrong, 1980 # We wrap in a try...finally block so if anything goes wrong,
1981 # we clean up the branches. 1981 # we clean up the branches.
1982 retcode = -1 1982 retcode = -1
1983 used_pending = False 1983 pushed_to_pending = False
1984 pending_ref = None 1984 pending_ref = None
1985 revision = None
1985 try: 1986 try:
1986 RunGit(['checkout', '-q', '-b', MERGE_BRANCH]) 1987 RunGit(['checkout', '-q', '-b', MERGE_BRANCH])
1987 RunGit(['reset', '--soft', base_branch]) 1988 RunGit(['reset', '--soft', merge_base])
1988 if options.contributor: 1989 if options.contributor:
1989 RunGit( 1990 RunGit(
1990 [ 1991 [
1991 'commit', '--author', options.contributor, 1992 'commit', '--author', options.contributor,
1992 '-m', commit_desc.description, 1993 '-m', commit_desc.description,
1993 ]) 1994 ])
1994 else: 1995 else:
1995 RunGit(['commit', '-m', commit_desc.description]) 1996 RunGit(['commit', '-m', commit_desc.description])
1996 if base_has_submodules: 1997 if base_has_submodules:
1997 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip() 1998 cherry_pick_commit = RunGit(['rev-list', 'HEAD^!']).rstrip()
1998 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head]) 1999 RunGit(['branch', CHERRY_PICK_BRANCH, svn_head])
1999 RunGit(['checkout', CHERRY_PICK_BRANCH]) 2000 RunGit(['checkout', CHERRY_PICK_BRANCH])
2000 RunGit(['cherry-pick', cherry_pick_commit]) 2001 RunGit(['cherry-pick', cherry_pick_commit])
2001 if cmd == 'land': 2002 if cmd == 'land':
2002 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch()) 2003 remote, branch = cl.FetchUpstreamTuple(cl.GetBranch())
2003 pending_prefix = settings.GetPendingRefPrefix() 2004 pending_prefix = settings.GetPendingRefPrefix()
2004 if not pending_prefix or branch.startswith(pending_prefix): 2005 if not pending_prefix or branch.startswith(pending_prefix):
2005 # If not using refs/pending/heads/* at all, or target ref is already set 2006 # If not using refs/pending/heads/* at all, or target ref is already set
2006 # to pending, then push to the target ref directly. 2007 # to pending, then push to the target ref directly.
2007 retcode, output = RunGitWithCode( 2008 retcode, output = RunGitWithCode(
2008 ['push', '--porcelain', remote, 'HEAD:%s' % branch]) 2009 ['push', '--porcelain', remote, 'HEAD:%s' % branch])
2009 used_pending = pending_prefix and branch.startswith(pending_prefix) 2010 pushed_to_pending = pending_prefix and branch.startswith(pending_prefix)
2010 else: 2011 else:
2011 # Cherry-pick the change on top of pending ref and then push it. 2012 # Cherry-pick the change on top of pending ref and then push it.
2012 assert branch.startswith('refs/'), branch 2013 assert branch.startswith('refs/'), branch
2013 assert pending_prefix[-1] == '/', pending_prefix 2014 assert pending_prefix[-1] == '/', pending_prefix
2014 pending_ref = pending_prefix + branch[len('refs/'):] 2015 pending_ref = pending_prefix + branch[len('refs/'):]
2015 retcode, output = PushToGitPending(remote, pending_ref, branch) 2016 retcode, output = PushToGitPending(remote, pending_ref, branch)
2016 used_pending = (retcode == 0) 2017 revision = RunGit(['rev-parse', 'HEAD']).strip()
2018 pushed_to_pending = (retcode == 0)
2017 logging.debug(output) 2019 logging.debug(output)
2018 else: 2020 else:
2019 # dcommit the merge branch. 2021 # dcommit the merge branch.
2020 retcode, output = RunGitWithCode(['svn', 'dcommit', 2022 retcode, output = RunGitWithCode(['svn', 'dcommit',
2021 '-C%s' % options.similarity, 2023 '-C%s' % options.similarity,
2022 '--no-rebase', '--rmdir']) 2024 '--no-rebase', '--rmdir'])
2023 finally: 2025 finally:
2024 # And then swap back to the original branch and clean up. 2026 # And then swap back to the original branch and clean up.
2025 RunGit(['checkout', '-q', cl.GetBranch()]) 2027 RunGit(['checkout', '-q', cl.GetBranch()])
2026 RunGit(['branch', '-D', MERGE_BRANCH]) 2028 RunGit(['branch', '-D', MERGE_BRANCH])
2027 if base_has_submodules: 2029 if base_has_submodules:
2028 RunGit(['branch', '-D', CHERRY_PICK_BRANCH]) 2030 RunGit(['branch', '-D', CHERRY_PICK_BRANCH])
2029 2031
2032 if retcode == 0 and pushed_to_pending:
2033 try:
2034 revision = WaitForRealCommit(remote, revision, base_branch, branch)
2035 # We set pushed_to_pending to False, since it made it all the way to the
2036 # real ref.
2037 pushed_to_pending = False
2038 except KeyboardInterrupt:
2039 pass
2040
2030 if cl.GetIssue(): 2041 if cl.GetIssue():
2031 if cmd == 'dcommit' and 'Committed r' in output: 2042 if not revision:
2032 revision = re.match('.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1) 2043 if cmd == 'dcommit' and 'Committed r' in output:
2033 elif cmd == 'land' and retcode == 0: 2044 revision = re.match(
2034 match = (re.match(r'.*?([a-f0-9]{7,})\.\.([a-f0-9]{7,})$', l) 2045 '.*?\nCommitted r(\\d+)', output, re.DOTALL).group(1)
2035 for l in output.splitlines(False)) 2046 elif cmd == 'land' and retcode == 0:
2036 match = filter(None, match) 2047 match = (re.match(r'.*?([a-f0-9]{7,})\.\.([a-f0-9]{7,})$', l)
2037 if len(match) != 1: 2048 for l in output.splitlines(False))
2038 DieWithError("Couldn't parse ouput to extract the committed hash:\n%s" % 2049 match = filter(None, match)
2039 output) 2050 if len(match) != 1:
2040 revision = match[0].group(2) 2051 DieWithError(
2041 else: 2052 "Couldn't parse ouput to extract the committed hash:\n%s" % output)
2042 return 1 2053 revision = match[0].group(2)
2043 to_pending = ' to pending queue' if used_pending else '' 2054 else:
2055 return 1
2056
2057 revision = revision[:7]
2058
2059 to_pending = ' to pending queue' if pushed_to_pending else ''
2044 viewvc_url = settings.GetViewVCUrl() 2060 viewvc_url = settings.GetViewVCUrl()
2045 if viewvc_url and revision: 2061 if not to_pending:
2046 change_desc.append_footer( 2062 if viewvc_url and revision:
2047 'Committed%s: %s%s' % (to_pending, viewvc_url, revision)) 2063 change_desc.append_footer(
2048 elif revision: 2064 'Committed: %s%s' % (viewvc_url, revision))
2049 change_desc.append_footer('Committed%s: %s' % (to_pending, revision)) 2065 elif revision:
2066 change_desc.append_footer('Committed: %s' % (revision,))
2050 print ('Closing issue ' 2067 print ('Closing issue '
2051 '(you may be prompted for your codereview password)...') 2068 '(you may be prompted for your codereview password)...')
2052 cl.UpdateDescription(change_desc.description) 2069 cl.UpdateDescription(change_desc.description)
2053 cl.CloseIssue() 2070 cl.CloseIssue()
2054 props = cl.GetIssueProperties() 2071 props = cl.GetIssueProperties()
2055 patch_num = len(props['patchsets']) 2072 patch_num = len(props['patchsets'])
2056 comment = "Committed patchset #%d (id:%d)%s manually as %s" % ( 2073 comment = "Committed patchset #%d (id:%d)%s manually as %s" % (
2057 patch_num, props['patchsets'][-1], to_pending, revision) 2074 patch_num, props['patchsets'][-1], to_pending, revision)
2058 if options.bypass_hooks: 2075 if options.bypass_hooks:
2059 comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.' 2076 comment += ' (tree was closed).' if GetTreeStatus() == 'closed' else '.'
2060 else: 2077 else:
2061 comment += ' (presubmit successful).' 2078 comment += ' (presubmit successful).'
2062 cl.RpcServer().add_comment(cl.GetIssue(), comment) 2079 cl.RpcServer().add_comment(cl.GetIssue(), comment)
2063 cl.SetIssue(None) 2080 cl.SetIssue(None)
2064 2081
2065 if used_pending and retcode == 0: 2082 if pushed_to_pending and retcode == 0:
2066 _, branch = cl.FetchUpstreamTuple(cl.GetBranch()) 2083 _, branch = cl.FetchUpstreamTuple(cl.GetBranch())
2067 print 'The commit is in the pending queue (%s).' % pending_ref 2084 print 'The commit is in the pending queue (%s).' % pending_ref
2068 print ( 2085 print (
2069 'It will show up on %s in ~1 min, once it gets Cr-Commit-Position ' 2086 'It will show up on %s in ~1 min, once it gets Cr-Commit-Position '
2070 'footer.' % branch) 2087 'footer.' % branch)
2071 2088
2072 if retcode == 0: 2089 if retcode == 0:
2073 hook = POSTUPSTREAM_HOOK_PATTERN % cmd 2090 hook = POSTUPSTREAM_HOOK_PATTERN % cmd
2074 if os.path.isfile(hook): 2091 if os.path.isfile(hook):
2075 RunCommand([hook, base_branch], error_ok=True) 2092 RunCommand([hook, merge_base], error_ok=True)
2076 2093
2077 return 0 2094 return 0
2078 2095
2079 2096
2097 def WaitForRealCommit(remote, pushed_commit, local_base_ref, real_ref):
2098 print
2099 print 'Waiting for commit to be landed on %s...' % real_ref
2100 print '(If you are impatient, you may Ctrl-C once without harm)'
2101 target_tree = RunGit(['rev-parse', '%s:' % pushed_commit]).strip()
2102 current_rev = RunGit(['rev-parse', local_base_ref]).strip()
2103
2104 loop = 0
2105 while True:
2106 sys.stdout.write('fetching (%d)... \r' % loop)
2107 sys.stdout.flush()
2108 loop += 1
2109
2110 RunGit(['retry', 'fetch', remote, real_ref], stderr=subprocess2.VOID)
2111 to_rev = RunGit(['rev-parse', 'FETCH_HEAD']).strip()
2112 commits = RunGit(['rev-list', '%s..%s' % (current_rev, to_rev)])
2113 for commit in commits.splitlines():
2114 if RunGit(['rev-parse', '%s:' % commit]).strip() == target_tree:
2115 print 'Found commit on %s' % real_ref
2116 return commit
2117
2118 current_rev = to_rev
2119
2120
2080 def PushToGitPending(remote, pending_ref, upstream_ref): 2121 def PushToGitPending(remote, pending_ref, upstream_ref):
2081 """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes. 2122 """Fetches pending_ref, cherry-picks current HEAD on top of it, pushes.
2082 2123
2083 Returns: 2124 Returns:
2084 (retcode of last operation, output log of last operation). 2125 (retcode of last operation, output log of last operation).
2085 """ 2126 """
2086 assert pending_ref.startswith('refs/'), pending_ref 2127 assert pending_ref.startswith('refs/'), pending_ref
2087 local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):] 2128 local_pending_ref = 'refs/git-cl/' + pending_ref[len('refs/'):]
2088 cherry = RunGit(['rev-parse', 'HEAD']).strip() 2129 cherry = RunGit(['rev-parse', 'HEAD']).strip()
2089 code = 0 2130 code = 0
(...skipping 27 matching lines...) Expand all
2117 print 'Please rebase your patch and try again.' 2158 print 'Please rebase your patch and try again.'
2118 RunGitWithCode(['cherry-pick', '--abort']) 2159 RunGitWithCode(['cherry-pick', '--abort'])
2119 return code, out 2160 return code, out
2120 2161
2121 # Applied cleanly, try to push now. Retry on error (flake or non-ff push). 2162 # Applied cleanly, try to push now. Retry on error (flake or non-ff push).
2122 print 'Pushing commit to %s... It can take a while.' % pending_ref 2163 print 'Pushing commit to %s... It can take a while.' % pending_ref
2123 code, out = RunGitWithCode( 2164 code, out = RunGitWithCode(
2124 ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref]) 2165 ['retry', 'push', '--porcelain', remote, 'HEAD:%s' % pending_ref])
2125 if code == 0: 2166 if code == 0:
2126 # Success. 2167 # Success.
2168 print 'Commit pushed to pending ref successfully!'
2127 return code, out 2169 return code, out
2128 2170
2129 print 'Push failed with exit code %d.' % code 2171 print 'Push failed with exit code %d.' % code
2130 if out.strip(): 2172 if out.strip():
2131 print out.strip() 2173 print out.strip()
2132 if IsFatalPushFailure(out): 2174 if IsFatalPushFailure(out):
2133 print ( 2175 print (
2134 'Fatal push error. Make sure your .netrc credentials and git ' 2176 'Fatal push error. Make sure your .netrc credentials and git '
2135 'user.email are correct and you have push access to the repo.') 2177 'user.email are correct and you have push access to the repo.')
2136 return code, out 2178 return code, out
(...skipping 628 matching lines...) Expand 10 before | Expand all | Expand 10 after
2765 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' 2807 ('AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
2766 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))) 2808 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)))
2767 2809
2768 2810
2769 if __name__ == '__main__': 2811 if __name__ == '__main__':
2770 # These affect sys.stdout so do it outside of main() to simplify mocks in 2812 # These affect sys.stdout so do it outside of main() to simplify mocks in
2771 # unit testing. 2813 # unit testing.
2772 fix_encoding.fix_encoding() 2814 fix_encoding.fix_encoding()
2773 colorama.init() 2815 colorama.init()
2774 sys.exit(main(sys.argv[1:])) 2816 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698