This repository has been archived by the owner on Jun 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 107
/
ShipItRepo.php
229 lines (205 loc) · 6.28 KB
/
ShipItRepo.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
<?hh
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* This file was moved from fbsource to www. View old history in diffusion:
* https://fburl.com/mxrtk0pl
*/
namespace Facebook\ShipIt;
use namespace HH\Lib\{C, Regex, Str}; // @oss-enable
class ShipItRepoException extends \Exception {
public function __construct(?IShipItRepo $repo, string $message) {
if ($repo !== null) {
$message = \get_class($repo).": $message";
}
parent::__construct($message);
}
}
/**
* Repo handler interface
* For agnostic communication with git, hg, etc...
*/
abstract class ShipItRepo implements ShipItSourceRepo, ShipItDestinationRepo {
/**
* @param $path the path to the repository
*/
public function __construct(
private IShipItLock $lock,
protected string $path,
) {}
protected function getSharedLock(): IShipItLock {
return $this->lock;
}
public function getPath(): string {
return $this->path;
}
final public static async function genTypedOpen<
<<__Enforceable>> reify Trepo as IShipItRepo,
>(IShipItLock $lock, string $path, string $branch): Awaitable<Trepo> {
$repo = await ShipItRepo::genOpen($lock, $path, $branch);
invariant(
$repo is Trepo,
'%s is a %s, needed a %s',
$path,
\get_class($repo),
Trepo::class,
);
return $repo as Trepo;
}
/**
* Factory
*/
final public static async function genOpen(
IShipItLock $lock,
string $path,
string $branch,
): Awaitable<IShipItRepo> {
if (PHP\file_exists($path.'/.git')) {
$repo = new ShipItRepoGIT($lock, $path);
await $repo->genSetBranch($branch);
return $repo;
}
if (PHP\file_exists($path.'/.hg')) {
$repo = new ShipItRepoHG($lock, $path);
await $repo->genSetBranch($branch);
return $repo;
}
throw
new ShipItRepoException(null, "Can't determine type of repo at ".$path);
}
/**
* Convert a hunk to a ShipItDiff shape
*/
public static function parseDiffHunk(string $hunk): ?ShipItDiff {
list($header, $body) = Str\split($hunk, "\n", 2);
$matches = Regex\first_match(
Str\trim($header),
re'@^diff --git ("?)[ab]/(.*?)"? ("?)[ab]/(.*?)"?$@',
);
if ($matches is null) {
return null;
}
$path = $matches[2] as string;
$new_path = $matches[4] !== '' ? $matches[4] : null;
if ($matches[1] === '"') {
// Quoted paths may contain escaped characters.
$path = PHP\stripslashes($path);
}
if ($new_path is nonnull && $path !== $new_path) {
if ($matches[3] === '"') {
$new_path = PHP\stripslashes($new_path);
}
return shape(
'path' => $path,
'body' => $body,
'operation' => ShipItDiffOperation::RENAME,
'new_path' => $new_path,
);
} else {
return shape(
'path' => $path,
'body' => $body,
'operation' => ShipItDiffOperation::CHANGE,
);
}
}
final public static function getCommitMessage(
ShipItChangeset $changeset,
): string {
return $changeset->getSubject()."\n\n".$changeset->getMessage();
}
final public static function fixInlinePatchesInCommitMessage(
string $message,
): string {
/* Insert a space before patterns that will make `git am` think that a
* line in the commit message is the start of a patch, which is an artifact
* of the way `git am` tries to tell where the message ends and the diffs
* begin. This fix is a hack; a better fix might be to use `git apply` and
* `git commit` directly instead of `git am`, but this is an edge-case so
* it's not worth it right now.
*
* https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/builtin/mailinfo.c#L701
*/
return
Regex\replace($message, re"/^(diff -|Index: |---(?:\s\S|\s*$))/m", ' $1');
}
protected bool $useNativeRenames = false;
final public function setUseNativeRenames(bool $use_native_renames): void {
$this->useNativeRenames = $use_native_renames;
}
/*
* Generator yielding patch sections of the diff blocks (individually)
* and finally the footer.
*/
public static function parsePatch(string $patch): Iterator<string> {
$contents = '';
$minus_lines = 0;
$plus_lines = 0;
$seen_range_header = false;
foreach (Str\split($patch, "\n") as $line) {
$line = Regex\replace($line, re"/(\r\n|\n)/", "\n");
if (
Regex\matches(
Str\trim_right($line),
re"@^diff --git \"?[ab]/(.*?)\"? \"?[ab]/(.*?)\"?$@",
)
) {
if ($contents !== '') {
yield $contents;
}
$seen_range_header = false;
$contents = $line."\n";
continue;
}
$matches = Regex\first_match(
$line,
re"/^@@ -\d+(,(?<minus_lines>\d+))? \+\d+(,(?<plus_lines>\d+))? @@/",
);
if ($matches !== null) {
$minus_lines = $matches['minus_lines'] ?? '';
$minus_lines = $minus_lines === '' ? 1 : (int)$minus_lines;
$plus_lines = $matches['plus_lines'] ?? '';
$plus_lines = $plus_lines === '' ? 1 : (int)$plus_lines;
$contents .= $line."\n";
$seen_range_header = true;
continue;
}
if (!$seen_range_header) {
$contents .= $line."\n";
continue;
}
$leftmost = Str\slice($line, 0, 1);
if ($leftmost === "\\") {
$contents .= $line."\n";
// Doesn't count as a + or - line whatever happens; if NL at EOF
// changes, there is a + and - for the last line of content
continue;
}
if ($minus_lines <= 0 && $plus_lines <= 0) {
continue;
}
$leftmost = Str\slice($line, 0, 1);
if ($leftmost === '+') {
--$plus_lines;
} else if ($leftmost === '-') {
--$minus_lines;
} else if ($leftmost === ' ') {
// Context goes from both.
--$plus_lines;
--$minus_lines;
} else {
invariant_violation("Can't parse hunk line: %s", $line);
}
$contents .= $line."\n";
}
if ($contents !== '') {
// If we got the patch from git-diff, there won't be the signature line
// from format-patch
yield $contents;
}
}
}