Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php
12242 views
1
<?php
2
3
/**
4
* Populate a @{class:DiffusionCommitRef} with information about a specific
5
* commit in a repository. This is a low-level query which talks directly to
6
* the underlying VCS.
7
*/
8
final class DiffusionLowLevelCommitQuery
9
extends DiffusionLowLevelQuery {
10
11
private $identifier;
12
13
public function withIdentifier($identifier) {
14
$this->identifier = $identifier;
15
return $this;
16
}
17
18
protected function executeQuery() {
19
if (!strlen($this->identifier)) {
20
throw new PhutilInvalidStateException('withIdentifier');
21
}
22
23
$type = $this->getRepository()->getVersionControlSystem();
24
switch ($type) {
25
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
26
$result = $this->loadGitCommitRef();
27
break;
28
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
29
$result = $this->loadMercurialCommitRef();
30
break;
31
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
32
$result = $this->loadSubversionCommitRef();
33
break;
34
default:
35
throw new Exception(pht('Unsupported repository type "%s"!', $type));
36
}
37
38
return $result;
39
}
40
41
private function loadGitCommitRef() {
42
$repository = $this->getRepository();
43
44
// See T5028. The "%B" (raw body) mode is not present in very old versions
45
// of Git. Use "%s" and "%b" ("subject" and "wrapped body") as an
46
// approximation.
47
48
$git_binary = PhutilBinaryAnalyzer::getForBinary('git');
49
$git_version = $git_binary->getBinaryVersion();
50
if (version_compare($git_version, '1.7.2', '>=')) {
51
$body_format = '%B';
52
$split_body = false;
53
} else {
54
$body_format = '%s%x00%b';
55
$split_body = true;
56
}
57
58
$argv = array();
59
60
$argv[] = '-n';
61
$argv[] = '1';
62
63
$argv[] = '--encoding=UTF-8';
64
65
$argv[] = sprintf(
66
'--format=%s',
67
implode(
68
'%x00',
69
array(
70
'%e',
71
'%cn',
72
'%ce',
73
'%an',
74
'%ae',
75
'%T',
76
'%at',
77
$body_format,
78
79
// The "git log" output includes a trailing newline. We want to
80
// faithfully capture only the exact text of the commit message,
81
// so include an explicit terminator: this makes sure the exact
82
// body text is surrounded by "\0" characters.
83
'~',
84
)));
85
86
// Even though we pass --encoding here, git doesn't always succeed, so
87
// we try a little harder, since git *does* tell us what the actual encoding
88
// is correctly (unless it doesn't; encoding is sometimes empty).
89
list($info) = $repository->execxLocalCommand(
90
'log -n 1 %Ls %s --',
91
$argv,
92
gitsprintf('%s', $this->identifier));
93
94
$parts = explode("\0", $info);
95
$encoding = array_shift($parts);
96
97
foreach ($parts as $key => $part) {
98
if ($encoding) {
99
$part = phutil_utf8_convert($part, 'UTF-8', $encoding);
100
}
101
$parts[$key] = phutil_utf8ize($part);
102
if (!strlen($parts[$key])) {
103
$parts[$key] = null;
104
}
105
}
106
107
$hashes = array(
108
id(new DiffusionCommitHash())
109
->setHashType(ArcanistDifferentialRevisionHash::HASH_GIT_COMMIT)
110
->setHashValue($this->identifier),
111
id(new DiffusionCommitHash())
112
->setHashType(ArcanistDifferentialRevisionHash::HASH_GIT_TREE)
113
->setHashValue($parts[4]),
114
);
115
116
$author_epoch = (int)$parts[5];
117
if (!$author_epoch) {
118
$author_epoch = null;
119
}
120
121
if ($split_body) {
122
// Here, the body is: "subject", "\0", "wrapped body". Stitch the
123
// pieces back together by putting a newline between them if both
124
// parts are nonempty.
125
126
$head = $parts[6];
127
$tail = $parts[7];
128
129
if (strlen($head) && strlen($tail)) {
130
$body = $head."\n\n".$tail;
131
} else if (strlen($head)) {
132
$body = $head;
133
} else if (strlen($tail)) {
134
$body = $tail;
135
} else {
136
$body = '';
137
}
138
} else {
139
// Here, the body is the raw unwrapped body.
140
$body = $parts[6];
141
}
142
143
return id(new DiffusionCommitRef())
144
->setCommitterName($parts[0])
145
->setCommitterEmail($parts[1])
146
->setAuthorName($parts[2])
147
->setAuthorEmail($parts[3])
148
->setHashes($hashes)
149
->setAuthorEpoch($author_epoch)
150
->setMessage($body);
151
}
152
153
private function loadMercurialCommitRef() {
154
$repository = $this->getRepository();
155
156
list($stdout) = $repository->execxLocalCommand(
157
'log --template %s --rev %s',
158
'{author}\\n{desc}',
159
hgsprintf('%s', $this->identifier));
160
161
list($author, $message) = explode("\n", $stdout, 2);
162
163
$author = phutil_utf8ize($author);
164
$message = phutil_utf8ize($message);
165
166
list($author_name, $author_email) = $this->splitUserIdentifier($author);
167
168
$hashes = array(
169
id(new DiffusionCommitHash())
170
->setHashType(ArcanistDifferentialRevisionHash::HASH_MERCURIAL_COMMIT)
171
->setHashValue($this->identifier),
172
);
173
174
return id(new DiffusionCommitRef())
175
->setAuthorName($author_name)
176
->setAuthorEmail($author_email)
177
->setMessage($message)
178
->setHashes($hashes);
179
}
180
181
private function loadSubversionCommitRef() {
182
$repository = $this->getRepository();
183
184
list($xml) = $repository->execxRemoteCommand(
185
'log --xml --limit 1 %s',
186
$repository->getSubversionPathURI(null, $this->identifier));
187
188
// Subversion may send us back commit messages which won't parse because
189
// they have non UTF-8 garbage in them. Slam them into valid UTF-8.
190
$xml = phutil_utf8ize($xml);
191
$log = new SimpleXMLElement($xml);
192
$entry = $log->logentry[0];
193
194
$author = (string)$entry->author;
195
$message = (string)$entry->msg;
196
197
list($author_name, $author_email) = $this->splitUserIdentifier($author);
198
199
// No hashes in Subversion.
200
$hashes = array();
201
202
return id(new DiffusionCommitRef())
203
->setAuthorName($author_name)
204
->setAuthorEmail($author_email)
205
->setMessage($message)
206
->setHashes($hashes);
207
}
208
209
private function splitUserIdentifier($user) {
210
$email = new PhutilEmailAddress($user);
211
212
if ($email->getDisplayName() || $email->getDomainName()) {
213
$user_name = $email->getDisplayName();
214
$user_email = $email->getAddress();
215
} else {
216
$user_name = $email->getAddress();
217
$user_email = null;
218
}
219
220
return array($user_name, $user_email);
221
}
222
223
}
224
225