def test_run_command_with_pipe(self): input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) self.assertEqual( run_command(['grep', 'bar'], input=input_process.stdout), "bar\n") # Test the non-pipe case too: self.assertEqual(run_command(['grep', 'bar'], input="foo\nbar"), "bar\n") command_returns_non_zero = ['/bin/sh', '--invalid-option'] # Test when the input pipe process fails. input_process = subprocess.Popen(command_returns_non_zero, stdout=subprocess.PIPE, stderr=self.dev_null) self.assertTrue(input_process.poll() != 0) self.assertRaises(ScriptError, run_command, ['grep', 'bar'], input=input_process.stdout) # Test when the run_command process fails. input_process = subprocess.Popen( ['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null ) # grep shows usage and calls exit(2) when called w/o arguments. self.assertRaises(ScriptError, run_command, command_returns_non_zero, input=input_process.stdout)
def tear_down(cls, test_object): run_command(['rm', '-rf', test_object.svn_repo_path]) run_command(['rm', '-rf', test_object.svn_checkout_path]) # Now that we've deleted the checkout paths, cwddir may be invalid # Change back to a valid directory so that later calls to os.getcwd() do not fail. os.chdir(detect_scm_system(os.path.dirname(__file__)).checkout_root)
def apply_reverse_diff(self, revision): # '-c -revision' applies the inverse diff of 'revision' svn_merge_args = ['svn', 'merge', '--non-interactive', '-c', '-%s' % revision, self._repository_url()] log("WARNING: svn merge has been known to take more than 10 minutes to complete. It is recommended you use git for rollouts.") log("Running '%s'" % " ".join(svn_merge_args)) # FIXME: Should this use cwd=self.checkout_root? run_command(svn_merge_args)
def _setup_test_commits(cls, test_object): # Add some test commits os.chdir(test_object.svn_checkout_path) write_into_file_at_path("test_file", "test1") cls._svn_add("test_file") cls._svn_commit("initial commit") write_into_file_at_path("test_file", "test1test2") # This used to be the last commit, but doing so broke # GitTest.test_apply_git_patch which use the inverse diff of the last commit. # svn-apply fails to remove directories in Git, see: # https://bugs.webkit.org/show_bug.cgi?id=34871 os.mkdir("test_dir") # Slash should always be the right path separator since we use cygwin on Windows. test_file3_path = "test_dir/test_file3" write_into_file_at_path(test_file3_path, "third file") cls._svn_add("test_dir") cls._svn_commit("second commit") write_into_file_at_path("test_file", "test1test2test3\n") write_into_file_at_path("test_file2", "second file") cls._svn_add("test_file2") cls._svn_commit("third commit") # This 4th commit is used to make sure that our patch file handling # code correctly treats patches as binary and does not attempt to # decode them assuming they're utf-8. write_into_file_at_path("test_file", u"latin1 test: \u00A0\n", "latin1") write_into_file_at_path("test_file2", u"utf-8 test: \u00A0\n", "utf-8") cls._svn_commit("fourth commit") # svn does not seem to update after commit as I would expect. run_command(['svn', 'update'])
def _setup_test_commits(cls, test_object): # Add some test commits os.chdir(test_object.svn_checkout_path) write_into_file_at_path("test_file", "test1") cls._svn_add("test_file") cls._svn_commit("initial commit") write_into_file_at_path("test_file", "test1test2") # This used to be the last commit, but doing so broke # GitTest.test_apply_git_patch which use the inverse diff of the last commit. # svn-apply fails to remove directories in Git, see: # https://bugs.webkit.org/show_bug.cgi?id=34871 os.mkdir("test_dir") # Slash should always be the right path separator since we use cygwin on Windows. test_file3_path = "test_dir/test_file3" write_into_file_at_path(test_file3_path, "third file") cls._svn_add("test_dir") cls._svn_commit("second commit") write_into_file_at_path("test_file", "test1test2test3\n") write_into_file_at_path("test_file2", "second file") cls._svn_add("test_file2") cls._svn_commit("third commit") write_into_file_at_path("test_file", "test1test2test3\ntest4\n") cls._svn_commit("fourth commit") # svn does not seem to update after commit as I would expect. run_command(['svn', 'update'])
def clean_working_directory(self): # FIXME: These should probably use cwd=self.checkout_root. # Could run git clean here too, but that wouldn't match working_directory_is_clean run_command(['git', 'reset', '--hard', 'HEAD']) # Aborting rebase even though this does not match working_directory_is_clean if self.rebase_in_progress(): run_command(['git', 'rebase', '--abort'])
def commit_with_message(self, message, username=None, git_commit=None, squash=None): # Username is ignored during Git commits. 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 test_create_patch_is_full_patch(self): test_dir_path = os.path.join(self.svn_checkout_path, "test_dir2") os.mkdir(test_dir_path) test_file_path = os.path.join(test_dir_path, 'test_file2') write_into_file_at_path(test_file_path, 'test content') run_command(['svn', 'add', 'test_dir2']) # create_patch depends on 'svn-create-patch', so make a dummy version. scripts_path = os.path.join(self.svn_checkout_path, 'WebKitTools', 'Scripts') os.makedirs(scripts_path) create_patch_path = os.path.join(scripts_path, 'svn-create-patch') write_into_file_at_path( create_patch_path, '#!/bin/sh\necho $PWD' ) # We could pass -n to prevent the \n, but not all echo accept -n. os.chmod(create_patch_path, stat.S_IXUSR | stat.S_IRUSR) # Change into our test directory and run the create_patch command. os.chdir(test_dir_path) scm = detect_scm_system(test_dir_path) self.assertEqual(scm.checkout_root, self.svn_checkout_path ) # Sanity check that detection worked right. patch_contents = scm.create_patch() # Our fake 'svn-create-patch' returns $PWD instead of a patch, check that it was executed from the root of the repo. self.assertEqual("%s\n" % os.path.realpath(scm.checkout_root), patch_contents) # Add a \n because echo adds a \n.
def _one_local_commit_plus_working_copy_changes(self): write_into_file_at_path('test_file_commit1', 'more test content') run_command(['git', 'add', 'test_file_commit1']) self.scm.commit_locally_with_message("another test commit") write_into_file_at_path('test_file_commit2', 'still more test content') run_command(['git', 'add', 'test_file_commit2'])
def _setup_test_commits(cls, test_object): # Add some test commits os.chdir(test_object.svn_checkout_path) write_into_file_at_path("test_file", "test1") cls._svn_add("test_file") cls._svn_commit("initial commit") write_into_file_at_path("test_file", "test1test2") # This used to be the last commit, but doing so broke # GitTest.test_apply_git_patch which use the inverse diff of the last commit. # svn-apply fails to remove directories in Git, see: # https://bugs.webkit.org/show_bug.cgi?id=34871 os.mkdir("test_dir") # Slash should always be the right path separator since we use cygwin on Windows. test_file3_path = "test_dir/test_file3" write_into_file_at_path(test_file3_path, "third file") cls._svn_add("test_dir") cls._svn_commit("second commit") write_into_file_at_path("test_file", "test1test2test3\n") write_into_file_at_path("test_file2", "second file") cls._svn_add("test_file2") cls._svn_commit("third commit") # This 4th commit is used to make sure that our patch file handling # code correctly treats patches as binary and does not attempt to # decode them assuming they're utf-8. write_into_file_at_path("test_file", u"latin1 test: \u00A0\n", "latin1") write_into_file_at_path("test_file2", u"utf-8 test: \u00A0\n", "utf-8") cls._svn_commit("fourth commit") # svn does not seem to update after commit as I would expect. run_command(['svn', 'update'])
def _one_local_commit_plus_working_copy_changes(self): write_into_file_at_path('test_file_commit1', 'more test content') run_command(['git', 'add', 'test_file_commit1']) self.scm.commit_locally_with_message("another test commit") write_into_file_at_path('test_file_commit2', 'still more test content') run_command(['git', 'add', 'test_file_commit2'])
def test_rebase_in_progress(self): svn_test_file = os.path.join(self.svn_checkout_path, 'test_file') write_into_file_at_path(svn_test_file, "svn_checkout") run_command([ 'svn', 'commit', '--message', 'commit to conflict with git commit' ], cwd=self.svn_checkout_path) git_test_file = os.path.join(self.git_checkout_path, 'test_file') write_into_file_at_path(git_test_file, "git_checkout") run_command([ 'git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort' ]) # --quiet doesn't make git svn silent, so use run_silent to redirect output self.assertRaises( ScriptError, run_silent, ['git', 'svn', '--quiet', 'rebase' ]) # Will fail due to a conflict leaving us mid-rebase. scm = detect_scm_system(self.git_checkout_path) self.assertTrue(scm.rebase_in_progress()) # Make sure our cleanup works. scm.clean_working_directory() self.assertFalse(scm.rebase_in_progress()) # Make sure cleanup doesn't throw when no rebase is in progress. scm.clean_working_directory()
def tear_down(cls, test_object): run_command(['rm', '-rf', test_object.svn_repo_path]) run_command(['rm', '-rf', test_object.svn_checkout_path]) # Now that we've deleted the checkout paths, cwddir may be invalid # Change back to a valid directory so that later calls to os.getcwd() do not fail. os.chdir(detect_scm_system(os.path.dirname(__file__)).checkout_root)
def _two_local_commits(self): write_into_file_at_path('test_file_commit1', 'more test content') run_command(['git', 'add', 'test_file_commit1']) self.scm.commit_locally_with_message("another test commit") write_into_file_at_path('test_file_commit2', 'still more test content') run_command(['git', 'add', 'test_file_commit2']) self.scm.commit_locally_with_message("yet another test commit")
def _two_local_commits(self): write_into_file_at_path('test_file_commit1', 'more test content') run_command(['git', 'add', 'test_file_commit1']) self.scm.commit_locally_with_message("another test commit") write_into_file_at_path('test_file_commit2', 'still more test content') run_command(['git', 'add', 'test_file_commit2']) self.scm.commit_locally_with_message("yet another test commit")
def test_discard_local_commits(self): test_file = os.path.join(self.git_checkout_path, 'test_file') write_into_file_at_path(test_file, 'foo') run_command(['git', 'commit', '-a', '-m', 'local commit']) self.assertEqual(len(self.scm.local_commits()), 1) self.scm.discard_local_commits() self.assertEqual(len(self.scm.local_commits()), 0)
def test_discard_local_commits(self): test_file = os.path.join(self.git_checkout_path, 'test_file') write_into_file_at_path(test_file, 'foo') run_command(['git', 'commit', '-a', '-m', 'local commit']) self.assertEqual(len(self.scm.local_commits()), 1) self.scm.discard_local_commits() self.assertEqual(len(self.scm.local_commits()), 0)
def ensure_clean_working_directory(self, force_clean): if not force_clean and not self.working_directory_is_clean(): # FIXME: Shouldn't this use cwd=self.checkout_root? print run_command(self.status_command(), error_handler=Executive.ignore_error) raise ScriptError(message="Working directory has modifications, pass --force-clean or --no-clean to continue.") log("Cleaning working directory") self.clean_working_directory()
def test_commit_with_message_working_copy_only(self): write_into_file_at_path('test_file_commit1', 'more test content') run_command(['git', 'add', 'test_file_commit1']) scm = detect_scm_system(self.git_checkout_path) commit_text = scm.commit_with_message("yet another test commit") self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) self.assertTrue(re.search(r'test_file_commit1', svn_log))
def test_commit_with_message_working_copy_only(self): write_into_file_at_path('test_file_commit1', 'more test content') run_command(['git', 'add', 'test_file_commit1']) scm = detect_scm_system(self.git_checkout_path) commit_text = scm.commit_with_message("yet another test commit") self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) self.assertTrue(re.search(r'test_file_commit1', svn_log))
def apply_patch(self, patch, force=False): # It's possible that the patch was not made from the root directory. # We should detect and handle that case. # FIXME: Move _scm.script_path here once we get rid of all the dependencies. args = [self._scm.script_path('svn-apply')] if patch.reviewer(): args += ['--reviewer', patch.reviewer().full_name] if force: args.append('--force') run_command(args, input=patch.contents())
def apply_patch(self, patch, force=False): # It's possible that the patch was not made from the root directory. # We should detect and handle that case. # FIXME: Move _scm.script_path here once we get rid of all the dependencies. args = [self._scm.script_path('svn-apply')] if patch.reviewer(): args += ['--reviewer', patch.reviewer().full_name] if force: args.append('--force') run_command(args, input=patch.contents())
def test_svn_merge_base(self): # Diff to merge-base should include working-copy changes, # which the diff to svn_branch.. doesn't. test_file = os.path.join(self.git_checkout_path, 'test_file') write_into_file_at_path(test_file, 'foo') diff_to_common_base = run_command(['git', 'diff', self.scm.svn_branch_name() + '..']) diff_to_merge_base = run_command(['git', 'diff', self.scm.svn_merge_base()]) self.assertFalse(re.search(r'foo', diff_to_common_base)) self.assertTrue(re.search(r'foo', diff_to_merge_base))
def test_commit_with_message_multiple_local_commits_no_squash(self): self._two_local_commits() scm = detect_scm_system(self.git_checkout_path) commit_text = scm.commit_with_message("yet another test commit", squash=False) self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) self.assertTrue(re.search(r'test_file_commit2', svn_log)) self.assertFalse(re.search(r'test_file_commit1', svn_log)) svn_log = run_command(['git', 'svn', 'log', '--limit=2', '--verbose']) self.assertTrue(re.search(r'test_file_commit1', svn_log))
def apply_patch(self, patch, force=False): # It's possible that the patch was not made from the root directory. # We should detect and handle that case. # FIXME: Use Executive instead of subprocess here. curl_process = subprocess.Popen(['curl', '--location', '--silent', '--show-error', patch.url()], stdout=subprocess.PIPE) # FIXME: Move _scm.script_path here once we get rid of all the dependencies. args = [self._scm.script_path('svn-apply')] if patch.reviewer(): args += ['--reviewer', patch.reviewer().full_name] if force: args.append('--force') run_command(args, input=curl_process.stdout)
def setup(cls, test_object): # Create an test SVN repository test_object.svn_repo_path = tempfile.mkdtemp(suffix="svn_test_repo") test_object.svn_repo_url = "file://%s" % test_object.svn_repo_path # Not sure this will work on windows # git svn complains if we don't pass --pre-1.5-compatible, not sure why: # Expected FS format '2'; found format '3' at /usr/local/libexec/git-core//git-svn line 1477 run_command(['svnadmin', 'create', '--pre-1.5-compatible', test_object.svn_repo_path]) # Create a test svn checkout test_object.svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout") run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url, test_object.svn_checkout_path]) cls._setup_test_commits(test_object)
def test_commit_with_message_multiple_local_commits_no_squash(self): self._two_local_commits() scm = detect_scm_system(self.git_checkout_path) commit_text = scm.commit_with_message("yet another test commit", squash=False) self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) self.assertTrue(re.search(r'test_file_commit2', svn_log)) self.assertFalse(re.search(r'test_file_commit1', svn_log)) svn_log = run_command(['git', 'svn', 'log', '--limit=2', '--verbose']) self.assertTrue(re.search(r'test_file_commit1', svn_log))
def test_create_binary_patch(self): # Create a git binary patch and check the contents. scm = detect_scm_system(self.git_checkout_path) test_file_name = 'binary_file' test_file_path = os.path.join(self.git_checkout_path, test_file_name) file_contents = ''.join(map(chr, range(256))) write_into_file_at_path(test_file_path, file_contents, encoding=None) run_command(['git', 'add', test_file_name]) patch = scm.create_patch() self.assertTrue(re.search(r'\nliteral 0\n', patch)) self.assertTrue(re.search(r'\nliteral 256\n', patch)) # Check if we can apply the created patch. run_command(['git', 'rm', '-f', test_file_name]) self._setup_webkittools_scripts_symlink(scm) self.checkout.apply_patch(self._create_patch(patch)) self.assertEqual(file_contents, read_from_path(test_file_path, encoding=None)) # Check if we can create a patch from a local commit. write_into_file_at_path(test_file_path, file_contents, encoding=None) run_command(['git', 'add', test_file_name]) run_command(['git', 'commit', '-m', 'binary diff']) patch_from_local_commit = scm.create_patch('HEAD') self.assertTrue(re.search(r'\nliteral 0\n', patch_from_local_commit)) self.assertTrue(re.search(r'\nliteral 256\n', patch_from_local_commit))
def commit_ids_from_commitish_arguments(self, args): if not len(args): args.append('%s..HEAD' % self.svn_branch_name()) commit_ids = [] for commitish in args: if '...' in commitish: raise ScriptError(message="'...' is not supported (found in '%s'). Did you mean '..'?" % commitish) elif '..' in commitish: commit_ids += reversed(run_command(['git', 'rev-list', commitish]).splitlines()) else: # Turn single commits or branch or tag names into commit ids. commit_ids += run_command(['git', 'rev-parse', '--revs-only', commitish]).splitlines() return commit_ids
def test_create_binary_patch(self): # Create a git binary patch and check the contents. scm = detect_scm_system(self.git_checkout_path) test_file_name = 'binary_file' test_file_path = os.path.join(self.git_checkout_path, test_file_name) file_contents = ''.join(map(chr, range(256))) write_into_file_at_path(test_file_path, file_contents, encoding=None) run_command(['git', 'add', test_file_name]) patch = scm.create_patch() self.assertTrue(re.search(r'\nliteral 0\n', patch)) self.assertTrue(re.search(r'\nliteral 256\n', patch)) # Check if we can apply the created patch. run_command(['git', 'rm', '-f', test_file_name]) self._setup_webkittools_scripts_symlink(scm) self.checkout.apply_patch(self._create_patch(patch)) self.assertEqual(file_contents, read_from_path(test_file_path, encoding=None)) # Check if we can create a patch from a local commit. write_into_file_at_path(test_file_path, file_contents, encoding=None) run_command(['git', 'add', test_file_name]) run_command(['git', 'commit', '-m', 'binary diff']) patch_from_local_commit = scm.create_patch('HEAD') self.assertTrue(re.search(r'\nliteral 0\n', patch_from_local_commit)) self.assertTrue(re.search(r'\nliteral 256\n', patch_from_local_commit))
def setup(cls, test_object): # Create an test SVN repository test_object.svn_repo_path = tempfile.mkdtemp(suffix="svn_test_repo") test_object.svn_repo_url = "file://%s" % test_object.svn_repo_path # Not sure this will work on windows # git svn complains if we don't pass --pre-1.5-compatible, not sure why: # Expected FS format '2'; found format '3' at /usr/local/libexec/git-core//git-svn line 1477 run_command(['svnadmin', 'create', '--pre-1.5-compatible', test_object.svn_repo_path]) # Create a test svn checkout test_object.svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout") run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url, test_object.svn_checkout_path]) # Create and checkout a trunk dir to match the standard svn configuration to match git-svn's expectations os.chdir(test_object.svn_checkout_path) os.mkdir('trunk') cls._svn_add('trunk') # We can add tags and branches as well if we ever need to test those. cls._svn_commit('add trunk') # Change directory out of the svn checkout so we can delete the checkout directory. # _setup_test_commits will CD back to the svn checkout directory. os.chdir('/') run_command(['rm', '-rf', test_object.svn_checkout_path]) run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url + '/trunk', test_object.svn_checkout_path]) cls._setup_test_commits(test_object)
def find_checkout_root(cls, path): # "git rev-parse --show-cdup" would be another way to get to the root (checkout_root, dot_git) = os.path.split(run_command(['git', 'rev-parse', '--git-dir'], cwd=(path or "./"))) # If we were using 2.6 # checkout_root = os.path.relpath(checkout_root, path) if not os.path.isabs(checkout_root): # Sometimes git returns relative paths checkout_root = os.path.join(path, checkout_root) return checkout_root
def value_from_svn_info(cls, path, field_name): svn_info_args = ['svn', 'info', path] info_output = run_command(svn_info_args).rstrip() match = re.search("^%s: (?P<value>.+)$" % field_name, info_output, re.MULTILINE) if not match: raise ScriptError(script_args=svn_info_args, message='svn info did not contain a %s.' % field_name) return match.group('value')
def git_commit_from_svn_revision(cls, revision): # FIXME: This should probably use cwd=self.checkout_root git_commit = run_command(['git', 'svn', 'find-rev', 'r%s' % revision]).rstrip() # git svn find-rev always exits 0, even when the revision is not found. if not git_commit: raise ScriptError(message='Failed to find git commit for revision %s, your checkout likely needs an update.' % revision) return git_commit
def _get_repo_type(self): """Get the repository type that client is using.""" return_code = run_command(['svn', 'info'], return_exit_code=True) if return_code == 0: return REPO_SVN return REPO_UNKNOWN
def test_run_command_with_pipe(self): input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) self.assertEqual(run_command(['grep', 'bar'], input=input_process.stdout), "bar\n") # Test the non-pipe case too: self.assertEqual(run_command(['grep', 'bar'], input="foo\nbar"), "bar\n") command_returns_non_zero = ['/bin/sh', '--invalid-option'] # Test when the input pipe process fails. input_process = subprocess.Popen(command_returns_non_zero, stdout=subprocess.PIPE, stderr=self.dev_null) self.assertTrue(input_process.poll() != 0) self.assertRaises(ScriptError, run_command, ['grep', 'bar'], input=input_process.stdout) # Test when the run_command process fails. input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) # grep shows usage and calls exit(2) when called w/o arguments. self.assertRaises(ScriptError, run_command, command_returns_non_zero, input=input_process.stdout)
def find_checkout_root(cls, path): # "git rev-parse --show-cdup" would be another way to get to the root (checkout_root, dot_git) = os.path.split(run_command(['git', 'rev-parse', '--git-dir'], cwd=(path or "./"))) # If we were using 2.6 # checkout_root = os.path.relpath(checkout_root, path) if not os.path.isabs(checkout_root): # Sometimes git returns relative paths checkout_root = os.path.join(path, checkout_root) return checkout_root
def value_from_svn_info(cls, path, field_name): svn_info_args = ['svn', 'info', path] info_output = run_command(svn_info_args).rstrip() match = re.search("^%s: (?P<value>.+)$" % field_name, info_output, re.MULTILINE) if not match: raise ScriptError(script_args=svn_info_args, message='svn info did not contain a %s.' % field_name) return match.group('value')
def read_git_config(cls, key, cwd=None): # FIXME: This should probably use cwd=self.checkout_root. # Pass --get-all for cases where the config has multiple values # Pass the cwd if provided so that we can handle the case of running webkit-patch outside of the working directory. return run_command(["git", "config", "--get-all", key], error_handler=Executive.ignore_error, cwd=cwd).rstrip( "\n" )
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 _get_repo_type(self): """Get the repository type that client is using.""" return_code = run_command(['svn', 'info'], return_exit_code=True) if return_code == 0: return REPO_SVN return REPO_UNKNOWN
def has_authorization_for_realm(self, realm=svn_server_realm, home_directory=os.getenv("HOME")): # Assumes find and grep are installed. if not os.path.isdir(os.path.join(home_directory, ".subversion")): return False find_args = ["find", ".subversion", "-type", "f", "-exec", "grep", "-q", realm, "{}", ";", "-print"]; find_output = run_command(find_args, cwd=home_directory, error_handler=Executive.ignore_error).rstrip() return find_output and os.path.isfile(os.path.join(home_directory, find_output))
def in_working_directory(cls, path): return ( run_command( ["git", "rev-parse", "--is-inside-work-tree"], cwd=path, error_handler=Executive.ignore_error ).rstrip() == "true" )
def git_commit_from_svn_revision(cls, revision): # FIXME: This should probably use cwd=self.checkout_root git_commit = run_command(['git', 'svn', 'find-rev', 'r%s' % revision]).rstrip() # git svn find-rev always exits 0, even when the revision is not found. if not git_commit: raise ScriptError(message='Failed to find git commit for revision %s, your checkout likely needs an update.' % revision) return git_commit
def test_error_handlers(self): git_failure_message = "Merge conflict during commit: Your file or directory 'WebCore/ChangeLog' is probably out-of-date: resource out of date; try updating at /usr/local/libexec/git-core//git-svn line 469" svn_failure_message = """svn: Commit failed (details follow): svn: File or directory 'ChangeLog' is out of date; try updating svn: resource out of date; try updating """ command_does_not_exist = ['does_not_exist', 'invalid_option'] self.assertRaises(OSError, run_command, command_does_not_exist) self.assertRaises(OSError, run_command, command_does_not_exist, error_handler=Executive.ignore_error) command_returns_non_zero = ['/bin/sh', '--invalid-option'] self.assertRaises(ScriptError, run_command, command_returns_non_zero) # Check if returns error text: self.assertTrue( run_command(command_returns_non_zero, error_handler=Executive.ignore_error)) self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=git_failure_message)) self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=svn_failure_message)) self.assertRaises(ScriptError, commit_error_handler, ScriptError(output='blah blah blah'))
def test_apply_git_patch(self): scm = detect_scm_system(self.git_checkout_path) # We carefullly pick a diff which does not have a directory addition # as currently svn-apply will error out when trying to remove directories # in Git: https://bugs.webkit.org/show_bug.cgi?id=34871 patch = self._create_patch(run_command(['git', 'diff', 'HEAD..HEAD^'])) self._setup_webkittools_scripts_symlink(scm) Checkout(scm).apply_patch(patch)
def clean_working_directory(self): # svn revert -R is not as awesome as git reset --hard. # It will leave added files around, causing later svn update # calls to fail on the bots. We make this mirror git reset --hard # by deleting any added files as well. added_files = reversed(sorted(self.added_files())) # added_files() returns directories for SVN, we walk the files in reverse path # length order so that we remove files before we try to remove the directories. run_command(["svn", "revert", "-R", "."], cwd=self.checkout_root) for path in added_files: # This is robust against cwd != self.checkout_root absolute_path = self.absolute_path(path) # Completely lame that there is no easy way to remove both types with one call. if os.path.isdir(path): os.rmdir(absolute_path) else: os.remove(absolute_path)
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
def push_local_commits_to_server(self): dcommit_command = ['git', 'svn', 'dcommit'] if self.dryrun: dcommit_command.append('--dry-run') output = run_command(dcommit_command, error_handler=commit_error_handler) # Return a string which looks like a commit so that things which parse this output will succeed. if self.dryrun: output += "\nCommitted r0" return output
def test_commit_with_message_squashed(self): self._one_local_commit_plus_working_copy_changes() scm = detect_scm_system(self.git_checkout_path) commit_text = scm.commit_with_message("yet another test commit", squash=True) self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) self.assertTrue(re.search(r'test_file_commit2', svn_log)) self.assertTrue(re.search(r'test_file_commit1', svn_log))
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 commit_message_for_local_commit(self, commit_id): commit_lines = run_command(['git', 'cat-file', 'commit', commit_id]).splitlines() # Skip the git headers. first_line_after_headers = 0 for line in commit_lines: first_line_after_headers += 1 if line == "": break return CommitMessage(commit_lines[first_line_after_headers:])