| 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 | 
|---|