5
5
namespace Butschster \ContextGenerator \Lib \Git ;
6
6
7
7
use Butschster \ContextGenerator \Application \Logger \LoggerPrefix ;
8
+ use Butschster \ContextGenerator \DirectoriesInterface ;
8
9
use Butschster \ContextGenerator \Lib \Git \Exception \GitClientException ;
10
+ use Butschster \ContextGenerator \Lib \Git \Exception \GitCommandException ;
9
11
use Psr \Log \LoggerInterface ;
12
+ use Spiral \Files \Exception \FilesException ;
13
+ use Spiral \Files \FilesInterface ;
14
+ use Symfony \Component \Process \Process ;
10
15
11
- final class GitClient implements GitClientInterface
16
+ /**
17
+ * Executes git commands and handles their results.
18
+ */
19
+ final class CommandsExecutor implements CommandsExecutorInterface
12
20
{
13
21
/**
14
22
* Static cache of validated repositories
@@ -17,28 +25,37 @@ final class GitClient implements GitClientInterface
17
25
private static array $ validatedRepositories = [];
18
26
19
27
public function __construct (
20
- #[LoggerPrefix(prefix: 'git-client ' )]
28
+ private readonly FilesInterface $ files ,
29
+ private readonly DirectoriesInterface $ dirs ,
30
+ #[LoggerPrefix(prefix: 'git-commands-executor ' )]
21
31
private readonly ?LoggerInterface $ logger = null ,
22
32
) {}
23
33
34
+ /**
35
+ * Execute a Git command and return the output as an array of lines.
36
+ *
37
+ * @inheritDoc
38
+ */
24
39
public function execute (string $ repository , string $ command ): array
25
40
{
26
- $ this ->validateRepository ($ repository );
41
+ $ repoPath = (string ) $ this ->dirs ->getRootPath ()->join ($ repository );
42
+
43
+ $ this ->validateRepository ($ repoPath );
27
44
28
45
$ currentDir = \getcwd ();
29
46
$ output = [];
30
47
$ returnCode = 0 ;
31
48
32
49
try {
33
50
// Change to the repository directory
34
- \chdir ($ repository );
51
+ \chdir ($ repoPath );
35
52
36
53
// Add 'git' prefix to the command if not already present
37
54
$ fullCommand = $ this ->prepareCommand ($ command );
38
55
39
56
$ this ->logger ?->debug('Executing Git command ' , [
40
57
'command ' => $ fullCommand ,
41
- 'repository ' => $ repository ,
58
+ 'repository ' => $ repoPath ,
42
59
]);
43
60
44
61
// Execute the command with error output capture
@@ -69,12 +86,47 @@ public function execute(string $repository, string $command): array
69
86
}
70
87
}
71
88
89
+ /**
90
+ * Execute a Git command and return the output as a string.
91
+ *
92
+ * @inheritDoc
93
+ */
72
94
public function executeString (string $ repository , string $ command ): string
73
95
{
74
96
$ output = $ this ->execute ($ repository , $ command );
97
+
75
98
return \implode (PHP_EOL , $ output );
76
99
}
77
100
101
+ /**
102
+ * Execute a Git command using an array of command parts.
103
+ *
104
+ * @inheritDoc
105
+ */
106
+ public function executeCommand (array $ command ): string
107
+ {
108
+ // Prepend 'git' to the command array
109
+ $ gitCommand = ['git ' ];
110
+ $ fullCommand = \array_merge ($ gitCommand , $ command );
111
+
112
+ $ process = new Process ($ fullCommand , (string ) $ this ->dirs ->getRootPath ());
113
+ $ process ->run ();
114
+
115
+ if (!$ process ->isSuccessful ()) {
116
+ throw new GitCommandException (
117
+ \sprintf ('Git command failed: %s ' , $ process ->getErrorOutput ()),
118
+ $ process ->getExitCode (),
119
+ );
120
+ }
121
+
122
+ return $ process ->getOutput ();
123
+ }
124
+
125
+ /**
126
+ * Check if a directory is a valid Git repository.
127
+ *
128
+ * @inheritDoc
129
+ */
78
130
public function isValidRepository (string $ repository ): bool
79
131
{
80
132
// Return cached result if available
@@ -129,6 +181,76 @@ public function isValidRepository(string $repository): bool
129
181
}
130
182
}
131
183
184
+ /**
185
+ * Applies a git patch to a file.
186
+ *
187
+ * @inheritDoc
188
+ */
189
+ public function applyPatch (string $ filePath , string $ patchContent ): string
190
+ {
191
+ $ rootPath = $ this ->dirs ->getRootPath ();
192
+ $ file = $ rootPath ->join ($ filePath );
193
+
194
+ // Ensure the file exists
195
+ if (!$ file ->exists ()) {
196
+ throw new GitCommandException (\sprintf ('File "%s" does not exist ' , $ filePath ));
197
+ }
198
+
199
+ // Create a temporary file for the patch
200
+ try {
201
+ $ patchFile = $ this ->files ->tempFilename ();
202
+ } catch (FilesException $ e ) {
203
+ $ this ->logger ?->error('Failed to create temporary file for patch ' , [
204
+ 'error ' => $ e ->getMessage (),
205
+ ]);
206
+
207
+ throw new GitCommandException ('Failed to create temporary file for patch ' , 0 , $ e );
208
+ }
209
+
210
+ try {
211
+ // Write the patch content to a temporary file
212
+ $ this ->files ->write ($ patchFile , $ patchContent , FilesInterface::READONLY );
213
+
214
+ // Apply the patch using git apply command
215
+ $ process = new Process (
216
+ ['git ' , 'apply ' , '--whitespace=nowarn ' , $ patchFile ],
217
+ (string ) $ rootPath ,
218
+ );
219
+
220
+ $ process ->run ();
221
+
222
+ // Check if the command was successful
223
+ if (!$ process ->isSuccessful ()) {
224
+ throw new GitCommandException (
225
+ \sprintf ('Failed to apply patch: %s ' , $ process ->getErrorOutput ()),
226
+ $ process ->getExitCode (),
227
+ );
228
+ }
229
+
230
+ return \sprintf ('Successfully applied patch to %s ' , $ filePath );
231
+ } finally {
232
+ if ($ this ->files ->exists ($ patchFile )) {
233
+ $ this ->files ->delete ($ patchFile );
234
+ }
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Checks if the given directory is a git repository.
240
+ *
241
+ * @return bool True if the directory is a git repository, false otherwise
242
+ * @internal This method is kept for backward compatibility
243
+ */
244
+ public function isGitRepository (): bool
245
+ {
246
+ try {
247
+ $ this ->executeCommand (['rev-parse ' , '--is-inside-work-tree ' ]);
248
+ return true ;
249
+ } catch (GitCommandException ) {
250
+ return false ;
251
+ }
252
+ }
253
+
132
254
/**
133
255
* Validate that the repository directory exists and is a valid Git repository
134
256
* Uses cached results when available
@@ -138,13 +260,6 @@ public function isValidRepository(string $repository): bool
138
260
*/
139
261
private function validateRepository (string $ repository ): void
140
262
{
141
- if (!\is_dir ($ repository )) {
142
- $ this ->logger ?->error('Repository directory does not exist ' , [
143
- 'repository ' => $ repository ,
144
- ]);
145
- throw new \InvalidArgumentException (\sprintf ('Git repository "%s" does not exist ' , $ repository ));
146
- }
147
-
148
263
if (!$ this ->isValidRepository ($ repository )) {
149
264
$ this ->logger ?->error('Not a valid Git repository ' , [
150
265
'repository ' => $ repository ,
0 commit comments