diff options
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/common/checkout/scm.py')
-rw-r--r-- | WebKitTools/Scripts/webkitpy/common/checkout/scm.py | 141 |
1 files changed, 62 insertions, 79 deletions
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py index d7c621c..569558a 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py @@ -35,7 +35,6 @@ import sys import shutil from webkitpy.common.system.executive import Executive, run_command, ScriptError -from webkitpy.common.system.user import User from webkitpy.common.system.deprecated_logging import error, log @@ -94,6 +93,17 @@ def commit_error_handler(error): Executive.default_error_handler(error) +class AuthenticationError(Exception): + def __init__(self, server_host): + self.server_host = server_host + + +class AmbiguousCommitError(Exception): + def __init__(self, num_local_commits, working_directory_is_clean): + self.num_local_commits = num_local_commits + self.working_directory_is_clean = working_directory_is_clean + + # SCM methods are expected to return paths relative to self.checkout_root. class SCM: def __init__(self, cwd): @@ -198,7 +208,7 @@ class SCM: def delete(self, path): self._subclass_must_implement() - def changed_files(self, git_commit=None, squash=None): + def changed_files(self, git_commit=None): self._subclass_must_implement() def changed_files_for_revision(self): @@ -213,7 +223,7 @@ class SCM: def display_name(self): self._subclass_must_implement() - def create_patch(self, git_commit=None, squash=None): + def create_patch(self, git_commit=None): self._subclass_must_implement() def committer_email_for_revision(self, revision): @@ -237,10 +247,7 @@ class SCM: def revert_files(self, file_paths): self._subclass_must_implement() - def should_squash(self, squash): - self._subclass_must_implement() - - def commit_with_message(self, message, username=None, git_commit=None, squash=None): + def commit_with_message(self, message, username=None, git_commit=None, force_squash=False): self._subclass_must_implement() def svn_commit_log(self, svn_revision): @@ -377,7 +384,7 @@ class SVN(SCM): parent, base = os.path.split(os.path.abspath(path)) return self.run(["svn", "delete", "--force", base], cwd=parent) - def changed_files(self, git_commit=None, squash=None): + def changed_files(self, git_commit=None): return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("ACDMR")) def changed_files_for_revision(self, revision): @@ -403,7 +410,7 @@ class SVN(SCM): return "svn" # FIXME: This method should be on Checkout. - def create_patch(self, git_commit=None, squash=None): + def create_patch(self, git_commit=None): """Returns a byte array (str()) representing the patch file. Patch files are effectively binary since they may contain files of multiple different encodings.""" @@ -477,22 +484,19 @@ class SVN(SCM): # FIXME: This should probably use cwd=self.checkout_root. self.run(['svn', 'revert'] + file_paths) - def should_squash(self, squash): - # SVN doesn't support the concept of squashing. - return False - - def commit_with_message(self, message, username=None, git_commit=None, squash=None): - # squash and git-commit are not used by SVN. + def commit_with_message(self, message, username=None, git_commit=None, force_squash=False): + # git-commit and force are not used by SVN. if self.dryrun: # Return a string which looks like a commit so that things which parse this output will succeed. return "Dry run, no commit.\nCommitted revision 0." + svn_commit_args = ["svn", "commit"] + if not username and not self.has_authorization_for_realm(): - username = User.prompt("%s login: " % self.svn_server_host, repeat=5) - if not username: - raise Exception("You need to specify the username on %s to perform the commit as." % self.svn_server_host) + raise AuthenticationError(self.svn_server_host) if username: svn_commit_args.extend(["--username", username]) + svn_commit_args.extend(["-m", message]) # FIXME: Should this use cwd=self.checkout_root? return self.run(svn_commit_args, error_handler=commit_error_handler) @@ -584,24 +588,25 @@ class Git(SCM): def delete(self, path): return self.run(["git", "rm", "-f", path]) - def _merge_base(self, git_commit, squash): + def _assert_synced(self): + if len(run_command(['git', 'rev-list', '--max-count=1', self.remote_branch_ref(), '^HEAD'])): + raise ScriptError(message="Not fully merged/rebased to %s. This branch needs to be synced first." % self.remote_branch_ref()) + + def merge_base(self, git_commit): if git_commit: - # FIXME: Calling code should turn commit ranges into a list of commit IDs - # and then treat each commit separately. + # Special-case HEAD.. to mean working-copy changes only. + if git_commit.upper() == 'HEAD..': + return 'HEAD' + if '..' not in git_commit: git_commit = git_commit + "^.." + git_commit return git_commit - if self.should_squash(squash): - return self.remote_merge_base() - - # FIXME: Non-squash behavior should match commit_with_message. It raises an error - # if there are working copy changes and --squash or --no-squash wasn't passed in. - # If --no-squash, then it should proceed with each local commit as a separate patch. - return 'HEAD' + self._assert_synced() + return self.remote_merge_base() - def changed_files(self, git_commit=None, squash=None): - status_command = ['git', 'diff', '-r', '--name-status', '-C', '-M', "--no-ext-diff", "--full-index", self._merge_base(git_commit, squash)] + def changed_files(self, git_commit=None): + status_command = ['git', 'diff', '-r', '--name-status', '-C', '-M', "--no-ext-diff", "--full-index", self.merge_base(git_commit)] return self.run_status_and_extract_filenames(status_command, self._status_regexp("ADM")) def _changes_files_for_commit(self, git_commit): @@ -633,12 +638,12 @@ class Git(SCM): def display_name(self): return "git" - def create_patch(self, git_commit=None, squash=None): + def create_patch(self, git_commit=None): """Returns a byte array (str()) representing the patch file. Patch files are effectively binary since they may contain files of multiple different encodings.""" # FIXME: This should probably use cwd=self.checkout_root - return self.run(['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self._merge_base(git_commit, squash)], decode_output=False) + return self.run(['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self.merge_base(git_commit)], decode_output=False) @classmethod def git_commit_from_svn_revision(cls, revision): @@ -679,63 +684,41 @@ class Git(SCM): def revert_files(self, file_paths): self.run(['git', 'checkout', 'HEAD'] + file_paths) - def _get_squash_error_message(self, num_local_commits): - working_directory_message = "" if self.working_directory_is_clean() else " and working copy changes" - return ("""There are %s local commits%s. Do one of the following: - 1) Use --squash or --no-squash - 2) git config webkit-patch.squash true/false - """ % (num_local_commits, working_directory_message)) - - def should_squash(self, squash): - if squash is None: - config_squash = Git.read_git_config('webkit-patch.squash') - if (config_squash and config_squash is not ""): - squash = config_squash.lower() == "true" - else: - # Only raise an error if there are actually multiple commits to squash. - num_local_commits = len(self.local_commits()) - if num_local_commits > 1 or (num_local_commits > 0 and not self.working_directory_is_clean()): - raise ScriptError(message=self._get_squash_error_message(num_local_commits)) - - if squash and self._remote_branch_has_extra_commits(): - raise ScriptError(message="Cannot use --squash when HEAD is not fully merged/rebased to %s. " - "This branch needs to be synced first." % self.remote_branch_ref()) + def _assert_can_squash(self, working_directory_is_clean): + squash = Git.read_git_config('webkit-patch.commit_should_always_squash') + should_squash = squash and squash.lower() == "true" - return squash + if not should_squash: + # Only warn if there are actually multiple commits to squash. + num_local_commits = len(self.local_commits()) + if num_local_commits > 1 or (num_local_commits > 0 and not working_directory_is_clean): + raise AmbiguousCommitError(num_local_commits, working_directory_is_clean) - def _remote_branch_has_extra_commits(self): - return len(run_command(['git', 'rev-list', '--max-count=1', self.remote_branch_ref(), '^HEAD'])) - - def commit_with_message(self, message, username=None, git_commit=None, squash=None): + def commit_with_message(self, message, username=None, git_commit=None, force_squash=False): # Username is ignored during Git commits. + working_directory_is_clean = self.working_directory_is_clean() + if git_commit: + # Special-case HEAD.. to mean working-copy changes only. + if git_commit.upper() == 'HEAD..': + if working_directory_is_clean: + raise ScriptError(message="The working copy is not modified. --git-commit=HEAD.. only commits working copy changes.") + self.commit_locally_with_message(message) + return self._commit_on_branch(message, 'HEAD') + # Need working directory changes to be committed so we can checkout the merge branch. - if not self.working_directory_is_clean(): + if not working_directory_is_clean: # FIXME: webkit-patch land will modify the ChangeLogs to correct the reviewer. # That will modify the working-copy and cause us to hit this error. - # The ChangeLog modification could be made to modify the existing local commit? + # The ChangeLog modification could be made to modify the existing local commit. raise ScriptError(message="Working copy is modified. Cannot commit individual git_commits.") return self._commit_on_branch(message, git_commit) - squash = self.should_squash(squash) - if squash: - self.run(['git', 'reset', '--soft', self.remote_branch_ref()]) - self.commit_locally_with_message(message) - elif not self.working_directory_is_clean(): - if not len(self.local_commits()): - # There are only working copy changes. Assume they should be committed. - self.commit_locally_with_message(message) - elif squash is None: - # The user didn't explicitly say to squash or not squash. There are local commits - # and working copy changes. Not clear what the user wants. - raise ScriptError(message="""There are local commits and working copy changes. Do one of the following: -1) Commit/revert working copy changes. -2) Use --squash or --no-squash -3) git config webkit-patch.squash true/false -""") - - # FIXME: This will commit all local commits, each with it's own message. We should restructure - # so that each local commit has the appropriate commit message based off it's ChangeLogs. + if not force_squash: + self._assert_can_squash(working_directory_is_clean) + self._assert_synced() + self.run(['git', 'reset', '--soft', self.remote_branch_ref()]) + self.commit_locally_with_message(message) return self.push_local_commits_to_server() def _commit_on_branch(self, message, git_commit): |