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 and Gerrit.""" | 8 """A git-command for integrating reviews on Rietveld and Gerrit.""" |
9 | 9 |
10 from distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
(...skipping 2500 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2511 # whitespace. This code is not doing this, but it clearly won't decrease | 2511 # whitespace. This code is not doing this, but it clearly won't decrease |
2512 # entropy. | 2512 # entropy. |
2513 lines.append(message) | 2513 lines.append(message) |
2514 change_hash = RunCommand(['git', 'hash-object', '-t', 'commit', '--stdin'], | 2514 change_hash = RunCommand(['git', 'hash-object', '-t', 'commit', '--stdin'], |
2515 stdin='\n'.join(lines)) | 2515 stdin='\n'.join(lines)) |
2516 return 'I%s' % change_hash.strip() | 2516 return 'I%s' % change_hash.strip() |
2517 | 2517 |
2518 | 2518 |
2519 def GerritUpload(options, args, cl, change): | 2519 def GerritUpload(options, args, cl, change): |
2520 """upload the current branch to gerrit.""" | 2520 """upload the current branch to gerrit.""" |
| 2521 # TODO(tandrii): refactor this to be a method of _GerritChangelistImpl, |
| 2522 # to avoid private members accessors below. |
| 2523 |
2521 # We assume the remote called "origin" is the one we want. | 2524 # We assume the remote called "origin" is the one we want. |
2522 # It is probably not worthwhile to support different workflows. | 2525 # It is probably not worthwhile to support different workflows. |
2523 gerrit_remote = 'origin' | 2526 gerrit_remote = 'origin' |
2524 | 2527 |
2525 remote, remote_branch = cl.GetRemoteBranch() | 2528 remote, remote_branch = cl.GetRemoteBranch() |
2526 branch = GetTargetRef(remote, remote_branch, options.target_branch, | 2529 branch = GetTargetRef(remote, remote_branch, options.target_branch, |
2527 pending_prefix='') | 2530 pending_prefix='') |
2528 | 2531 |
2529 change_desc = ChangeDescription( | |
2530 options.message or CreateDescriptionFromLog(args)) | |
2531 if not change_desc.description: | |
2532 print "\nDescription is empty. Aborting..." | |
2533 return 1 | |
2534 | |
2535 if options.title: | 2532 if options.title: |
2536 print "\nPatch titles (-t) are not supported in Gerrit. Aborting..." | 2533 print "\nPatch titles (-t) are not supported in Gerrit. Aborting..." |
2537 return 1 | 2534 return 1 |
2538 | 2535 |
2539 if options.squash: | 2536 if options.squash: |
2540 # Try to get the message from a previous upload. | 2537 if not cl.GetIssue(): |
2541 shadow_branch = 'refs/heads/git_cl_uploads/' + cl.GetBranch() | 2538 # TODO(tandrii): deperecate this after 2016Q2. |
2542 message = RunGitSilent(['show', '--format=%B', '-s', shadow_branch]) | 2539 # Backwards compatibility with shadow branch, which used to contain |
2543 if not message: | 2540 # change-id for a given branch, using which we can fetch actual issue |
| 2541 # number and set it as the property of the branch, which is the new way. |
| 2542 message = RunGitSilent(['show', '--format=%B', '-s', |
| 2543 'refs/heads/git_cl_uploads/%s' % cl.GetBranch()]) |
| 2544 if message: |
| 2545 change_ids = git_footers.get_footer_change_id(message.strip()) |
| 2546 if change_ids and len(change_ids) == 1: |
| 2547 details = gerrit_util.GetChangeDetail( |
| 2548 cl._codereview_impl._GetGerritHost(), change_ids[0]) |
| 2549 if details: |
| 2550 print('WARNING: found old upload in branch git_cl_uploads/%s ' |
| 2551 'corresponding to issue %s' % |
| 2552 (cl.GetBranch(), details['_number'])) |
| 2553 cl.SetIssue(details['_number']) |
| 2554 if not cl.GetIssue(): |
| 2555 DieWithError( |
| 2556 '\n' # For readability of the blob below. |
| 2557 'Found old upload in branch git_cl_uploads/%s, ' |
| 2558 'but failed to find corresponding Gerrit issue.\n' |
| 2559 'If you know the issue number, set it manually first:\n' |
| 2560 ' git cl issue 123456\n' |
| 2561 'If you intended to upload this CL as new issue, ' |
| 2562 'just delete or rename the old upload branch:\n' |
| 2563 ' git rename-branch git_cl_uploads/%s old_upload-%s\n' |
| 2564 'After that, please run git cl upload again.' % |
| 2565 tuple([cl.GetBranch()] * 3)) |
| 2566 # End of backwards compatability. |
| 2567 |
| 2568 if cl.GetIssue(): |
| 2569 # Try to get the message from a previous upload. |
| 2570 message = cl.GetDescription() |
| 2571 if not message: |
| 2572 DieWithError( |
| 2573 'failed to fetch description from current Gerrit issue %d\n' |
| 2574 '%s' % (cl.GetIssue(), cl.GetIssueURL())) |
| 2575 change_id = cl._codereview_impl._GetChangeDetail([])['change_id'] |
| 2576 while True: |
| 2577 footer_change_ids = git_footers.get_footer_change_id(message) |
| 2578 if footer_change_ids == [change_id]: |
| 2579 break |
| 2580 if not footer_change_ids: |
| 2581 message = git_footers.add_footer_change_id(message, change_id) |
| 2582 print('WARNING: appended missing Change-Id to issue description') |
| 2583 continue |
| 2584 # There is already a valid footer but with different or several ids. |
| 2585 # Doing this automatically is non-trivial as we don't want to lose |
| 2586 # existing other footers, yet we want to append just 1 desired |
| 2587 # Change-Id. Thus, just create a new footer, but let user verify the new |
| 2588 # description. |
| 2589 message = '%s\n\nChange-Id: %s' % (message, change_id) |
| 2590 print( |
| 2591 'WARNING: issue %s has Change-Id footer(s):\n' |
| 2592 ' %s\n' |
| 2593 'but issue has Change-Id %s, according to Gerrit.\n' |
| 2594 'Please, check the proposed correction to the description, ' |
| 2595 'and edit it if necessary but keep the "Change-Id: %s" footer\n' |
| 2596 % (cl.GetIssue(), '\n '.join(footer_change_ids), change_id, |
| 2597 change_id)) |
| 2598 ask_for_data('Press enter to edit now, Ctrl+C to abort') |
| 2599 if not options.force: |
| 2600 change_desc = ChangeDescription(message) |
| 2601 change_desc.prompt() |
| 2602 message = change_desc.description |
| 2603 if not message: |
| 2604 DieWithError("Description is empty. Aborting...") |
| 2605 # Continue the while loop. |
| 2606 # Sanity check of this code - we should end up with proper message footer. |
| 2607 assert [change_id] == git_footers.get_footer_change_id(message) |
| 2608 change_desc = ChangeDescription(message) |
| 2609 else: |
| 2610 change_desc = ChangeDescription( |
| 2611 options.message or CreateDescriptionFromLog(args)) |
2544 if not options.force: | 2612 if not options.force: |
2545 change_desc.prompt() | 2613 change_desc.prompt() |
2546 if not change_desc.description: | 2614 if not change_desc.description: |
2547 print "Description is empty; aborting." | 2615 DieWithError("Description is empty. Aborting...") |
2548 return 1 | |
2549 message = change_desc.description | 2616 message = change_desc.description |
2550 change_ids = git_footers.get_footer_change_id(message) | 2617 change_ids = git_footers.get_footer_change_id(message) |
2551 if len(change_ids) > 1: | 2618 if len(change_ids) > 1: |
2552 DieWithError('too many Change-Id footers in %s branch' % shadow_branch) | 2619 DieWithError('too many Change-Id footers, at most 1 allowed.') |
2553 if not change_ids: | 2620 if not change_ids: |
| 2621 # Generate the Change-Id automatically. |
2554 message = git_footers.add_footer_change_id( | 2622 message = git_footers.add_footer_change_id( |
2555 message, GenerateGerritChangeId(message)) | 2623 message, GenerateGerritChangeId(message)) |
2556 change_desc.set_description(message) | 2624 change_desc.set_description(message) |
2557 change_ids = git_footers.get_footer_change_id(message) | 2625 change_ids = git_footers.get_footer_change_id(message) |
2558 assert len(change_ids) == 1 | 2626 assert len(change_ids) == 1 |
2559 | |
2560 change_id = change_ids[0] | 2627 change_id = change_ids[0] |
2561 | 2628 |
2562 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch()) | 2629 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch()) |
2563 if remote is '.': | 2630 if remote is '.': |
2564 # If our upstream branch is local, we base our squashed commit on its | 2631 # If our upstream branch is local, we base our squashed commit on its |
2565 # squashed version. | 2632 # squashed version. |
2566 parent = ('refs/heads/git_cl_uploads/' + | 2633 upstream_branch_name = scm.GIT.ShortBranchName(upstream_branch) |
2567 scm.GIT.ShortBranchName(upstream_branch)) | 2634 # Check the squashed hash of the parent. |
2568 | 2635 parent = RunGit(['config', |
2569 # Verify that the upstream branch has been uploaded too, otherwise Gerrit | 2636 'branch.%s.gerritsquashhash' % upstream_branch_name], |
2570 # will create additional CLs when uploading. | 2637 error_ok=True).strip() |
2571 if (RunGitSilent(['rev-parse', upstream_branch + ':']) != | 2638 # Verify that the upstream branch has been uploaded too, otherwise |
2572 RunGitSilent(['rev-parse', parent + ':'])): | 2639 # Gerrit will create additional CLs when uploading. |
2573 print 'Upload upstream branch ' + upstream_branch + ' first.' | 2640 if not parent or (RunGitSilent(['rev-parse', upstream_branch + ':']) != |
2574 return 1 | 2641 RunGitSilent(['rev-parse', parent + ':'])): |
| 2642 # TODO(tandrii): remove "old depot_tools" part on April 12, 2016. |
| 2643 DieWithError( |
| 2644 'Upload upstream branch %s first.\n' |
| 2645 'Note: maybe you\'ve uploaded it with --no-squash or with an old\n' |
| 2646 ' version of depot_tools. If so, then re-upload it with:\n' |
| 2647 ' git cl upload --squash\n' % upstream_branch_name) |
2575 else: | 2648 else: |
2576 parent = cl.GetCommonAncestorWithUpstream() | 2649 parent = cl.GetCommonAncestorWithUpstream() |
2577 | 2650 |
2578 tree = RunGit(['rev-parse', 'HEAD:']).strip() | 2651 tree = RunGit(['rev-parse', 'HEAD:']).strip() |
2579 ref_to_push = RunGit(['commit-tree', tree, '-p', parent, | 2652 ref_to_push = RunGit(['commit-tree', tree, '-p', parent, |
2580 '-m', message]).strip() | 2653 '-m', message]).strip() |
2581 else: | 2654 else: |
| 2655 change_desc = ChangeDescription( |
| 2656 options.message or CreateDescriptionFromLog(args)) |
| 2657 if not change_desc.description: |
| 2658 DieWithError("Description is empty. Aborting...") |
| 2659 |
2582 if not git_footers.get_footer_change_id(change_desc.description): | 2660 if not git_footers.get_footer_change_id(change_desc.description): |
2583 DownloadGerritHook(False) | 2661 DownloadGerritHook(False) |
2584 change_desc.set_description(AddChangeIdToCommitMessage(options, args)) | 2662 change_desc.set_description(AddChangeIdToCommitMessage(options, args)) |
2585 ref_to_push = 'HEAD' | 2663 ref_to_push = 'HEAD' |
2586 parent = '%s/%s' % (gerrit_remote, branch) | 2664 parent = '%s/%s' % (gerrit_remote, branch) |
2587 change_id = git_footers.get_footer_change_id(change_desc.description)[0] | 2665 change_id = git_footers.get_footer_change_id(change_desc.description)[0] |
2588 | 2666 |
| 2667 assert change_desc |
2589 commits = RunGitSilent(['rev-list', '%s..%s' % (parent, | 2668 commits = RunGitSilent(['rev-list', '%s..%s' % (parent, |
2590 ref_to_push)]).splitlines() | 2669 ref_to_push)]).splitlines() |
2591 if len(commits) > 1: | 2670 if len(commits) > 1: |
2592 print('WARNING: This will upload %d commits. Run the following command ' | 2671 print('WARNING: This will upload %d commits. Run the following command ' |
2593 'to see which commits will be uploaded: ' % len(commits)) | 2672 'to see which commits will be uploaded: ' % len(commits)) |
2594 print('git log %s..%s' % (parent, ref_to_push)) | 2673 print('git log %s..%s' % (parent, ref_to_push)) |
2595 print('You can also use `git squash-branch` to squash these into a single ' | 2674 print('You can also use `git squash-branch` to squash these into a single ' |
2596 'commit.') | 2675 'commit.') |
2597 ask_for_data('About to upload; enter to confirm.') | 2676 ask_for_data('About to upload; enter to confirm.') |
2598 | 2677 |
(...skipping 26 matching lines...) Expand all Loading... |
2625 if options.squash: | 2704 if options.squash: |
2626 regex = re.compile(r'remote:\s+https?://[\w\-\.\/]*/(\d+)\s.*') | 2705 regex = re.compile(r'remote:\s+https?://[\w\-\.\/]*/(\d+)\s.*') |
2627 change_numbers = [m.group(1) | 2706 change_numbers = [m.group(1) |
2628 for m in map(regex.match, push_stdout.splitlines()) | 2707 for m in map(regex.match, push_stdout.splitlines()) |
2629 if m] | 2708 if m] |
2630 if len(change_numbers) != 1: | 2709 if len(change_numbers) != 1: |
2631 DieWithError( | 2710 DieWithError( |
2632 ('Created|Updated %d issues on Gerrit, but only 1 expected.\n' | 2711 ('Created|Updated %d issues on Gerrit, but only 1 expected.\n' |
2633 'Change-Id: %s') % (len(change_numbers), change_id)) | 2712 'Change-Id: %s') % (len(change_numbers), change_id)) |
2634 cl.SetIssue(change_numbers[0]) | 2713 cl.SetIssue(change_numbers[0]) |
2635 head = RunGit(['rev-parse', 'HEAD']).strip() | 2714 RunGit(['config', 'branch.%s.gerritsquashhash' % cl.GetBranch(), |
2636 RunGit(['update-ref', '-m', 'Uploaded ' + head, shadow_branch, ref_to_push]) | 2715 ref_to_push]) |
2637 return 0 | 2716 return 0 |
2638 | 2717 |
2639 | 2718 |
2640 def GetTargetRef(remote, remote_branch, target_branch, pending_prefix): | 2719 def GetTargetRef(remote, remote_branch, target_branch, pending_prefix): |
2641 """Computes the remote branch ref to use for the CL. | 2720 """Computes the remote branch ref to use for the CL. |
2642 | 2721 |
2643 Args: | 2722 Args: |
2644 remote (str): The git remote for the CL. | 2723 remote (str): The git remote for the CL. |
2645 remote_branch (str): The git remote branch for the CL. | 2724 remote_branch (str): The git remote branch for the CL. |
2646 target_branch (str): The target branch specified by the user. | 2725 target_branch (str): The target branch specified by the user. |
(...skipping 270 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2917 auth.add_auth_options(parser) | 2996 auth.add_auth_options(parser) |
2918 (options, args) = parser.parse_args(args) | 2997 (options, args) = parser.parse_args(args) |
2919 auth_config = auth.extract_auth_config_from_options(options) | 2998 auth_config = auth.extract_auth_config_from_options(options) |
2920 | 2999 |
2921 if git_common.is_dirty_git_tree('upload'): | 3000 if git_common.is_dirty_git_tree('upload'): |
2922 return 1 | 3001 return 1 |
2923 | 3002 |
2924 options.reviewers = cleanup_list(options.reviewers) | 3003 options.reviewers = cleanup_list(options.reviewers) |
2925 options.cc = cleanup_list(options.cc) | 3004 options.cc = cleanup_list(options.cc) |
2926 | 3005 |
| 3006 # For sanity of test expectations, do this otherwise lazy-loading *now*. |
| 3007 settings.GetIsGerrit() |
| 3008 |
2927 cl = Changelist(auth_config=auth_config) | 3009 cl = Changelist(auth_config=auth_config) |
2928 if args: | 3010 if args: |
2929 # TODO(ukai): is it ok for gerrit case? | 3011 # TODO(ukai): is it ok for gerrit case? |
2930 base_branch = args[0] | 3012 base_branch = args[0] |
2931 else: | 3013 else: |
2932 if cl.GetBranch() is None: | 3014 if cl.GetBranch() is None: |
2933 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!') | 3015 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!') |
2934 | 3016 |
2935 # Default to diffing against common ancestor of upstream branch | 3017 # Default to diffing against common ancestor of upstream branch |
2936 base_branch = cl.GetCommonAncestorWithUpstream() | 3018 base_branch = cl.GetCommonAncestorWithUpstream() |
(...skipping 1389 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4326 if __name__ == '__main__': | 4408 if __name__ == '__main__': |
4327 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4409 # These affect sys.stdout so do it outside of main() to simplify mocks in |
4328 # unit testing. | 4410 # unit testing. |
4329 fix_encoding.fix_encoding() | 4411 fix_encoding.fix_encoding() |
4330 colorama.init() | 4412 colorama.init() |
4331 try: | 4413 try: |
4332 sys.exit(main(sys.argv[1:])) | 4414 sys.exit(main(sys.argv[1:])) |
4333 except KeyboardInterrupt: | 4415 except KeyboardInterrupt: |
4334 sys.stderr.write('interrupted\n') | 4416 sys.stderr.write('interrupted\n') |
4335 sys.exit(1) | 4417 sys.exit(1) |
OLD | NEW |