diff --git a/src/Command/Checkout/RestoreWorkingTree.php b/src/Command/Checkout/RestoreWorkingTree.php new file mode 100644 index 0000000..d27b1ae --- /dev/null +++ b/src/Command/Checkout/RestoreWorkingTree.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianFeldmann\Git\Command\Checkout; + +use SebastianFeldmann\Git\Command\Base; + +/** + * Class RestoreWorkingTree + * + * @package SebastianFeldmann\Git + * @author Sebastian Feldmann + * @link https://github.com/sebastianfeldmann/git + * @since Class available since Release 3.7.0 + */ +class RestoreWorkingTree extends Base +{ + /** + * Files and directories to restore. + * + * @var string[] + */ + private $files = ['.']; + + /** + * Limits the paths affected by the operation to those specified here. + * + * @param array $files + * + * @return \SebastianFeldmann\Git\Command\Checkout\RestoreWorkingTree + */ + public function files(array $files): RestoreWorkingTree + { + $this->files = $files; + return $this; + } + + /** + * Return the command to execute. + * + * @return string + */ + protected function getGitCommand(): string + { + return 'checkout --quiet' + . ' -- ' + . implode(' ', array_map('escapeshellarg', $this->files)); + } +} diff --git a/src/Operator/Status.php b/src/Operator/Status.php index 129b3a7..6f78782 100644 --- a/src/Operator/Status.php +++ b/src/Operator/Status.php @@ -11,6 +11,7 @@ namespace SebastianFeldmann\Git\Operator; +use SebastianFeldmann\Git\Command\Checkout\RestoreWorkingTree; use SebastianFeldmann\Git\Command\Status\WorkingTreeStatus; use SebastianFeldmann\Git\Command\Status\Porcelain\PathList; @@ -37,4 +38,20 @@ public function getWorkingTreeStatus(): iterable return $result->getFormattedOutput(); } + + /** + * Performs a checkout (restore) operation on the given paths + * (or the entire repo, by default). + * + * @param string[] $limitToPaths + * @return bool + */ + public function restoreWorkingTree(array $limitToPaths = ['.']): bool + { + $cmd = (new RestoreWorkingTree($this->repo->getRoot()))->files($limitToPaths); + + $result = $this->runner->run($cmd); + + return $result->isSuccessful(); + } } diff --git a/tests/git/Command/Checkout/RestoreWorkingTreeTest.php b/tests/git/Command/Checkout/RestoreWorkingTreeTest.php new file mode 100644 index 0000000..1711364 --- /dev/null +++ b/tests/git/Command/Checkout/RestoreWorkingTreeTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianFeldmann\Git\Command\Checkout; + +use PHPUnit\Framework\TestCase; + +/** + * Class RestoreWorkingTreeTest + * + * @package SebastianFeldmann\Git + * @author Sebastian Feldmann + * @link https://github.com/sebastianfeldmann/git + * @since Class available since Release 3.7.0 + */ +class RestoreWorkingTreeTest extends TestCase +{ + public function testDefault(): void + { + $command = new RestoreWorkingTree(); + + $this->assertSame('git checkout --quiet -- \'.\'', $command->getCommand()); + } + + public function testFiles(): void + { + $command = new RestoreWorkingTree(); + $command = $command->files([ + "foo/*", + "foo bar.txt", + "foo' 'bar.txt", + "fooBar.txt", + ]); + + $this->assertSame( + "git checkout --quiet -- 'foo/*' 'foo bar.txt' 'foo'\'' '\''bar.txt' 'fooBar.txt'", + $command->getCommand() + ); + } +} diff --git a/tests/git/Operator/StatusTest.php b/tests/git/Operator/StatusTest.php index 7e624e0..8bc587b 100644 --- a/tests/git/Operator/StatusTest.php +++ b/tests/git/Operator/StatusTest.php @@ -13,6 +13,7 @@ use SebastianFeldmann\Cli\Command\Result as CommandResult; use SebastianFeldmann\Cli\Command\Runner\Result as RunnerResult; +use SebastianFeldmann\Git\Command\Checkout\RestoreWorkingTree; use SebastianFeldmann\Git\Command\Status\Porcelain\PathList; use SebastianFeldmann\Git\Command\Status\WorkingTreeStatus; use SebastianFeldmann\Git\Status\Path; @@ -57,4 +58,46 @@ public function testGetWorkingTreeStatus(): void $this->assertCount(2, $paths); $this->assertContainsOnlyInstancesOf(Path::class, $paths); } + + public function testRestoreWorkingTreeWithDefaultPathsParameter(): void + { + $root = (string) realpath(__FILE__ . '/../../..'); + + $repo = $this->getRepoMock(); + $runner = $this->getRunnerMock(); + $cmdRes = new CommandResult('git ...', 0); + $runRes = new RunnerResult($cmdRes, ['foobar']); + $gitCmd = new RestoreWorkingTree($root); + + $repo->method('getRoot')->willReturn($root); + $runner->expects($this->once()) + ->method('run') + ->with($this->equalTo($gitCmd)) + ->willReturn($runRes); + + $status = new Status($runner, $repo); + + $this->assertTrue($status->restoreWorkingTree()); + } + + public function testRestoreWorkingTreeWithPassedPathsAndErrorResponse(): void + { + $root = (string) realpath(__FILE__ . '/../../..'); + + $repo = $this->getRepoMock(); + $runner = $this->getRunnerMock(); + $cmdRes = new CommandResult('git ...', 1); + $runRes = new RunnerResult($cmdRes, ['foobar']); + $gitCmd = (new RestoreWorkingTree($root))->files(['foo', 'bar']); + + $repo->method('getRoot')->willReturn($root); + $runner->expects($this->once()) + ->method('run') + ->with($this->equalTo($gitCmd)) + ->willReturn($runRes); + + $status = new Status($runner, $repo); + + $this->assertFalse($status->restoreWorkingTree(['foo', 'bar'])); + } }