OLD | NEW |
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 |
11 from multiprocessing.pool import ThreadPool | 11 from multiprocessing.pool import ThreadPool |
12 import base64 | 12 import base64 |
| 13 import collections |
13 import glob | 14 import glob |
14 import httplib | 15 import httplib |
15 import json | 16 import json |
16 import logging | 17 import logging |
17 import optparse | 18 import optparse |
18 import os | 19 import os |
19 import Queue | 20 import Queue |
20 import re | 21 import re |
21 import stat | 22 import stat |
22 import sys | 23 import sys |
(...skipping 1486 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1509 else len(branches_to_fetch)) | 1510 else len(branches_to_fetch)) |
1510 for x in pool.imap_unordered(fetch, branches_to_fetch): | 1511 for x in pool.imap_unordered(fetch, branches_to_fetch): |
1511 yield x | 1512 yield x |
1512 else: | 1513 else: |
1513 # Do not use GetApprovingReviewers(), since it requires an HTTP request. | 1514 # Do not use GetApprovingReviewers(), since it requires an HTTP request. |
1514 for b in branches: | 1515 for b in branches: |
1515 cl = Changelist(branchref=b, auth_config=auth_config) | 1516 cl = Changelist(branchref=b, auth_config=auth_config) |
1516 url = cl.GetIssueURL() | 1517 url = cl.GetIssueURL() |
1517 yield (b, url, 'waiting' if url else 'error') | 1518 yield (b, url, 'waiting' if url else 'error') |
1518 | 1519 |
| 1520 |
| 1521 def upload_branch_deps(cl, args): |
| 1522 """Uploads CLs of local branches that are dependents of the current branch. |
| 1523 |
| 1524 If the local branch dependency tree looks like: |
| 1525 test1 -> test2.1 -> test3.1 |
| 1526 -> test3.2 |
| 1527 -> test2.2 -> test3.3 |
| 1528 |
| 1529 and you run "git cl upload --dependencies" from test1 then "git cl upload" is |
| 1530 run on the dependent branches in this order: |
| 1531 test2.1, test3.1, test3.2, test2.2, test3.3 |
| 1532 |
| 1533 Note: This function does not rebase your local dependent branches. Use it when |
| 1534 you make a change to the parent branch that will not conflict with its |
| 1535 dependent branches, and you would like their dependencies updated in |
| 1536 Rietveld. |
| 1537 """ |
| 1538 if git_common.is_dirty_git_tree('upload-branch-deps'): |
| 1539 return 1 |
| 1540 |
| 1541 root_branch = cl.GetBranch() |
| 1542 if root_branch is None: |
| 1543 DieWithError('Can\'t find dependent branches from detached HEAD state. ' |
| 1544 'Get on a branch!') |
| 1545 if not cl.GetIssue() or not cl.GetPatchset(): |
| 1546 DieWithError('Current branch does not have an uploaded CL. We cannot set ' |
| 1547 'patchset dependencies without an uploaded CL.') |
| 1548 |
| 1549 branches = RunGit(['for-each-ref', |
| 1550 '--format=%(refname:short) %(upstream:short)', |
| 1551 'refs/heads']) |
| 1552 if not branches: |
| 1553 print('No local branches found.') |
| 1554 return 0 |
| 1555 |
| 1556 # Create a dictionary of all local branches to the branches that are dependent |
| 1557 # on it. |
| 1558 tracked_to_dependents = collections.defaultdict(list) |
| 1559 for b in branches.splitlines(): |
| 1560 tokens = b.split() |
| 1561 if len(tokens) == 2: |
| 1562 branch_name, tracked = tokens |
| 1563 tracked_to_dependents[tracked].append(branch_name) |
| 1564 |
| 1565 print |
| 1566 print 'The dependent local branches of %s are:' % root_branch |
| 1567 dependents = [] |
| 1568 def traverse_dependents_preorder(branch, padding=''): |
| 1569 dependents_to_process = tracked_to_dependents.get(branch, []) |
| 1570 padding += ' ' |
| 1571 for dependent in dependents_to_process: |
| 1572 print '%s%s' % (padding, dependent) |
| 1573 dependents.append(dependent) |
| 1574 traverse_dependents_preorder(dependent, padding) |
| 1575 traverse_dependents_preorder(root_branch) |
| 1576 print |
| 1577 |
| 1578 if not dependents: |
| 1579 print 'There are no dependent local branches for %s' % root_branch |
| 1580 return 0 |
| 1581 |
| 1582 print ('This command will checkout all dependent branches and run ' |
| 1583 '"git cl upload".') |
| 1584 ask_for_data('[Press enter to continue or ctrl-C to quit]') |
| 1585 |
| 1586 # Add a default patchset title to all upload calls. |
| 1587 args.extend(['-t', 'Updated patchset dependency']) |
| 1588 # Record all dependents that failed to upload. |
| 1589 failures = {} |
| 1590 # Go through all dependents, checkout the branch and upload. |
| 1591 try: |
| 1592 for dependent_branch in dependents: |
| 1593 print |
| 1594 print '--------------------------------------' |
| 1595 print 'Running "git cl upload" from %s:' % dependent_branch |
| 1596 RunGit(['checkout', '-q', dependent_branch]) |
| 1597 print |
| 1598 try: |
| 1599 if CMDupload(OptionParser(), args) != 0: |
| 1600 print 'Upload failed for %s!' % dependent_branch |
| 1601 failures[dependent_branch] = 1 |
| 1602 except: # pylint: disable=W0702 |
| 1603 failures[dependent_branch] = 1 |
| 1604 print |
| 1605 finally: |
| 1606 # Swap back to the original root branch. |
| 1607 RunGit(['checkout', '-q', root_branch]) |
| 1608 |
| 1609 print |
| 1610 print 'Upload complete for dependent branches!' |
| 1611 for dependent_branch in dependents: |
| 1612 upload_status = 'failed' if failures.get(dependent_branch) else 'succeeded' |
| 1613 print ' %s : %s' % (dependent_branch, upload_status) |
| 1614 print |
| 1615 |
| 1616 return 0 |
| 1617 |
| 1618 |
1519 def CMDstatus(parser, args): | 1619 def CMDstatus(parser, args): |
1520 """Show status of changelists. | 1620 """Show status of changelists. |
1521 | 1621 |
1522 Colors are used to tell the state of the CL unless --fast is used: | 1622 Colors are used to tell the state of the CL unless --fast is used: |
1523 - Red not sent for review or broken | 1623 - Red not sent for review or broken |
1524 - Blue waiting for review | 1624 - Blue waiting for review |
1525 - Yellow waiting for you to reply to review | 1625 - Yellow waiting for you to reply to review |
1526 - Green LGTM'ed | 1626 - Green LGTM'ed |
1527 - Magenta in the commit queue | 1627 - Magenta in the commit queue |
1528 - Cyan was committed, branch can be deleted | 1628 - Cyan was committed, branch can be deleted |
(...skipping 661 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2190 'Default: remote branch head, or master') | 2290 'Default: remote branch head, or master') |
2191 parser.add_option('--squash', action='store_true', | 2291 parser.add_option('--squash', action='store_true', |
2192 help='Squash multiple commits into one (Gerrit only)') | 2292 help='Squash multiple commits into one (Gerrit only)') |
2193 parser.add_option('--email', default=None, | 2293 parser.add_option('--email', default=None, |
2194 help='email address to use to connect to Rietveld') | 2294 help='email address to use to connect to Rietveld') |
2195 parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true', | 2295 parser.add_option('--tbr-owners', dest='tbr_owners', action='store_true', |
2196 help='add a set of OWNERS to TBR') | 2296 help='add a set of OWNERS to TBR') |
2197 parser.add_option('--cq-dry-run', dest='cq_dry_run', action='store_true', | 2297 parser.add_option('--cq-dry-run', dest='cq_dry_run', action='store_true', |
2198 help='Send the patchset to do a CQ dry run right after ' | 2298 help='Send the patchset to do a CQ dry run right after ' |
2199 'upload.') | 2299 'upload.') |
| 2300 parser.add_option('--dependencies', action='store_true', |
| 2301 help='Uploads CLs of all the local branches that depend on ' |
| 2302 'the current branch') |
2200 | 2303 |
| 2304 orig_args = args |
2201 add_git_similarity(parser) | 2305 add_git_similarity(parser) |
2202 auth.add_auth_options(parser) | 2306 auth.add_auth_options(parser) |
2203 (options, args) = parser.parse_args(args) | 2307 (options, args) = parser.parse_args(args) |
2204 auth_config = auth.extract_auth_config_from_options(options) | 2308 auth_config = auth.extract_auth_config_from_options(options) |
2205 | 2309 |
2206 if git_common.is_dirty_git_tree('upload'): | 2310 if git_common.is_dirty_git_tree('upload'): |
2207 return 1 | 2311 return 1 |
2208 | 2312 |
2209 options.reviewers = cleanup_list(options.reviewers) | 2313 options.reviewers = cleanup_list(options.reviewers) |
2210 options.cc = cleanup_list(options.cc) | 2314 options.cc = cleanup_list(options.cc) |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2275 RunGit(['rev-parse', 'HEAD']).strip()) | 2379 RunGit(['rev-parse', 'HEAD']).strip()) |
2276 # Run post upload hooks, if specified. | 2380 # Run post upload hooks, if specified. |
2277 if settings.GetRunPostUploadHook(): | 2381 if settings.GetRunPostUploadHook(): |
2278 presubmit_support.DoPostUploadExecuter( | 2382 presubmit_support.DoPostUploadExecuter( |
2279 change, | 2383 change, |
2280 cl, | 2384 cl, |
2281 settings.GetRoot(), | 2385 settings.GetRoot(), |
2282 options.verbose, | 2386 options.verbose, |
2283 sys.stdout) | 2387 sys.stdout) |
2284 | 2388 |
| 2389 # Upload all dependencies if specified. |
| 2390 if options.dependencies: |
| 2391 print |
| 2392 print '--dependencies has been specified.' |
| 2393 print 'All dependent local branches will be re-uploaded.' |
| 2394 print |
| 2395 # Remove the dependencies flag from args so that we do not end up in a |
| 2396 # loop. |
| 2397 orig_args.remove('--dependencies') |
| 2398 upload_branch_deps(cl, orig_args) |
2285 return ret | 2399 return ret |
2286 | 2400 |
2287 | 2401 |
2288 def IsSubmoduleMergeCommit(ref): | 2402 def IsSubmoduleMergeCommit(ref): |
2289 # When submodules are added to the repo, we expect there to be a single | 2403 # When submodules are added to the repo, we expect there to be a single |
2290 # non-git-svn merge commit at remote HEAD with a signature comment. | 2404 # non-git-svn merge commit at remote HEAD with a signature comment. |
2291 pattern = '^SVN changes up to revision [0-9]*$' | 2405 pattern = '^SVN changes up to revision [0-9]*$' |
2292 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] | 2406 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] |
2293 return RunGit(cmd) != '' | 2407 return RunGit(cmd) != '' |
2294 | 2408 |
(...skipping 1121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3416 if __name__ == '__main__': | 3530 if __name__ == '__main__': |
3417 # These affect sys.stdout so do it outside of main() to simplify mocks in | 3531 # These affect sys.stdout so do it outside of main() to simplify mocks in |
3418 # unit testing. | 3532 # unit testing. |
3419 fix_encoding.fix_encoding() | 3533 fix_encoding.fix_encoding() |
3420 colorama.init() | 3534 colorama.init() |
3421 try: | 3535 try: |
3422 sys.exit(main(sys.argv[1:])) | 3536 sys.exit(main(sys.argv[1:])) |
3423 except KeyboardInterrupt: | 3537 except KeyboardInterrupt: |
3424 sys.stderr.write('interrupted\n') | 3538 sys.stderr.write('interrupted\n') |
3425 sys.exit(1) | 3539 sys.exit(1) |
OLD | NEW |