git-watchtower 1.9.4 → 1.9.5
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 +14 -134
- package/package.json +1 -1
- package/src/polling/engine.js +2 -0
package/bin/git-watchtower.js
CHANGED
|
@@ -81,9 +81,9 @@ const { parseGitHubPr, parseGitLabMr, parseGitHubPrList, parseGitLabMrList, isBa
|
|
|
81
81
|
// ============================================================================
|
|
82
82
|
// Security & Validation (imported from src/git/branch.js and src/git/commands.js)
|
|
83
83
|
// ============================================================================
|
|
84
|
-
const { isValidBranchName, sanitizeBranchName, getGoneBranches, deleteGoneBranches } = require('../src/git/branch');
|
|
84
|
+
const { isValidBranchName, sanitizeBranchName, getGoneBranches, deleteGoneBranches, getCurrentBranch: getCurrentBranchRaw, getAllBranches: getAllBranchesRaw } = require('../src/git/branch');
|
|
85
85
|
const { pruneStaleEntries } = require('../src/polling/engine');
|
|
86
|
-
const { isGitAvailable: checkGitAvailable, execGit, execGitSilent, getDiffStats: getDiffStatsSafe, getAheadBehind, getDiffShortstat } = require('../src/git/commands');
|
|
86
|
+
const { isGitAvailable: checkGitAvailable, execGit, execGitSilent, getDiffStats: getDiffStatsSafe, getAheadBehind, getDiffShortstat, hasUncommittedChanges: checkUncommittedChanges } = require('../src/git/commands');
|
|
87
87
|
|
|
88
88
|
// Session stats (always-on, non-casino stats)
|
|
89
89
|
const sessionStats = require('../src/stats/session');
|
|
@@ -743,8 +743,9 @@ const actions = require('../src/ui/actions');
|
|
|
743
743
|
// Diff stats parsing and stash imported from src/git/commands.js
|
|
744
744
|
const { parseDiffStats, stash: gitStash, stashPop: gitStashPop } = require('../src/git/commands');
|
|
745
745
|
|
|
746
|
-
// Server process command parsing
|
|
746
|
+
// Server process command parsing and static server utilities
|
|
747
747
|
const { parseCommand } = require('../src/server/process');
|
|
748
|
+
const { getMimeType, injectLiveReload } = require('../src/server/static');
|
|
748
749
|
|
|
749
750
|
// State (non-store globals)
|
|
750
751
|
let previousBranchStates = new Map(); // branch name -> commit hash
|
|
@@ -781,39 +782,7 @@ const MAX_HISTORY = 20;
|
|
|
781
782
|
let lastSparklineUpdate = 0;
|
|
782
783
|
const SPARKLINE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
783
784
|
|
|
784
|
-
//
|
|
785
|
-
const MIME_TYPES = {
|
|
786
|
-
'.html': 'text/html',
|
|
787
|
-
'.css': 'text/css',
|
|
788
|
-
'.js': 'application/javascript',
|
|
789
|
-
'.json': 'application/json',
|
|
790
|
-
'.png': 'image/png',
|
|
791
|
-
'.jpg': 'image/jpeg',
|
|
792
|
-
'.jpeg': 'image/jpeg',
|
|
793
|
-
'.gif': 'image/gif',
|
|
794
|
-
'.svg': 'image/svg+xml',
|
|
795
|
-
'.ico': 'image/x-icon',
|
|
796
|
-
'.webp': 'image/webp',
|
|
797
|
-
'.woff': 'font/woff',
|
|
798
|
-
'.woff2': 'font/woff2',
|
|
799
|
-
'.ttf': 'font/ttf',
|
|
800
|
-
'.xml': 'application/xml',
|
|
801
|
-
'.txt': 'text/plain',
|
|
802
|
-
'.md': 'text/markdown',
|
|
803
|
-
'.pdf': 'application/pdf',
|
|
804
|
-
};
|
|
805
|
-
|
|
806
|
-
// Live reload script
|
|
807
|
-
const LIVE_RELOAD_SCRIPT = `
|
|
808
|
-
<script>
|
|
809
|
-
(function() {
|
|
810
|
-
var source = new EventSource('/livereload');
|
|
811
|
-
source.onmessage = function(e) {
|
|
812
|
-
if (e.data === 'reload') location.reload();
|
|
813
|
-
};
|
|
814
|
-
})();
|
|
815
|
-
</script>
|
|
816
|
-
</body>`;
|
|
785
|
+
// MIME_TYPES and LIVE_RELOAD_SCRIPT imported from src/server/static.js (via getMimeType and injectLiveReload)
|
|
817
786
|
|
|
818
787
|
// ============================================================================
|
|
819
788
|
// Utility Functions
|
|
@@ -1379,20 +1348,9 @@ function hideStashConfirm() {
|
|
|
1379
1348
|
// ============================================================================
|
|
1380
1349
|
|
|
1381
1350
|
async function getCurrentBranch() {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
if (stdout === 'HEAD') {
|
|
1386
|
-
store.setState({ isDetachedHead: true });
|
|
1387
|
-
// Get the short commit hash instead
|
|
1388
|
-
const { stdout: commitHash } = await execGit(['rev-parse', '--short', 'HEAD'], { cwd: PROJECT_ROOT });
|
|
1389
|
-
return `HEAD@${commitHash}`;
|
|
1390
|
-
}
|
|
1391
|
-
store.setState({ isDetachedHead: false });
|
|
1392
|
-
return stdout;
|
|
1393
|
-
} catch (e) {
|
|
1394
|
-
return null;
|
|
1395
|
-
}
|
|
1351
|
+
const result = await getCurrentBranchRaw(PROJECT_ROOT);
|
|
1352
|
+
store.setState({ isDetachedHead: result.isDetached });
|
|
1353
|
+
return result.name;
|
|
1396
1354
|
}
|
|
1397
1355
|
|
|
1398
1356
|
async function checkRemoteExists() {
|
|
@@ -1406,91 +1364,14 @@ async function checkRemoteExists() {
|
|
|
1406
1364
|
}
|
|
1407
1365
|
|
|
1408
1366
|
async function hasUncommittedChanges() {
|
|
1409
|
-
|
|
1410
|
-
const { stdout } = await execGit(['status', '--porcelain'], { cwd: PROJECT_ROOT, timeout: 5000 });
|
|
1411
|
-
return stdout.length > 0;
|
|
1412
|
-
} catch (e) {
|
|
1413
|
-
return false;
|
|
1414
|
-
}
|
|
1367
|
+
return checkUncommittedChanges(PROJECT_ROOT);
|
|
1415
1368
|
}
|
|
1416
1369
|
|
|
1417
1370
|
// isAuthError, isMergeConflict, isNetworkError imported from src/utils/errors.js
|
|
1418
1371
|
|
|
1419
1372
|
async function getAllBranches() {
|
|
1420
1373
|
try {
|
|
1421
|
-
await
|
|
1422
|
-
|
|
1423
|
-
const branchList = [];
|
|
1424
|
-
const seenBranches = new Set();
|
|
1425
|
-
|
|
1426
|
-
// Get local branches
|
|
1427
|
-
// Use \x1f (Unit Separator) as delimiter since | can appear in commit subjects
|
|
1428
|
-
const delimiter = '\x1f';
|
|
1429
|
-
const { stdout: localOutput } = await execGit(
|
|
1430
|
-
['for-each-ref', '--sort=-committerdate', `--format=%(refname:short)${delimiter}%(committerdate:iso8601)${delimiter}%(objectname:short)${delimiter}%(subject)`, 'refs/heads/'],
|
|
1431
|
-
{ cwd: PROJECT_ROOT }
|
|
1432
|
-
);
|
|
1433
|
-
|
|
1434
|
-
for (const line of localOutput.split('\n').filter(Boolean)) {
|
|
1435
|
-
const [name, dateStr, commit, ...subjectParts] = line.split(delimiter);
|
|
1436
|
-
const subject = subjectParts.join(delimiter);
|
|
1437
|
-
if (!seenBranches.has(name) && isValidBranchName(name)) {
|
|
1438
|
-
seenBranches.add(name);
|
|
1439
|
-
branchList.push({
|
|
1440
|
-
name,
|
|
1441
|
-
commit,
|
|
1442
|
-
subject: subject || '',
|
|
1443
|
-
date: new Date(dateStr),
|
|
1444
|
-
isLocal: true,
|
|
1445
|
-
hasRemote: false,
|
|
1446
|
-
hasUpdates: false,
|
|
1447
|
-
});
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
// Get remote branches (using configured remote name)
|
|
1452
|
-
const remoteResult = await execGitSilent(
|
|
1453
|
-
['for-each-ref', '--sort=-committerdate', `--format=%(refname:short)${delimiter}%(committerdate:iso8601)${delimiter}%(objectname:short)${delimiter}%(subject)`, `refs/remotes/${REMOTE_NAME}/`],
|
|
1454
|
-
{ cwd: PROJECT_ROOT }
|
|
1455
|
-
);
|
|
1456
|
-
const remoteOutput = remoteResult ? remoteResult.stdout : '';
|
|
1457
|
-
|
|
1458
|
-
const remotePrefix = `${REMOTE_NAME}/`;
|
|
1459
|
-
for (const line of remoteOutput.split('\n').filter(Boolean)) {
|
|
1460
|
-
const [fullName, dateStr, commit, ...subjectParts] = line.split(delimiter);
|
|
1461
|
-
const subject = subjectParts.join(delimiter);
|
|
1462
|
-
const name = fullName.replace(remotePrefix, '');
|
|
1463
|
-
if (name === 'HEAD') continue;
|
|
1464
|
-
if (!isValidBranchName(name)) continue;
|
|
1465
|
-
|
|
1466
|
-
const existing = branchList.find(b => b.name === name);
|
|
1467
|
-
if (existing) {
|
|
1468
|
-
existing.hasRemote = true;
|
|
1469
|
-
existing.remoteCommit = commit;
|
|
1470
|
-
existing.remoteDate = new Date(dateStr);
|
|
1471
|
-
existing.remoteSubject = subject || '';
|
|
1472
|
-
if (commit !== existing.commit) {
|
|
1473
|
-
existing.hasUpdates = true;
|
|
1474
|
-
// Use remote's date when it has updates (so it sorts to top)
|
|
1475
|
-
existing.date = new Date(dateStr);
|
|
1476
|
-
existing.subject = subject || existing.subject;
|
|
1477
|
-
}
|
|
1478
|
-
} else if (!seenBranches.has(name)) {
|
|
1479
|
-
seenBranches.add(name);
|
|
1480
|
-
branchList.push({
|
|
1481
|
-
name,
|
|
1482
|
-
commit,
|
|
1483
|
-
subject: subject || '',
|
|
1484
|
-
date: new Date(dateStr),
|
|
1485
|
-
isLocal: false,
|
|
1486
|
-
hasRemote: true,
|
|
1487
|
-
hasUpdates: false,
|
|
1488
|
-
});
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
branchList.sort((a, b) => b.date - a.date);
|
|
1493
|
-
return branchList; // Return all branches, caller will slice
|
|
1374
|
+
return await getAllBranchesRaw({ remoteName: REMOTE_NAME, fetch: true, cwd: PROJECT_ROOT });
|
|
1494
1375
|
} catch (e) {
|
|
1495
1376
|
addLog(`Failed to get branches: ${e.message || e}`, 'error');
|
|
1496
1377
|
return [];
|
|
@@ -1841,6 +1722,8 @@ async function pollGitChanges() {
|
|
|
1841
1722
|
const updatedBranches = [];
|
|
1842
1723
|
const currentBranchName = store.get('currentBranch');
|
|
1843
1724
|
for (const branch of pollFilteredBranches) {
|
|
1725
|
+
// Clear previous cycle's flag so only freshly-updated branches are highlighted
|
|
1726
|
+
branch.justUpdated = false;
|
|
1844
1727
|
if (branch.isDeleted) continue;
|
|
1845
1728
|
const prevCommit = previousBranchStates.get(branch.name);
|
|
1846
1729
|
if (prevCommit && prevCommit !== branch.commit && branch.name !== currentBranchName) {
|
|
@@ -2109,7 +1992,7 @@ function handleLiveReload(req, res) {
|
|
|
2109
1992
|
|
|
2110
1993
|
function serveFile(res, filePath, logPath) {
|
|
2111
1994
|
const ext = path.extname(filePath).toLowerCase();
|
|
2112
|
-
const mimeType =
|
|
1995
|
+
const mimeType = getMimeType(ext);
|
|
2113
1996
|
|
|
2114
1997
|
fs.readFile(filePath, (err, data) => {
|
|
2115
1998
|
if (err) {
|
|
@@ -2120,10 +2003,7 @@ function serveFile(res, filePath, logPath) {
|
|
|
2120
2003
|
}
|
|
2121
2004
|
|
|
2122
2005
|
if (mimeType === 'text/html') {
|
|
2123
|
-
|
|
2124
|
-
if (html.includes('</body>')) {
|
|
2125
|
-
html = html.replace('</body>', LIVE_RELOAD_SCRIPT);
|
|
2126
|
-
}
|
|
2006
|
+
const html = injectLiveReload(data.toString());
|
|
2127
2007
|
res.writeHead(200, { 'Content-Type': mimeType });
|
|
2128
2008
|
res.end(html);
|
|
2129
2009
|
} else {
|
package/package.json
CHANGED
package/src/polling/engine.js
CHANGED
|
@@ -57,6 +57,8 @@ function detectDeletedBranches(knownBranchNames, fetchedBranchNames, existingBra
|
|
|
57
57
|
function detectUpdatedBranches(branches, previousStates, currentBranch) {
|
|
58
58
|
const updated = [];
|
|
59
59
|
for (const branch of branches) {
|
|
60
|
+
// Clear previous cycle's flag so only freshly-updated branches are highlighted
|
|
61
|
+
branch.justUpdated = false;
|
|
60
62
|
if (branch.isDeleted) continue;
|
|
61
63
|
const prevCommit = previousStates.get(branch.name);
|
|
62
64
|
if (prevCommit && prevCommit !== branch.commit && branch.name !== currentBranch) {
|