Skip to content

Commit a476232

Browse files
Lukáš Křížclaude
andcommitted
feat: extend existing commands with missing flags and options
- log: --oneline (short hash + first line), --all (walk all refs) - diff: <commit>..<commit> (compare two commits), --stat (diffstat summary), --name-only (just file paths) - status: -s/--short (XY path compact format) - show: --stat and --name-only for commit objects - branch: -m (rename with config migration), -a (list local + remote), --set-upstream-to (set tracking branch) - checkout: -- <file> (restore file from HEAD) 15 new functional tests (260 total, 754 assertions). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 14f1e7d commit a476232

10 files changed

Lines changed: 884 additions & 77 deletions

File tree

src/Application/Handler/BranchHandler.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,63 @@ public function list(): array
3939
return $this->repository->refs->listRefs('refs/heads/');
4040
}
4141

42+
public function rename(string $oldName, string $newName): void
43+
{
44+
$oldRef = RefName::branch($oldName);
45+
$newRef = RefName::branch($newName);
46+
47+
if (! $this->repository->refs->exists($oldRef)) {
48+
throw new PureGitException(sprintf('Branch not found: %s', $oldName));
49+
}
50+
51+
if ($this->repository->refs->exists($newRef)) {
52+
throw new PureGitException(sprintf('Branch already exists: %s', $newName));
53+
}
54+
55+
$id = $this->repository->refs->resolve($oldRef);
56+
$this->repository->refs->updateRef($newRef, $id);
57+
$this->repository->refs->deleteRef($oldRef);
58+
59+
// Update HEAD if renaming the current branch
60+
$currentBranch = $this->getCurrentBranch();
61+
if ($currentBranch instanceof RefName && $currentBranch->equals($oldRef)) {
62+
$this->repository->refs->updateSymbolicRef(RefName::head(), $newRef);
63+
}
64+
65+
// Migrate tracking config
66+
$this->migrateTrackingConfig($oldName, $newName);
67+
}
68+
69+
/**
70+
* @return array<string, ObjectId>
71+
*/
72+
public function listRemote(): array
73+
{
74+
return $this->repository->refs->listRefs('refs/remotes/');
75+
}
76+
77+
public function setUpstreamTo(string $upstream, ?string $branchName = null): void
78+
{
79+
if ($branchName === null) {
80+
$current = $this->getCurrentBranch();
81+
if (! $current instanceof RefName) {
82+
throw new PureGitException('HEAD is not on a branch');
83+
}
84+
$branchName = $current->shortName();
85+
}
86+
87+
if (! str_contains($upstream, '/')) {
88+
throw new PureGitException(sprintf('Not a valid upstream: %s', $upstream));
89+
}
90+
91+
$parts = explode('/', $upstream, 2);
92+
$configPath = $this->repository->gitDir . '/config';
93+
$section = 'branch "' . $branchName . '"';
94+
$writer = new GitConfigWriter();
95+
$writer->set($configPath, $section, 'remote', $parts[0]);
96+
$writer->set($configPath, $section, 'merge', 'refs/heads/' . $parts[1]);
97+
}
98+
4299
public function delete(string $name): void
43100
{
44101
$ref = RefName::branch($name);
@@ -159,4 +216,24 @@ private function collectAncestors(ObjectId $startId): array
159216

160217
return $visited;
161218
}
219+
220+
private function migrateTrackingConfig(string $oldName, string $newName): void
221+
{
222+
$configPath = $this->repository->gitDir . '/config';
223+
$config = new GitConfigReader($configPath);
224+
$oldSection = 'branch "' . $oldName . '"';
225+
$remote = $config->get($oldSection, 'remote');
226+
$merge = $config->get($oldSection, 'merge');
227+
228+
if ($remote === null || $merge === null) {
229+
return;
230+
}
231+
232+
$writer = new GitConfigWriter();
233+
$newSection = 'branch "' . $newName . '"';
234+
$writer->set($configPath, $newSection, 'remote', $remote);
235+
$writer->set($configPath, $newSection, 'merge', $merge);
236+
$writer->unsetKey($configPath, $oldSection, 'remote');
237+
$writer->unsetKey($configPath, $oldSection, 'merge');
238+
}
162239
}

