diff --git a/git_drover.py b/git_drover.py index ad6d0e13c..c9a4af315 100755 --- a/git_drover.py +++ b/git_drover.py @@ -265,8 +265,9 @@ class _Drover(object): repo_status = self._run_git_command(['status', '--porcelain']).splitlines() extra_files = [f[3:] for f in repo_status if f[:2] == ' D'] if extra_files: - self._run_git_command(['update-index', '--skip-worktree', '--'] + - extra_files) + self._run_git_command_with_stdin( + ['update-index', '--skip-worktree', '--stdin'], + stdin='\n'.join(extra_files) + '\n') def _upload_and_land(self): if self._dry_run: @@ -314,6 +315,32 @@ class _Drover(object): else: raise Error('Command %r failed: %s' % (' '.join(args), e)) + def _run_git_command_with_stdin(self, args, stdin): + """Runs a git command with a provided stdin. + + Args: + args: A list of strings containing the args to pass to git. + stdin: A string to provide on stdin. + + Raises: + Error: The command failed to complete successfully. + """ + cwd = self._workdir if self._workdir else self._parent_repo + logging.debug('Running git %s (cwd %r)', ' '.join('%s' % arg + for arg in args), cwd) + + # Discard stderr unless verbose is enabled. + stderr = None if self._verbose else _DEV_NULL_FILE + + try: + popen = subprocess.Popen(['git'] + args, shell=False, cwd=cwd, + stderr=stderr, stdin=subprocess.PIPE) + popen.communicate(stdin) + if popen.returncode != 0: + raise Error('Command %r failed' % ' '.join(args)) + except OSError as e: + raise Error('Command %r failed: %s' % (' '.join(args), e)) + def cherry_pick_change(branch, revision, parent_repo, dry_run, verbose=False): """Cherry-picks a change into a branch. diff --git a/tests/git_drover_test.py b/tests/git_drover_test.py index 98f9352ed..ea38df600 100755 --- a/tests/git_drover_test.py +++ b/tests/git_drover_test.py @@ -37,6 +37,8 @@ class GitDroverTest(auto_stub.TestCase): self.mock(__builtins__, 'raw_input', self._get_input) self.mock(subprocess, 'check_call', self._check_call) self.mock(subprocess, 'check_output', self._check_call) + self.real_popen = subprocess.Popen + self.mock(subprocess, 'Popen', self._Popen) self._commands = [] self._input = [] self._fail_on_command = None @@ -75,7 +77,7 @@ class GitDroverTest(auto_stub.TestCase): ] self.MANUAL_RESOLVE_PREPARATION_COMMANDS = [ (['git', 'status', '--porcelain'], self._target_repo), - (['git', 'update-index', '--skip-worktree', '--', 'foo', 'bar'], + (['git', 'update-index', '--skip-worktree', '--stdin'], self._target_repo), ] self.FINISH_MANUAL_RESOLVE_COMMANDS = [ @@ -113,6 +115,25 @@ class GitDroverTest(auto_stub.TestCase): return ' D foo\nUU baz\n D bar\n' return '' + def _Popen(self, args, shell=False, cwd=None, stdin=None, stdout=None, + stderr=None): + if args == ['git', 'update-index', '--skip-worktree', '--stdin']: + self._commands.append((args, cwd)) + self.assertFalse(shell) + self.assertEqual(stdin, subprocess.PIPE) + class MockPopen(object): + def __init__(self, *args, **kwargs): + self.returncode = -999 + def communicate(self, stdin): + if stdin == 'foo\nbar\n': + self.returncode = 0 + else: + self.returncode = 1 + return MockPopen() + else: + return self.real_popen(args, shell=shell, cwd=cwd, stdin=stdin, + stdout=stdout, stderr=stderr) + def testSuccess(self): self._input = ['y', 'y'] git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)