summaryrefslogtreecommitdiffstats
path: root/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2010-05-11 18:35:50 +0100
committerBen Murdoch <benm@google.com>2010-05-14 10:23:05 +0100
commit21939df44de1705786c545cd1bf519d47250322d (patch)
treeef56c310f5c0cdc379c2abb2e212308a3281ce20 /WebKitTools/Scripts/webkitpy/common/checkout/scm.py
parent4ff1d8891d520763f17675827154340c7c740f90 (diff)
downloadexternal_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.py199
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)