src/Application/Handler/LogHandler.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ public function __construct(
1919
/**
2020
* @return list<Commit>
2121
*/
22-
public function handle(int $maxCount = 20, ?string $fromRef = null): array
22+
public function handle(int $maxCount = 20, ?string $fromRef = null, bool $all = false): array
2323
{
24-
$ref = $fromRef !== null ? RefName::fromString($fromRef) : RefName::head();
25-
$commitId = $this->repository->refs->resolve($ref);
24+
$startIds = $all ? $this->allRefIds() : [$this->repository->refs->resolve(
25+
$fromRef !== null ? RefName::fromString($fromRef) : RefName::head(),
26+
)];
2627

2728
$commits = [];
2829
$seen = [];
29-
$queue = [$commitId];
30+
$queue = $startIds;
3031

3132
while ($queue !== [] && count($commits) < $maxCount) {
3233
$id = array_shift($queue);
@@ -44,9 +45,30 @@ public function handle(int $maxCount = 20, ?string $fromRef = null): array
4445
$this->enqueueUnseen($commit->parents, $seen, $queue);
4546
}
4647

48+
usort($commits, static fn (Commit $a, Commit $b): int => $b->author->timestamp->getTimestamp() - $a->author->timestamp->getTimestamp());
49+
4750
return $commits;
4851
}
4952

53+
/**
54+
* @return list<ObjectId>
55+
*/
56+
private function allRefIds(): array
57+
{
58+
$allRefs = $this->repository->refs->listRefs('refs/');
59+
$ids = [];
60+
$seen = [];
61+
62+
foreach ($allRefs as $id) {
63+
if (! isset($seen[$id->hash])) {
64+
$seen[$id->hash] = true;
65+
$ids[] = $id;
66+
}
67+
}
68+
69+
return $ids;
70+
}
71+
5072
private function readCommit(ObjectId $id): ?Commit
5173
{
5274
$object = $this->repository->objects->read($id);

src/CLI/Command/BranchCommand.php

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function description(): string
2121

2222
public function usage(): string
2323
{
24-
return 'branch [<name>] [-d <name>] [--unset-upstream [<name>]]';
24+
return 'branch [<name>] [-d <name>] [-m <old> <new>] [-a] [--set-upstream-to=<upstream>] [--unset-upstream [<name>]]';
2525
}
2626

2727
/**
@@ -39,43 +39,61 @@ public function execute(array $args): int
3939
$repo = Repository::discover($cwd);
4040
$handler = new BranchHandler($repo);
4141

42-
if ($this->isUnsetUpstreamRequest($args)) {
43-
return $this->unsetUpstream($handler, $args[1] ?? null);
44-
}
45-
46-
if ($this->isDeleteRequest($args)) {
47-
return $this->deleteBranch($handler, $args[1]);
48-
}
49-
50-
if ($this->isCreateRequest($args)) {
51-
return $this->createBranch($handler, $args[0]);
52-
}
53-
54-
return $this->listBranches($handler);
42+
return $this->dispatch($handler, $args);
5543
}
5644

5745
/**
5846
* @param list<string> $args
5947
*/
60-
private function isUnsetUpstreamRequest(array $args): bool
48+
private function dispatch(BranchHandler $handler, array $args): int
6149
{
62-
return isset($args[0]) && $args[0] === '--unset-upstream';
50+
if ($args === []) {
51+
return $this->listBranches($handler, false);
52+
}
53+
54+
return match ($args[0]) {
55+
'--unset-upstream' => $this->unsetUpstream($handler, $args[1] ?? null),
56+
'-d' => isset($args[1]) ? $this->deleteBranch($handler, $args[1]) : 1,
57+
'-m' => isset($args[1], $args[2]) ? $this->renameBranch($handler, $args[1], $args[2]) : 1,
58+
'-a' => $this->listBranches($handler, true),
59+
default => $this->handleDefault($handler, $args),
60+
};
6361
}
6462

6563
/**
6664
* @param list<string> $args
6765
*/
68-
private function isDeleteRequest(array $args): bool
66+
private function handleDefault(BranchHandler $handler, array $args): int
6967
{
70-
return isset($args[0]) && $args[0] === '-d' && isset($args[1]);
68+
$setUpstream = $this->extractSetUpstreamTo($args);
69+
if ($setUpstream !== null) {
70+
$handler->setUpstreamTo($setUpstream);
71+
$current = $handler->getCurrentBranch();
72+
$branchName = $current instanceof \Lukasojd\PureGit\Domain\Ref\RefName ? $current->shortName() : 'HEAD';
73+
fwrite(STDOUT, sprintf("branch '%s' set up to track '%s'.\n", $branchName, $setUpstream));
74+
75+
return 0;
76+
}
77+
78+
if (isset($args[0]) && $args[0] !== '' && $args[0][0] !== '-') {
79+
return $this->createBranch($handler, $args[0]);
80+
}
81+
82+
return $this->listBranches($handler, false);
7183
}
7284

7385
/**
7486
* @param list<string> $args
7587
*/
76-
private function isCreateRequest(array $args): bool
88+
private function extractSetUpstreamTo(array $args): ?string
7789
{
78-
return isset($args[0]) && $args[0] !== '' && $args[0][0] !== '-';
90+
foreach ($args as $arg) {
91+
if (str_starts_with($arg, '--set-upstream-to=')) {
92+
return substr($arg, strlen('--set-upstream-to='));
93+
}
94+
}
95+
96+
return null;
7997
}
8098

8199
private function unsetUpstream(BranchHandler $handler, ?string $name): int
@@ -93,6 +111,14 @@ private function deleteBranch(BranchHandler $handler, string $name): int
93111
return 0;
94112
}
95113

114+
private function renameBranch(BranchHandler $handler, string $oldName, string $newName): int
115+
{
116+
$handler->rename($oldName, $newName);
117+
fwrite(STDOUT, sprintf("Branch '%s' renamed to '%s'\n", $oldName, $newName));
118+
119+
return 0;
120+
}
121+
96122
private function createBranch(BranchHandler $handler, string $name): int
97123
{
98124
$handler->create($name);
@@ -101,7 +127,7 @@ private function createBranch(BranchHandler $handler, string $name): int
101127
return 0;
102128
}
103129

104-
private function listBranches(BranchHandler $handler): int
130+
private function listBranches(BranchHandler $handler, bool $all): int
105131
{
106132
$branches = $handler->list();
107133
$currentBranch = $handler->getCurrentBranch();
@@ -113,6 +139,14 @@ private function listBranches(BranchHandler $handler): int
113139
fwrite(STDOUT, sprintf("%s%s\n", $prefix, $short));
114140
}
115141

142+
if ($all) {
143+
$remotes = $handler->listRemote();
144+
foreach (array_keys($remotes) as $refName) {
145+
$short = str_replace('refs/', '', $refName);
146+
fwrite(STDOUT, sprintf(" %s\n", $short));
147+
}
148+
}
149+
116150
return 0;
117151
}
118152
}

