diff options
author | Ben Murdoch <benm@google.com> | 2010-05-11 18:35:50 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-05-14 10:23:05 +0100 |
commit | 21939df44de1705786c545cd1bf519d47250322d (patch) | |
tree | ef56c310f5c0cdc379c2abb2e212308a3281ce20 /WebKitTools/Scripts/webkitpy/common/checkout/scm.py | |
parent | 4ff1d8891d520763f17675827154340c7c740f90 (diff) | |
download | external_webkit-21939df44de1705786c545cd1bf519d47250322d.zip external_webkit-21939df44de1705786c545cd1bf519d47250322d.tar.gz external_webkit-21939df44de1705786c545cd1bf519d47250322d.tar.bz2 |
Merge Webkit at r58956: Initial merge by Git.
Change-Id: I1d9fb60ea2c3f2ddc04c17a871acdb39353be228
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/common/checkout/scm.py')
-rw-r--r-- | WebKitTools/Scripts/webkitpy/common/checkout/scm.py | 199 |
1 files changed, 160 insertions, 39 deletions
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py index 2704f07..02e114a 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py @@ -41,11 +41,13 @@ from webkitpy.common.system.deprecated_logging import error, log def detect_scm_system(path): - if SVN.in_working_directory(path): - return SVN(cwd=path) + absolute_path = os.path.abspath(path) + + if SVN.in_working_directory(absolute_path): + return SVN(cwd=absolute_path) - if Git.in_working_directory(path): - return Git(cwd=path) + if Git.in_working_directory(absolute_path): + return Git(cwd=absolute_path) return None @@ -145,7 +147,7 @@ class SCM: return filenames def strip_r_from_svn_revision(self, svn_revision): - match = re.match("^r(?P<svn_revision>\d+)", svn_revision) + match = re.match("^r(?P<svn_revision>\d+)", unicode(svn_revision)) if (match): return match.group('svn_revision') return svn_revision @@ -178,7 +180,7 @@ class SCM: def add(self, path): raise NotImplementedError, "subclasses must implement" - def changed_files(self): + def changed_files(self, git_commit=None, squash=None): raise NotImplementedError, "subclasses must implement" def changed_files_for_revision(self): @@ -193,7 +195,7 @@ class SCM: def display_name(self): raise NotImplementedError, "subclasses must implement" - def create_patch(self): + def create_patch(self, git_commit=None, squash=None): raise NotImplementedError, "subclasses must implement" def committer_email_for_revision(self, revision): @@ -211,7 +213,10 @@ class SCM: def revert_files(self, file_paths): raise NotImplementedError, "subclasses must implement" - def commit_with_message(self, message, username=None): + def should_squash(self, squash): + raise NotImplementedError, "subclasses must implement" + + def commit_with_message(self, message, username=None, git_commit=None, squash=None): raise NotImplementedError, "subclasses must implement" def svn_commit_log(self, svn_revision): @@ -229,12 +234,6 @@ class SCM: def svn_merge_base(): raise NotImplementedError, "subclasses must implement" - def create_patch_from_local_commit(self, commit_id): - error("Your source control manager does not support creating a patch from a local commit.") - - def create_patch_since_local_commit(self, commit_id): - error("Your source control manager does not support creating a patch from a local commit.") - def commit_locally_with_message(self, message): error("Your source control manager does not support local commits.") @@ -308,7 +307,7 @@ class SVN(SCM): return self.cached_version def working_directory_is_clean(self): - return run_command(["svn", "diff"], cwd=self.checkout_root) == "" + return run_command(["svn", "diff"], cwd=self.checkout_root, decode_output=False) == "" def clean_working_directory(self): # svn revert -R is not as awesome as git reset --hard. @@ -339,12 +338,13 @@ class SVN(SCM): # path is assumed to be cwd relative? run_command(["svn", "add", path]) - def changed_files(self): + def changed_files(self, git_commit=None, squash=None): return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("ACDMR")) def changed_files_for_revision(self, revision): # As far as I can tell svn diff --summarize output looks just like svn status output. - status_command = ["svn", "diff", "--summarize", "-c", str(revision)] + # No file contents printed, thus utf-8 auto-decoding in run_command is fine. + status_command = ["svn", "diff", "--summarize", "-c", revision] return self.run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR")) def conflicted_files(self): @@ -360,19 +360,26 @@ class SVN(SCM): def display_name(self): return "svn" - def create_patch(self): - return run_command(self.script_path("svn-create-patch"), cwd=self.checkout_root, return_stderr=False) + def create_patch(self, git_commit=None, squash=None): + """Returns a byte array (str()) representing the patch file. + Patch files are effectively binary since they may contain + files of multiple different encodings.""" + return run_command([self.script_path("svn-create-patch")], + cwd=self.checkout_root, return_stderr=False, + decode_output=False) def committer_email_for_revision(self, revision): - return run_command(["svn", "propget", "svn:author", "--revprop", "-r", str(revision)]).rstrip() + return run_command(["svn", "propget", "svn:author", "--revprop", "-r", revision]).rstrip() def contents_at_revision(self, path, revision): + """Returns a byte array (str()) containing the contents + of path @ revision in the repository.""" remote_path = "%s/%s" % (self._repository_url(), path) - return run_command(["svn", "cat", "-r", str(revision), remote_path]) + return run_command(["svn", "cat", "-r", revision, remote_path], decode_output=False) def diff_for_revision(self, revision): # FIXME: This should probably use cwd=self.checkout_root - return run_command(['svn', 'diff', '-c', str(revision)]) + return run_command(['svn', 'diff', '-c', revision]) def _repository_url(self): return self.value_from_svn_info(self.checkout_root, 'URL') @@ -389,7 +396,12 @@ class SVN(SCM): # FIXME: This should probably use cwd=self.checkout_root. run_command(['svn', 'revert'] + file_paths) - def commit_with_message(self, message, username=None): + 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. 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." @@ -405,7 +417,7 @@ class SVN(SCM): return run_command(svn_commit_args, error_handler=commit_error_handler) def svn_commit_log(self, svn_revision): - svn_revision = self.strip_r_from_svn_revision(str(svn_revision)) + svn_revision = self.strip_r_from_svn_revision(svn_revision) return run_command(['svn', 'log', '--non-interactive', '--revision', svn_revision]); def last_svn_commit_log(self): @@ -466,6 +478,7 @@ class Git(SCM): def status_command(self): # git status returns non-zero when there are changes, so we use git diff name --name-status HEAD instead. + # No file contents printed, thus utf-8 autodecoding in run_command is fine. return ["git", "diff", "--name-status", "HEAD"] def _status_regexp(self, expected_types): @@ -475,8 +488,24 @@ class Git(SCM): # path is assumed to be cwd relative? run_command(["git", "add", path]) - def changed_files(self): - status_command = ['git', 'diff', '-r', '--name-status', '-C', '-M', 'HEAD'] + def _merge_base(self, git_commit, squash): + if git_commit: + # FIXME: Calling code should turn commit ranges into a list of commit IDs + # and then treat each commit separately. + if '..' not in git_commit: + git_commit = git_commit + "^.." + git_commit + return git_commit + + if self.should_squash(squash): + return self.svn_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' + + 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)] return self.run_status_and_extract_filenames(status_command, self._status_regexp("ADM")) def _changes_files_for_commit(self, git_commit): @@ -490,6 +519,8 @@ class Git(SCM): return self._changes_files_for_commit(commit_id) def conflicted_files(self): + # We do not need to pass decode_output for this diff command + # as we're passing --name-status which does not output any data. status_command = ['git', 'diff', '--name-status', '-C', '-M', '--diff-filter=U'] return self.run_status_and_extract_filenames(status_command, self._status_regexp("U")) @@ -503,9 +534,12 @@ class Git(SCM): def display_name(self): return "git" - def create_patch(self): + def create_patch(self, git_commit=None, squash=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 run_command(['git', 'diff', '--binary', 'HEAD']) + return run_command(['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self._merge_base(git_commit, squash)], decode_output=False) @classmethod def git_commit_from_svn_revision(cls, revision): @@ -517,11 +551,13 @@ class Git(SCM): return git_commit def contents_at_revision(self, path, revision): - return run_command(["git", "show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)]) + """Returns a byte array (str()) containing the contents + of path @ revision in the repository.""" + return run_command(["git", "show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)], decode_output=False) def diff_for_revision(self, revision): git_commit = self.git_commit_from_svn_revision(revision) - return self.create_patch_from_local_commit(git_commit) + return self.create_patch(git_commit) def committer_email_for_revision(self, revision): git_commit = self.git_commit_from_svn_revision(revision) @@ -538,11 +574,100 @@ class Git(SCM): def revert_files(self, file_paths): run_command(['git', 'checkout', 'HEAD'] + file_paths) - def commit_with_message(self, message, username=None): + def should_squash(self, squash): + if squash is not None: + # Squash is specified on the command-line. + return squash + + config_squash = Git.read_git_config('webkit-patch.squash') + if (config_squash and config_squash is not ""): + return config_squash.lower() == "true" + + # 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(): + working_directory_message = "" if self.working_directory_is_clean() else " and working copy changes" + raise ScriptError(message="""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)) + + return None + + def commit_with_message(self, message, username=None, git_commit=None, squash=None): # Username is ignored during Git commits. - self.commit_locally_with_message(message) + if git_commit: + # Need working directory changes to be committed so we can checkout the merge branch. + if not self.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? + 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: + run_command(['git', 'reset', '--soft', self.svn_branch_name()]) + 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. return self.push_local_commits_to_server() + def _commit_on_branch(self, message, git_commit): + branch_ref = run_command(['git', 'symbolic-ref', 'HEAD']).strip() + branch_name = branch_ref.replace('refs/heads/', '') + commit_ids = self.commit_ids_from_commitish_arguments([git_commit]) + + # We want to squash all this branch's commits into one commit with the proper description. + # We do this by doing a "merge --squash" into a new commit branch, then dcommitting that. + MERGE_BRANCH = 'webkit-patch-land' + self.delete_branch(MERGE_BRANCH) + + # We might be in a directory that's present in this branch but not in the + # trunk. Move up to the top of the tree so that git commands that expect a + # valid CWD won't fail after we check out the merge branch. + os.chdir(self.checkout_root) + + # Stuff our change into the merge branch. + # We wrap in a try...finally block so if anything goes wrong, we clean up the branches. + commit_succeeded = True + try: + run_command(['git', 'checkout', '-q', '-b', MERGE_BRANCH, self.svn_branch_name()]) + + for commit in commit_ids: + # We're on a different branch now, so convert "head" to the branch name. + commit = re.sub(r'(?i)head', branch_name, commit) + # FIXME: Once changed_files and create_patch are modified to separately handle each + # commit in a commit range, commit each cherry pick so they'll get dcommitted separately. + run_command(['git', 'cherry-pick', '--no-commit', commit]) + + run_command(['git', 'commit', '-m', message]) + output = self.push_local_commits_to_server() + except Exception, e: + log("COMMIT FAILED: " + str(e)) + output = "Commit failed." + commit_succeeded = False + finally: + # And then swap back to the original branch and clean up. + self.clean_working_directory() + run_command(['git', 'checkout', '-q', branch_name]) + self.delete_branch(MERGE_BRANCH) + + return output + def svn_commit_log(self, svn_revision): svn_revision = self.strip_r_from_svn_revision(svn_revision) return run_command(['git', 'svn', 'log', '-r', svn_revision]) @@ -560,13 +685,9 @@ class Git(SCM): return run_command(['git', 'merge-base', self.svn_branch_name(), 'HEAD']).strip() def svn_branch_name(self): - return Git.read_git_config('svn-remote.svn.fetch').split(':')[1] - - def create_patch_from_local_commit(self, commit_id): - return run_command(['git', 'diff', '--binary', commit_id + "^.." + commit_id]) - - def create_patch_since_local_commit(self, commit_id): - return run_command(['git', 'diff', '--binary', commit_id]) + # FIXME: This should so something like: Git.read_git_config('svn-remote.svn.fetch').split(':')[1] + # but that doesn't work if the git repo is tracking multiple svn branches. + return 'trunk' def commit_locally_with_message(self, message): run_command(['git', 'commit', '--all', '-F', '-'], input=message) |