-
Notifications
You must be signed in to change notification settings - Fork 0
/
commit-report
executable file
·398 lines (343 loc) · 14.4 KB
/
commit-report
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#!/usr/bin/php
<?php
/*******************************************************************************
*
* COMMIT REPORTER
*
* Created : 2019-09-02
* Modified : 2024-03-18
* Version : 0.5
*
* Purpose : Formats commit messages into short LaTeX notes to be included
* in the daily reports.
*
* Changelog : 0.5 2024-03-18
* Updated formatting of commit messages because the journal now
* uses a different format. Also fixed processing of some special
* characters; curly braces and tildes were ignored. Commit hashes
* are now shortened if they don't fit on the first line, and the
* wrapping of the commit lines is fixed. Variables, paths, and
* branch names are recognized in a better, more accurate way.
* 0.4 2021-03-01
* Fixed wrapping issues once and for all. Also fixed new problems
* that started occurring with wrapping when we started to adhere
* to better standards and our git editor started to wrap our
* commit messages. Also added some additional words to escape.
* 0.3 2019-09-25
* 0.3.3 2021-01-01
* Minor fixes in the formatting of commits in the email.
* 0.2 2019-09-12
* Script will now no longer truncate the commit log. Instead, it
* will simply append the new counter. Logically, it will now
* ignore commits listed before the last counter.
* 0.1 2019-09-02
* Initial release.
*
* Programmer : Ivo F.A.C. Fokkema <I.F.A.C.Fokkema@LUMC.nl>
*
*************/
// Command line only.
if (isset($_SERVER['HTTP_HOST'])) {
die('Please run this script through the command line.' . "\n");
}
// Default settings.
$_CONFIG = array(
'name' => 'Commit reporter',
'version' => '0.5',
);
// Exit codes.
// See http://tldp.org/LDP/abs/html/exitcodes.html for recommendations, in particular:
// "[I propose] restricting user-defined exit codes to the range 64 - 113 (...), to conform with the C/C++ standard."
define('EXIT_OK', 0);
define('EXIT_WARNINGS_OCCURRED', 64);
define('EXIT_ERROR_ARGS_INSUFFICIENT', 65);
define('EXIT_ERROR_ARGS_NOT_UNDERSTOOD', 66);
define('EXIT_ERROR_INPUT_NOT_A_FILE', 67);
define('EXIT_ERROR_INPUT_UNREADABLE', 68);
define('EXIT_ERROR_INPUT_CANT_OPEN', 69);
define('EXIT_ERROR_CONNECTION_PROBLEM', 76);
define('EXIT_ERROR_DATA_CONTENT_ERROR', 81);
// PHP's array_key_last exists from PHP 7.3.0.
if (!function_exists('array_key_last')) {
function array_key_last($a)
{
return array_keys($a)[count($a)-1];
}
}
// Parse command line options.
$aArgs = $_SERVER['argv'];
$nArgs = $_SERVER['argc'];
// We accept one argument; the location of the git log.
$nArgsRequired = 1;
$sScriptName = array_shift($aArgs);
$nArgs --;
$nWarningsOccurred = 0;
function usage ($sScriptName)
{
// Displays the usage information.
global $_CONFIG;
print(
$_CONFIG['name'] . ' v' . $_CONFIG['version'] . '.' . "\n" .
'Usage: ' . $sScriptName . ' /path/to/commits.log' . "\n\n");
}
if ($nArgs < $nArgsRequired) {
// Not enough arguments.
usage($sScriptName);
die(EXIT_ERROR_ARGS_INSUFFICIENT);
}
$sCommitLogPath = '';
if ($nArgs) {
$sCommitLogPath = array_shift($aArgs);
$nArgs --;
}
if ($nArgs) {
// More arguments sent.
usage($sScriptName);
die(EXIT_ERROR_ARGS_NOT_UNDERSTOOD);
}
// Check path.
if (!file_exists($sCommitLogPath) || !is_file($sCommitLogPath)) {
print(
'Given commit log path does not exist or is not a file.' . "\n\n");
die(EXIT_ERROR_INPUT_NOT_A_FILE);
}
if (!is_readable($sCommitLogPath) || !is_writable($sCommitLogPath)) {
print(
'Permission denied trying to access given commit log path.' . "\n\n");
die(EXIT_ERROR_INPUT_UNREADABLE);
}
// This script used to collect the commit messages itself.
// That is not useful either we'll run this each time for one commit (and we'll end up getting lots of emails),
// or we'll have to remember that we need to run it after a few commits.
// Now, commit messages are logged using a git hook, and we're collecting them once per day.
// Disabled this code completely, but leaving it for future reference.
// In case we'll ever do more fancy stuff, we might still want this around for something.
//// Find git root dir.
//$sRoot = './';
//while (!file_exists($sRoot . '.git')) {
// $sRoot = '../' . $sRoot;
// if (strlen($sRoot) > 100) {
// $sRoot = false;
// break;
// }
//}
//if (!$sRoot) {
// print(
// 'Unable to find git root directory.' . "\n\n");
// die(EXIT_ERROR_INPUT_CANT_OPEN);
//}
//// Get project name, and branch name.
//// NOTE: You can get some info as well through `git rev-parse --absolute-git-dir --show-toplevel`.
//$sProject = basename(
// preg_replace('/\/[^\/]+\/\.\.\//', '/', // Remove 'dir/../'.
// preg_replace('/\/\.\//', '/', // Remove './'.
// preg_replace('/\/+/', '/', // Remove double slashes.
// getcwd() . '/' . $sRoot))));
//// Current branch.
//$sBranch = exec('git symbolic-ref HEAD 2> /dev/null | cut -d / -f 3-');
//if (!$sBranch) {
// // Fatal error. Not a git repo?
// print(
// 'Unable to get branch information from git.' . "\n\n");
// die(EXIT_ERROR_INPUT_UNREADABLE);
//}
//// Get the commit text.
//@exec('git log -' . $nCommits . ' 2> /dev/null', $aOutput, $nReturn);
//if ($nReturn !== 0) {
// // Fatal error. Not a git repo?
// print(
// 'Unable to get commit information from git.' . "\n\n");
// die(EXIT_ERROR_INPUT_UNREADABLE);
//}
//if (!$aOutput) {
// // Still not. Bail out quietly.
// die(EXIT_ERROR_DATA_CONTENT_ERROR);
//}
// NOTE: All this locking seems rather useless, I couldn't get it to ever prevent altering the file.
// Open commit log, and lock it (as we'll write to it later).
$f = fopen($sCommitLogPath, 'a+');
$bLocked = false;
if ($f) {
// Try to lock it, and keep trying for 10 seconds or so.
$i = 0;
while (!$bLocked && $i < 10) {
// The |LOCK_NB part makes sure that the script doesn't freeze while waiting for the file lock.
if (flock($f, LOCK_EX|LOCK_NB)) {
// We got the lock!
$bLocked = true;
} else {
// Failed, something else locked the file. Wait.
$i ++;
sleep(1);
}
}
}
if (!$bLocked) {
// Either we couldn't open the file, or we couldn't lock it.
print(
'Unable to obtain lock on commits log.');
die(EXIT_ERROR_INPUT_CANT_OPEN);
}
// Loop through the commits, and store them.
$aCommits = array(); // Array where we'll store the parsed Commit data.
$sProject = $sBranch = $sCommitID = $sDate = '';
$d = $nCounter = 0;
rewind($f); // Set pointer to the beginning of the file for reading.
while ($sLine = fgets($f)) {
if (!trim($sLine)) {
// Ignore empty lines.
continue;
}
$sFirstWord = strstr($sLine, ' ', true);
switch ($sFirstWord) {
case 'Counter:':
// For fun, a counter that numbers commits.
$nCounter = trim(substr($sLine, strpos($sLine, ':')+1));
// Reset all variables.
$aCommits = array();
$sProject = $sBranch = $sCommitID = $sDate = '';
$d = 0;
break;
case 'Project:':
$sProject = trim(substr($sLine, strpos($sLine, ':')+1));
break;
case 'Branch:':
$sBranch = trim(substr($sLine, strpos($sLine, ':')+1));
break;
case 'commit':
$sCommitID = trim(strstr($sLine, ' '));
break;
case 'Date:':
// Parse date.
$d = strtotime(trim(strstr($sLine, ' ')));
$sDate = date('Y-m-d', $d);
break;
default:
// Commit body?
if ($sProject && $sBranch && $sCommitID && $d) {
// Group commits on date.
if (!isset($aCommits[$sDate])) {
$aCommits[$sDate] = array();
}
if (!isset($aCommits[$sDate][$d])) {
$aCommits[$sDate][$d] = array(
'project' => $sProject,
'branch' => $sBranch,
'id' => $sCommitID,
'body' => '',
);
}
// Trim the newline off, we need to see if there is a period (dot) there or not.
$sLine = trim($sLine);
// Our commits are now wrapped by our editor already, so many
// lines don't end with a period. Better check only for those
// lines that never have a period - the lines we don't write.
if (((!$aCommits[$sDate][$d]['body'] && substr(ltrim($sLine), 0, 6) == 'Merge ') || substr(ltrim($aCommits[$sDate][$d]['body']), 0, 6) == 'Merge ')
&& !in_array(substr($sLine, -1), array('.', '?', '!', ':', ';'))) {
$sLine .= ".\n";
} elseif (in_array(substr($sLine, -1), array('.', '?', '!', ':', ';'))) {
$sLine .= "\n"; // Put whitespace back.
} else {
// This is wrapping by the git editor, remove it.
$sLine .= ' ';
}
$aCommits[$sDate][$d]['body'] .= $sLine;
} else {
// Silently ignore unparsable lines (Author, Merge, ...).
}
}
}
ksort($aCommits); // Sort commits on date.
foreach ($aCommits as $sDate => $aDailyCommits) {
ksort($aDailyCommits); // Sort the commits on time.
$d = array_key_last($aDailyCommits); // Get last commit, email wil be sent with that timestamp.
// Loop through commits, formatting them and emailing them.
// One email will be sent for all commits grouped per day.
$sEmailBody = '';
$aProjects = array(); // For the email subject.
$aCommitIDs = array(); // For the email subject.
$sPrevProject = ''; // When switching project, add additional space.
foreach ($aDailyCommits as $aCommit) {
$nCounter ++;
$aProjects[] = $aCommit['project'] . ':' . $aCommit['branch'];
$aCommitIDs[] = substr($aCommit['id'], 0, 8); // Store the first 8 chars for the subject.
$sEmailBody .=
($sPrevProject != $aCommit['project']? (!$sEmailBody? '' : "\n\n") : '\\par' . "\n") .
'---\\texttt{\\,\\textbf{Commit {\tiny(\#' . str_pad($nCounter, 3, '0', STR_PAD_LEFT) . ')} ' . str_replace('_', '\_', $aCommit['project'] . ' ' . $aCommit['branch']) . '}}: % ' . $aCommit['id'] . "\n" .
// Prevent LaTeX errors by escaping troublesome chars.
str_replace(array('_', '#', '$', '%', '&', '^', '~'), array('\_', '\#', '\$', '\%', '\&', '\^{}', '$\sim$'),
// Also quote certain abbreviations.
preg_replace('/\b(NULL|SELECT|FROM|INNER JOIN|OUTER JOIN|WHERE|GROUP BY|HAVING|ORDER BY|LIMIT|SIGNED|UNSIGNED|FLOAT|TEXT|(TINY|SMALL|MEDIUM|BIG)?INT|' .
'ACTION|GET|HEAD|POST|' .
'[EIW][A-Z]{4,})\b/', '\\texttt{$1}',
// Quote variable names, file names, function names, branch names, and custom column names.
preg_replace('/(\$[A-Za-z0-9]+|[^ ,]+(\.json|\.php|\.png|\.txt|\.yml|\(\))|\'(plus|master|[a-z]+\/[^\']+)\'|\b[A-Za-z0-9._-]+[\/_][A-Za-z0-9._-]+\b)/', '\\texttt{$1}',
// Handle curly braces first, before we add them ourselves.
str_replace(array('{', '}'), array('\{', '\}'),
rtrim($aCommit['body']))))); // and rtrim() the last additional newline off.
$sPrevProject = $aCommit['project'];
}
// Cut lines where the commit header is very long due to a long branch name.
$sEmailBody = implode(
"\n",
array_map(
function ($sLine)
{
if (preg_match('/^---\\\texttt\{.+Commit /', $sLine) && strlen($sLine) > 120) {
$sLine = substr($sLine, 0, 120);
}
return $sLine;
},
explode("\n", $sEmailBody)
)
);
// Dump email contents in file.
do {
$sTempFile = uniqid();
} while (file_exists($sTempFile));
// wordwrap() behaves strangely. If I want to increase the indentation by
// adding spaces to the 3rd argument, it starts wrapping at strange
// locations. Too early, or too late. No clue what it's trying to do.
// Now we hacked around this a bit by wrapping first, and indenting after.
file_put_contents($sTempFile,
// This, however, wrapped the commit line too early.
preg_replace(
'/%\n ([a-f0-9]+)\n/',
'% $1' . "\n",
str_replace(
"\n ---",
"\n---",
str_replace(
"\n",
"\n ",
wordwrap($sEmailBody, 116, "\n")))));
// Send the email.
sort($aProjects);
$sEmailAddress = (isset($_SERVER['USERNAME'])? $_SERVER['USERNAME'] : (isset($_SERVER['LOGNAME'])? $_SERVER['LOGNAME'] : false));
if (!$sEmailAddress) {
print(
'Unable to find email address to email to.' . "\n\n");
die(EXIT_ERROR_CONNECTION_PROBLEM);
}
@exec('mutt' .
' -i ' . escapeshellarg($sTempFile) . // The email body.
' -e "my_hdr Date: ' . date('r', $d) . '"' . // Change the date of sending to the last commit.
' -s "Commits ' . implode(', ', array_unique($aProjects)) . ' (' . implode(', ', $aCommitIDs) . ')"' . // Build the subject.
' -- ' . $_SERVER['LOGNAME'] . // Send it to the current user.
' < /dev/null', $aOutput, $nReturn); // Force mutt to be non-interactive by piping /dev/null into the program.
unlink($sTempFile); // Remove the temp file.
if ($nReturn !== 0) {
print(
'Unable to send email with commit information.' . "\n\n");
flock($f, LOCK_UN); // Release the file lock.
die(EXIT_ERROR_CONNECTION_PROBLEM);
}
}
// Completed successfully. Append counter and release lock.
if (!$nCounter || $aCommits) {
// Append the counter only when we actually had something to do (empty file or commits to handle).
fwrite($f, 'Counter: ' . $nCounter . "\n"); // Write counter to file for next time.
}
flock($f, LOCK_UN); // Release the file lock.
fclose($f);
?>