src/CLI/Command/CheckoutCommand.php

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function description(): string
2323

2424
public function usage(): string
2525
{
26-
return 'checkout [-b <new-branch>] <branch|commit>';
26+
return 'checkout [-b <new-branch>] [-- <file>...] <branch|commit>';
2727
}
2828

2929
/**
@@ -47,23 +47,59 @@ public function execute(array $args): int
4747
$repo = Repository::discover($cwd);
4848
$handler = new CheckoutHandler($repo);
4949

50+
return $this->dispatch($handler, $repo, $args);
51+
}
52+
53+
/**
54+
* @param list<string> $args
55+
*/
56+
private function dispatch(CheckoutHandler $handler, Repository $repo, array $args): int
57+
{
58+
if ($this->tryRestoreFiles($handler, $args)) {
59+
return 0;
60+
}
61+
5062
if ($args[0] === '-b') {
51-
if (! isset($args[1])) {
52-
fwrite(STDERR, "error: switch 'b' requires a value\n");
63+
return $this->handleNewBranch($handler, $repo, $args);
64+
}
5365

54-
return 1;
55-
}
66+
$result = $handler->checkout($args[0]);
67+
$this->printResult($result, $args[0], $repo);
5668

57-
$startPoint = $args[2] ?? null;
58-
$result = $handler->checkoutNewBranch($args[1], $startPoint);
59-
$this->printResult($result, $args[1], $repo);
69+
return 0;
70+
}
6071

61-
return 0;
72+
/**
73+
* @param list<string> $args
74+
*/
75+
private function tryRestoreFiles(CheckoutHandler $handler, array $args): bool
76+
{
77+
$dashDash = array_search('--', $args, true);
78+
if ($dashDash === false || ! isset($args[$dashDash + 1])) {
79+
return false;
6280
}
6381

64-
$result = $handler->checkout($args[0]);
82+
for ($i = $dashDash + 1, $iMax = count($args); $i < $iMax; $i++) {
83+
$handler->restoreFile($args[$i]);
84+
}
6585

66-
$this->printResult($result, $args[0], $repo);
86+
return true;
87+
}
88+
89+
/**
90+
* @param list<string> $args
91+
*/
92+
private function handleNewBranch(CheckoutHandler $handler, Repository $repo, array $args): int
93+
{
94+
if (! isset($args[1])) {
95+
fwrite(STDERR, "error: switch 'b' requires a value\n");
96+
97+
return 1;
98+
}
99+
100+
$startPoint = $args[2] ?? null;
101+
$result = $handler->checkoutNewBranch($args[1], $startPoint);
102+
$this->printResult($result, $args[1], $repo);
67103

68104
return 0;
69105
}

0 commit comments

Comments
 (0)