git-watchtower 2.3.17 → 2.3.19
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/bin/git-watchtower.js +21 -21
- package/package.json +1 -1
- package/src/server/coordinator.js +20 -1
package/bin/git-watchtower.js
CHANGED
|
@@ -1881,18 +1881,24 @@ async function pollGitChanges() {
|
|
|
1881
1881
|
// Skip if a poll is already in progress (don't queue)
|
|
1882
1882
|
if (pollMutex.isLocked()) return;
|
|
1883
1883
|
const pollToken = await pollMutex.acquire();
|
|
1884
|
-
store.setState({ isPolling: true, pollingStatus: 'fetching' });
|
|
1885
1884
|
|
|
1886
|
-
//
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1885
|
+
// Everything past acquire() must be wrapped so the finally releases
|
|
1886
|
+
// the token. The previous shape kept the setState / casino.startSlotReels
|
|
1887
|
+
// / render() calls outside the try, so a throw from any of them (e.g. a
|
|
1888
|
+
// store middleware error, a casino interval-setup failure) leaked the
|
|
1889
|
+
// mutex permanently and stalled every subsequent poll cycle.
|
|
1890
|
+
try {
|
|
1891
|
+
store.setState({ isPolling: true, pollingStatus: 'fetching' });
|
|
1890
1892
|
|
|
1891
|
-
|
|
1893
|
+
// Casino mode: start slot reels spinning (no sound - too annoying)
|
|
1894
|
+
if (store.get('casinoModeEnabled')) {
|
|
1895
|
+
casino.startSlotReels(render);
|
|
1896
|
+
}
|
|
1892
1897
|
|
|
1893
|
-
|
|
1898
|
+
render();
|
|
1899
|
+
|
|
1900
|
+
const fetchStartTime = Date.now();
|
|
1894
1901
|
|
|
1895
|
-
try {
|
|
1896
1902
|
const newCurrentBranch = await getCurrentBranch();
|
|
1897
1903
|
const prevCurrentBranch = store.get('currentBranch');
|
|
1898
1904
|
|
|
@@ -2004,12 +2010,10 @@ async function pollGitChanges() {
|
|
|
2004
2010
|
const updatedBranches = [];
|
|
2005
2011
|
const updatedBranchPrevCommits = new Map();
|
|
2006
2012
|
const currentBranchName = store.get('currentBranch');
|
|
2007
|
-
const activeBranchNames = new Set();
|
|
2008
2013
|
for (const branch of pollFilteredBranches) {
|
|
2009
2014
|
// Clear previous cycle's flag so only freshly-updated branches are highlighted
|
|
2010
2015
|
branch.justUpdated = false;
|
|
2011
2016
|
if (branch.isDeleted) continue;
|
|
2012
|
-
activeBranchNames.add(branch.name);
|
|
2013
2017
|
const prevCommit = previousBranchStates.get(branch.name);
|
|
2014
2018
|
if (prevCommit && prevCommit !== branch.commit && branch.name !== currentBranchName) {
|
|
2015
2019
|
updatedBranches.push(branch);
|
|
@@ -2019,17 +2023,13 @@ async function pollGitChanges() {
|
|
|
2019
2023
|
previousBranchStates.set(branch.name, branch.commit);
|
|
2020
2024
|
}
|
|
2021
2025
|
|
|
2022
|
-
//
|
|
2023
|
-
//
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
cache.delete(name);
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2026
|
+
// (No second prune pass here — pruneStaleEntries above already prunes
|
|
2027
|
+
// these four caches against fetchedBranchNames with a 30 s deleted-
|
|
2028
|
+
// branch grace period. The previous extra loop pruned against the
|
|
2029
|
+
// active-only set instead, which excluded isDeleted entries and so
|
|
2030
|
+
// wiped a recently-deleted branch's sparkline / PR status / ahead-
|
|
2031
|
+
// behind data IMMEDIATELY — contradicting the retention window the
|
|
2032
|
+
// first prune was specifically designed to provide.)
|
|
2033
2033
|
|
|
2034
2034
|
// Flash and sound for updates or new branches
|
|
2035
2035
|
const casinoOn = store.get('casinoModeEnabled');
|
package/package.json
CHANGED
|
@@ -108,6 +108,24 @@ function ensureDir() {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Check if a process with the given PID is alive.
|
|
111
|
+
*
|
|
112
|
+
* `process.kill(pid, 0)` is the standard "is this PID alive?" probe. It
|
|
113
|
+
* sends signal 0 (no-op) and surfaces the kernel's answer via errno:
|
|
114
|
+
*
|
|
115
|
+
* - resolves cleanly → process exists and we can signal it
|
|
116
|
+
* - throws ESRCH → no such process; safe to call dead
|
|
117
|
+
* - throws EPERM → process exists but is owned by another
|
|
118
|
+
* user or in a different cgroup. STILL
|
|
119
|
+
* alive — we just can't signal it.
|
|
120
|
+
*
|
|
121
|
+
* Treating EPERM as "dead" was a real bug for the coordinator lock: if
|
|
122
|
+
* a coordinator's PID was reused by another local user's process after
|
|
123
|
+
* a crash, a peer instance would read the lock, see EPERM, decide the
|
|
124
|
+
* coordinator was dead, unlink the lock, and try to take over while
|
|
125
|
+
* the original (or reused) PID was still running. Mirroring the same
|
|
126
|
+
* EPERM-aware check used by src/utils/monitor-lock.js prevents that
|
|
127
|
+
* cleanup-then-clobber race.
|
|
128
|
+
*
|
|
111
129
|
* @param {number} pid
|
|
112
130
|
* @returns {boolean}
|
|
113
131
|
*/
|
|
@@ -116,7 +134,8 @@ function isProcessAlive(pid) {
|
|
|
116
134
|
process.kill(pid, 0);
|
|
117
135
|
return true;
|
|
118
136
|
} catch (e) {
|
|
119
|
-
|
|
137
|
+
// ESRCH = no such process; EPERM = exists but owned by another user.
|
|
138
|
+
return e.code === 'EPERM';
|
|
120
139
|
}
|
|
121
140
|
}
|
|
122
141
|
|