Skip to content

Commit b9598a2

Browse files
committed
feat(gitattributes): add GitAttributesCommand to manage export-ignore rules
Signed-off-by: Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
1 parent 500f85b commit b9598a2

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of fast-forward/dev-tools.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @copyright Copyright (c) 2026 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
12+
* @license https://opensource.org/licenses/MIT MIT License
13+
*
14+
* @see https://github.com/php-fast-forward/dev-tools
15+
* @see https://github.com/php-fast-forward
16+
* @see https://datatracker.ietf.org/doc/html/rfc2119
17+
*/
18+
19+
namespace FastForward\DevTools\Command;
20+
21+
use FastForward\DevTools\GitAttributes\CandidateProvider;
22+
use FastForward\DevTools\GitAttributes\CandidateProviderInterface;
23+
use FastForward\DevTools\GitAttributes\ExistenceChecker;
24+
use FastForward\DevTools\GitAttributes\Merger;
25+
use Symfony\Component\Console\Input\InputInterface;
26+
use Symfony\Component\Console\Output\OutputInterface;
27+
use Symfony\Component\Filesystem\Filesystem;
28+
use Symfony\Component\Filesystem\Path;
29+
30+
/**
31+
* Provides functionality to manage .gitattributes export-ignore rules.
32+
*
33+
* This command adds export-ignore entries for repository-only files and directories
34+
* to keep them out of Composer package archives.
35+
*/
36+
final class GitAttributesCommand extends AbstractCommand
37+
{
38+
/**
39+
* Creates a new GitAttributesCommand instance.
40+
*
41+
* @param Filesystem|null $filesystem the filesystem component
42+
* @param CandidateProviderInterface|null $candidateProvider the candidate provider
43+
*/
44+
public function __construct(
45+
?Filesystem $filesystem = null,
46+
private readonly ?CandidateProviderInterface $candidateProvider = new CandidateProvider()
47+
) {
48+
parent::__construct($filesystem);
49+
}
50+
51+
/**
52+
* Configures the current command.
53+
*
54+
* This method MUST define the name, description, and help text for the command.
55+
*
56+
* @param InputInterface $input
57+
* @param OutputInterface $output
58+
*/
59+
protected function execute(InputInterface $input, OutputInterface $output): int
60+
{
61+
$output->writeln('<info>Synchronizing .gitattributes export-ignore rules...</info>');
62+
63+
$basePath = $this->getCurrentWorkingDirectory();
64+
65+
/** @var ExistenceChecker $checker */
66+
$checker = new ExistenceChecker($basePath, $this->filesystem);
67+
68+
$existingFolders = $checker->filterExisting($this->candidateProvider->folders());
69+
$existingFiles = $checker->filterExisting($this->candidateProvider->files());
70+
71+
sort($existingFolders, \SORT_STRING);
72+
sort($existingFiles, \SORT_STRING);
73+
74+
$entries = [...$existingFolders, ...$existingFiles];
75+
76+
if ([] === $entries) {
77+
$output->writeln(
78+
'<comment>No candidate paths found in repository. Skipping .gitattributes sync.</comment>'
79+
);
80+
81+
return self::SUCCESS;
82+
}
83+
84+
$gitattributesPath = Path::join($basePath, '.gitattributes');
85+
$merger = new Merger($gitattributesPath);
86+
87+
$content = $merger->merge($entries);
88+
$merger->write($content);
89+
90+
$output->writeln(\sprintf(
91+
'<info>Added %d export-ignore entries to .gitattributes.</info>',
92+
\count($entries)
93+
));
94+
95+
return self::SUCCESS;
96+
}
97+
98+
/**
99+
* Configures the current command.
100+
*
101+
* This method MUST define the name, description, and help text for the command.
102+
*
103+
* @return void
104+
*/
105+
protected function configure(): void
106+
{
107+
$this
108+
->setName('gitattributes')
109+
->setDescription('Manages .gitattributes export-ignore rules for leaner package archives.')
110+
->setHelp(
111+
'This command adds export-ignore entries for repository-only files and directories '
112+
. 'to keep them out of Composer package archives. Only paths that exist in the '
113+
. 'repository are added, and existing custom rules are preserved.'
114+
);
115+
}
116+
}

0 commit comments

Comments
 (0)