Skip to content

Commit b5cffd7

Browse files
authored
Add Repository::runProcess() to expose stderr on successful commands (#247)
Repository::run() only ever returns stdout, even on success. Some git commands (push, in particular) write their meaningful output to stderr even when they succeed, so callers had no way to read it (#205). runProcess() returns the full, already-run Process instead, giving access to getErrorOutput(). run() now delegates to it and keeps its exact original behavior.
1 parent 9bdf6bd commit b5cffd7

3 files changed

Lines changed: 54 additions & 6 deletions

File tree

phpstan-baseline.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ parameters:
246246
count: 1
247247
path: src/Gitonomy/Git/Repository.php
248248

249+
-
250+
message: '#^Method Gitonomy\\Git\\Repository\:\:runProcess\(\) has parameter \$args with no value type specified in iterable type array\.$#'
251+
identifier: missingType.iterableValue
252+
count: 1
253+
path: src/Gitonomy/Git/Repository.php
254+
249255
-
250256
message: '#^Method Gitonomy\\Git\\Repository\:\:shell\(\) has parameter \$env with no value type specified in iterable type array\.$#'
251257
identifier: missingType.iterableValue

src/Gitonomy/Git/Repository.php

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,11 @@ public function setDescription(string $description): static
470470
* This command is a facility command. You can run any command
471471
* directly on git repository.
472472
*
473+
* Note that this only returns the standard output of the process. Some
474+
* git commands (`push`, for instance) write their meaningful output to
475+
* stderr even when they succeed: use {@see runProcess()} instead if you
476+
* need access to it.
477+
*
473478
* @param string $command Git command to run (checkout, branch, tag)
474479
* @param array $args Arguments of git command
475480
*
@@ -478,6 +483,26 @@ public function setDescription(string $description): static
478483
* @throws RuntimeException Error while executing git command (debug-mode only)
479484
*/
480485
public function run(string $command, array $args = []): ?string
486+
{
487+
$process = $this->runProcess($command, $args);
488+
489+
return $process->isSuccessful() ? $process->getOutput() : null;
490+
}
491+
492+
/**
493+
* Same as {@see run()}, but returns the full, already-run process
494+
* instead of only its standard output on success.
495+
*
496+
* This is useful for commands like `push`, which write their
497+
* meaningful output to stderr even when they succeed, so it can't be
498+
* read through run().
499+
*
500+
* @param string $command Git command to run (checkout, branch, tag)
501+
* @param array $args Arguments of git command
502+
*
503+
* @throws RuntimeException Error while executing git command (debug-mode only)
504+
*/
505+
public function runProcess(string $command, array $args = []): Process
481506
{
482507
$process = $this->getProcess($command, $args);
483508

@@ -488,13 +513,11 @@ public function run(string $command, array $args = []): ?string
488513

489514
$process->run();
490515

491-
$output = $process->getOutput();
492-
493516
if ($this->logger && $this->debug) {
494517
$duration = microtime(true) - $before;
495518
$this->logger->debug(\sprintf('last command (%s) duration: %sms', $command, \sprintf('%.2f', $duration * 1000)));
496519
$this->logger->debug(\sprintf('last command (%s) return code: %s', $command, $process->getExitCode()));
497-
$this->logger->debug(\sprintf('last command (%s) output: %s', $command, $output));
520+
$this->logger->debug(\sprintf('last command (%s) output: %s', $command, $process->getOutput()));
498521
}
499522

500523
if (!$process->isSuccessful()) {
@@ -507,11 +530,9 @@ public function run(string $command, array $args = []): ?string
507530
if ($this->debug) {
508531
throw new ProcessException($process);
509532
}
510-
511-
return null;
512533
}
513534

514-
return $output;
535+
return $process;
515536
}
516537

517538
/**

tests/Gitonomy/Git/Tests/RepositoryTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,27 @@ public function testRunReturnsNullInsteadOfThrowingWhenDebugIsFalse(): void
3030
$this->assertNull($repository->run('not-a-command'));
3131
}
3232

33+
public function testRunProcessGivesAccessToStderrOnSuccess(): void
34+
{
35+
$repository = self::createFoobarRepository(false);
36+
37+
// `git checkout` reports the switched branch on stderr, even on success.
38+
$process = $repository->runProcess('checkout', ['master']);
39+
40+
$this->assertTrue($process->isSuccessful());
41+
$this->assertStringContainsString('master', $process->getErrorOutput());
42+
}
43+
44+
public function testRunProcessReturnsFailedProcessInsteadOfThrowingWhenDebugIsFalse(): void
45+
{
46+
$repository = self::createFoobarRepository(true);
47+
$repository = new Repository($repository->getPath(), array_merge(self::getOptions(), ['debug' => false]));
48+
49+
$process = $repository->runProcess('not-a-command');
50+
51+
$this->assertFalse($process->isSuccessful());
52+
}
53+
3354
public function testGetShortHashThrowsCleanExceptionWhenDebugIsFalse(): void
3455
{
3556
$repository = self::createFoobarRepository(true);

0 commit comments

Comments
 (0)