git-watchtower 1.14.0 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/git/commands.js +20 -6
- package/src/server/process.js +74 -28
package/package.json
CHANGED
package/src/git/commands.js
CHANGED
|
@@ -198,7 +198,13 @@ async function log(branchName, options = {}) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
|
-
* Get commit count by day for sparkline
|
|
201
|
+
* Get commit count by day for sparkline.
|
|
202
|
+
*
|
|
203
|
+
* Buckets commits by local calendar date rather than by dividing a ms
|
|
204
|
+
* difference by 86 400 000, which breaks on DST transitions (a
|
|
205
|
+
* spring-forward day is only 23 h, causing Math.floor(23/24) = 0 and
|
|
206
|
+
* merging yesterday's commits into today's bucket).
|
|
207
|
+
*
|
|
202
208
|
* @param {string} branchName - Branch name
|
|
203
209
|
* @param {number} [days=7] - Number of days
|
|
204
210
|
* @param {string} [cwd] - Working directory
|
|
@@ -215,15 +221,23 @@ async function getCommitsByDay(branchName, days = 7, cwd) {
|
|
|
215
221
|
|
|
216
222
|
if (!stdout) return counts;
|
|
217
223
|
|
|
224
|
+
// Build a map from "YYYY-MM-DD" → bucket index. Using setDate()
|
|
225
|
+
// to step backwards is DST-safe because it adjusts the calendar
|
|
226
|
+
// day without relying on a fixed ms offset.
|
|
218
227
|
const today = new Date();
|
|
219
|
-
|
|
228
|
+
const dayBuckets = new Map();
|
|
229
|
+
for (let i = 0; i < days; i++) {
|
|
230
|
+
const d = new Date(today.getFullYear(), today.getMonth(), today.getDate() - i);
|
|
231
|
+
const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
232
|
+
dayBuckets.set(key, days - 1 - i);
|
|
233
|
+
}
|
|
220
234
|
|
|
221
235
|
for (const line of stdout.split('\n').filter(Boolean)) {
|
|
222
236
|
const commitDate = new Date(line);
|
|
223
|
-
commitDate.
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
counts[
|
|
237
|
+
const key = `${commitDate.getFullYear()}-${String(commitDate.getMonth() + 1).padStart(2, '0')}-${String(commitDate.getDate()).padStart(2, '0')}`;
|
|
238
|
+
const idx = dayBuckets.get(key);
|
|
239
|
+
if (idx !== undefined) {
|
|
240
|
+
counts[idx]++;
|
|
227
241
|
}
|
|
228
242
|
}
|
|
229
243
|
} catch (error) {
|
package/src/server/process.js
CHANGED
|
@@ -265,38 +265,13 @@ class ProcessManager {
|
|
|
265
265
|
return false;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
// Capture reference before nulling — needed for deferred
|
|
268
|
+
// Capture reference before nulling — needed for deferred force-kill
|
|
269
269
|
const proc = this.process;
|
|
270
270
|
|
|
271
|
-
// Try graceful shutdown first
|
|
272
271
|
if (process.platform === 'win32') {
|
|
273
|
-
|
|
274
|
-
spawn('taskkill', ['/pid', proc.pid.toString(), '/f', '/t']);
|
|
275
|
-
} catch (e) {
|
|
276
|
-
// Ignore taskkill errors
|
|
277
|
-
}
|
|
272
|
+
this._stopWindows(proc);
|
|
278
273
|
} else {
|
|
279
|
-
|
|
280
|
-
// Kill the entire process group (negative PID) so that
|
|
281
|
-
// grandchildren (e.g. npm -> node -> vite) are also terminated.
|
|
282
|
-
process.kill(-proc.pid, 'SIGTERM');
|
|
283
|
-
|
|
284
|
-
// Force kill after grace period
|
|
285
|
-
const forceKillTimeout = setTimeout(() => {
|
|
286
|
-
try {
|
|
287
|
-
process.kill(-proc.pid, 'SIGKILL');
|
|
288
|
-
} catch (e) {
|
|
289
|
-
// Process group may already be dead
|
|
290
|
-
}
|
|
291
|
-
}, KILL_GRACE_PERIOD);
|
|
292
|
-
|
|
293
|
-
// Clear timeout if process exits cleanly
|
|
294
|
-
proc.once('close', () => {
|
|
295
|
-
clearTimeout(forceKillTimeout);
|
|
296
|
-
});
|
|
297
|
-
} catch (e) {
|
|
298
|
-
// Process group may already be dead
|
|
299
|
-
}
|
|
274
|
+
this._stopUnix(proc);
|
|
300
275
|
}
|
|
301
276
|
|
|
302
277
|
this.process = null;
|
|
@@ -305,6 +280,77 @@ class ProcessManager {
|
|
|
305
280
|
return true;
|
|
306
281
|
}
|
|
307
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Unix stop: SIGTERM the process group, then SIGKILL after a grace period.
|
|
285
|
+
* The grace timer is unref'd so it doesn't keep the event loop alive when
|
|
286
|
+
* the main process wants to exit.
|
|
287
|
+
* @param {import('child_process').ChildProcess} proc
|
|
288
|
+
* @private
|
|
289
|
+
*/
|
|
290
|
+
_stopUnix(proc) {
|
|
291
|
+
// If the process has already exited, there's nothing to signal.
|
|
292
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return;
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
process.kill(-proc.pid, 'SIGTERM');
|
|
296
|
+
} catch (e) {
|
|
297
|
+
// Process group may already be dead
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const forceKillTimeout = setTimeout(() => {
|
|
302
|
+
// Re-check: process may have exited during the grace period.
|
|
303
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return;
|
|
304
|
+
try {
|
|
305
|
+
process.kill(-proc.pid, 'SIGKILL');
|
|
306
|
+
} catch (e) {
|
|
307
|
+
// Process group may already be dead
|
|
308
|
+
}
|
|
309
|
+
}, KILL_GRACE_PERIOD);
|
|
310
|
+
|
|
311
|
+
// Don't let this timer keep the event loop alive on shutdown.
|
|
312
|
+
forceKillTimeout.unref();
|
|
313
|
+
|
|
314
|
+
// Clear early if the process exits before the grace period.
|
|
315
|
+
proc.once('close', () => {
|
|
316
|
+
clearTimeout(forceKillTimeout);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Windows stop: taskkill /t (tree kill). If the process doesn't exit
|
|
322
|
+
* within the grace period, retry with /f (force).
|
|
323
|
+
* @param {import('child_process').ChildProcess} proc
|
|
324
|
+
* @private
|
|
325
|
+
*/
|
|
326
|
+
_stopWindows(proc) {
|
|
327
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return;
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
spawn('taskkill', ['/pid', proc.pid.toString(), '/t']);
|
|
331
|
+
} catch (e) {
|
|
332
|
+
// Ignore spawn errors (PID already gone, etc.)
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Fallback: force-kill if the process is still alive after the
|
|
337
|
+
// grace period. This mirrors the Unix SIGTERM → SIGKILL pattern.
|
|
338
|
+
const forceKillTimeout = setTimeout(() => {
|
|
339
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return;
|
|
340
|
+
try {
|
|
341
|
+
spawn('taskkill', ['/pid', proc.pid.toString(), '/f', '/t']);
|
|
342
|
+
} catch (e) {
|
|
343
|
+
// Ignore — process may already be dead
|
|
344
|
+
}
|
|
345
|
+
}, KILL_GRACE_PERIOD);
|
|
346
|
+
|
|
347
|
+
forceKillTimeout.unref();
|
|
348
|
+
|
|
349
|
+
proc.once('close', () => {
|
|
350
|
+
clearTimeout(forceKillTimeout);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
308
354
|
/**
|
|
309
355
|
* Restart the server process
|
|
310
356
|
* @returns {Promise<{success: boolean, error?: Error, pid?: number}>}
|