| # Copyright (C) 2022 Apple Inc. All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions |
| # are met: |
| # 1. Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND |
| # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR |
| # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import logging |
| import re |
| import sys |
| import os |
| import re |
| |
| from .command import Command |
| from .branch import Branch |
| from .pull_request import PullRequest |
| |
| from webkitbugspy import Tracker |
| from webkitcorepy import arguments, run, Terminal |
| from webkitscmpy import local, log, remote |
| from ..commit import Commit |
| |
| |
| class Revert(Command): |
| name = 'revert' |
| help = 'Revert provided list of commits and create a pull-request with this revert commit' |
| REVERT_TITLE_TEMPLATE = 'Revert [{}] {}' |
| REVERT_TITLE_RE = re.compile(r'^Revert \[{}\] [\s\S]*'.format(Commit.IDENTIFIER_RE.pattern)) |
| |
| @classmethod |
| def parser(cls, parser, loggers=None): |
| PullRequest.parser(parser, loggers=loggers) |
| # Only allow revert one commit one time, because git automatically generate mesaage will only contain one commit information |
| parser.add_argument( |
| 'commit', |
| help='git hash, svn revision or identifier you want to revert' |
| ) |
| |
| parser.add_argument( |
| '--pr', |
| default=False, |
| action='store_true', |
| help='Create a pull request at the same time' |
| ) |
| |
| @classmethod |
| def revert_commit(cls, args, repository, **kwargs): |
| # Check if there are any outstanding changes: |
| if repository.modified(): |
| sys.stderr.write('Please commit your changes or stash them before you revert commit: {}'.format(args.commit)) |
| return 1 |
| # Make sure we have the commit that user want to revert |
| try: |
| commit = repository.find(args.commit, include_log=True) |
| except (local.Scm.Exception, ValueError) as exception: |
| # ValueErrors and Scm exceptions usually contain enough information to be displayed |
| # to the user as an error |
| sys.stderr.write('Could not find "{}"'.format(args.commit) + '\n') |
| return 1 |
| |
| result = run([repository.executable(), 'revert', '--no-commit'] + [commit.hash], cwd=repository.root_path, capture_output=True) |
| if result.returncode: |
| # git revert will output nothing if this commit is already reverted |
| if not result.stdout.strip(): |
| sys.stderr.write('The commits you spiecfied seems already be reverted.') |
| return 2 |
| # print out |
| sys.stderr.write(result.stdout.decode('utf-8')) |
| sys.stderr.write(result.stderr.decode('utf-8')) |
| sys.stderr.write('If you have merge conflicts, after resolving them, please use git-webkit pfr to publish your pull request') |
| return 1 |
| # restore all ChangeLog changes, we should not revert those entries |
| modifiled_files = repository.modified() |
| if not modifiled_files: |
| sys.stderr.write('Failed to detect any diff after revert.') |
| return 1 |
| for file in modifiled_files: |
| if 'ChangeLog' in file: |
| result = run([repository.executable(), 'restore', '--staged', file], cwd=repository.root_path) |
| if result.returncode: |
| sys.stderr.write('Failed to restore staged file: {}'.format(file)) |
| run([repository.executable(), 'revert', '--abort'], cwd=repository.root_path) |
| return 1 |
| |
| result = run([repository.executable(), 'checkout', file], cwd=repository.root_path) |
| if result.returncode: |
| sys.stderr.write('Failed to checkout file: {}'.format(file)) |
| run([repository.executable(), 'revert', '--abort'], cwd=repository.root_path) |
| return 1 |
| |
| bug_urls = [] |
| commit_title = None |
| for line in commit.message.splitlines(): |
| if not commit_title: |
| commit_title = line |
| tracker = Tracker.from_string(line) |
| if tracker: |
| bug_urls.append(line) |
| env = os.environ |
| env['COMMIT_MESSAGE_TITLE'] = cls.REVERT_TITLE_TEMPLATE.format(commit, commit_title) |
| env['COMMIT_MESSAGE_BUG'] = '\n'.join(bug_urls) |
| result = run([repository.executable(), 'commit', '--date=now'], cwd=repository.root_path, env=env) |
| if result.returncode: |
| run([repository.executable(), 'revert', '--abort'], cwd=repository.root_path) |
| sys.stderr.write('Failed revert commit') |
| return 1 |
| log.info('Reverted {}'.format(commit.hash)) |
| return 0 |
| |
| @classmethod |
| def main(cls, args, repository, **kwargs): |
| if not isinstance(repository, local.Git): |
| sys.stderr.write("Can only '{}' on a native Git repository\n".format(cls.name)) |
| return 1 |
| |
| if not PullRequest.check_pull_request_args(repository, args): |
| return 1 |
| |
| branch_point = PullRequest.pull_request_branch_point(repository, args, **kwargs) |
| if not branch_point: |
| return 1 |
| |
| result = cls.revert_commit(args, repository, **kwargs) |
| if result: |
| return result |
| |
| if not args.pr: |
| return result |
| else: |
| return PullRequest.create_pull_request(repository, args, branch_point) |
| |