git-watchtower 2.1.6 → 2.1.8
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 +43 -6
- package/package.json +1 -1
- package/src/telemetry/config.js +9 -2
- package/src/telemetry/index.js +19 -14
package/bin/git-watchtower.js
CHANGED
|
@@ -1602,6 +1602,43 @@ async function undoLastSwitch() {
|
|
|
1602
1602
|
}
|
|
1603
1603
|
|
|
1604
1604
|
const lastSwitch = currentHistory[0];
|
|
1605
|
+
|
|
1606
|
+
// Detached HEAD restore: switchHistory captured the previous state as the
|
|
1607
|
+
// synthetic "HEAD@<hash>" name produced by getCurrentBranch(). sanitizeBranchName
|
|
1608
|
+
// rejects '@', so round-tripping through switchToBranch fails with "Invalid
|
|
1609
|
+
// Branch Name". Detect the detached form and `git checkout <hash>` directly.
|
|
1610
|
+
const detachedMatch = /^HEAD@([0-9a-f]{4,40})$/i.exec(lastSwitch.from);
|
|
1611
|
+
if (detachedMatch) {
|
|
1612
|
+
const hash = detachedMatch[1];
|
|
1613
|
+
addLog(`Undoing: restoring detached HEAD at ${hash}`, 'update');
|
|
1614
|
+
|
|
1615
|
+
if (await hasUncommittedChanges()) {
|
|
1616
|
+
addLog('Cannot undo: uncommitted changes in working directory', 'error');
|
|
1617
|
+
pendingDirtyOperation = { type: 'switch', branch: lastSwitch.from };
|
|
1618
|
+
showStashConfirm(`undo to detached HEAD ${hash}`);
|
|
1619
|
+
return { success: false, reason: 'dirty' };
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
try {
|
|
1623
|
+
await execGit(['checkout', hash], { cwd: PROJECT_ROOT });
|
|
1624
|
+
store.setState({
|
|
1625
|
+
currentBranch: lastSwitch.from,
|
|
1626
|
+
isDetachedHead: true,
|
|
1627
|
+
switchHistory: store.get('switchHistory').slice(1),
|
|
1628
|
+
});
|
|
1629
|
+
addLog(`Undone: detached HEAD at ${hash}`, 'success');
|
|
1630
|
+
branchSwitchCount++;
|
|
1631
|
+
pendingDirtyOperation = null;
|
|
1632
|
+
notifyClients();
|
|
1633
|
+
return { success: true };
|
|
1634
|
+
} catch (e) {
|
|
1635
|
+
const errMsg = e.stderr || e.message || String(e);
|
|
1636
|
+
addLog(`Undo failed: ${errMsg}`, 'error');
|
|
1637
|
+
showErrorToast('Undo Failed', truncate(errMsg, 100), 'Commit may have been garbage-collected');
|
|
1638
|
+
return { success: false };
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1605
1642
|
addLog(`Undoing: going back to ${lastSwitch.from}`, 'update');
|
|
1606
1643
|
|
|
1607
1644
|
const result = await switchToBranch(lastSwitch.from, false);
|
|
@@ -3125,12 +3162,12 @@ async function handleWebAction(action, payload) {
|
|
|
3125
3162
|
sendResult(true, 'Fetch complete');
|
|
3126
3163
|
break;
|
|
3127
3164
|
case 'undo': {
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
sendResult(true, `
|
|
3165
|
+
// Delegate to undoLastSwitch so detached-HEAD restoration and
|
|
3166
|
+
// history popping stay consistent with the TUI 'u' keybinding.
|
|
3167
|
+
const result = await undoLastSwitch();
|
|
3168
|
+
await pollGitChanges();
|
|
3169
|
+
if (result.success) {
|
|
3170
|
+
sendResult(true, `Undone last switch`);
|
|
3134
3171
|
} else {
|
|
3135
3172
|
sendResult(false, 'No switch to undo');
|
|
3136
3173
|
}
|
package/package.json
CHANGED
package/src/telemetry/config.js
CHANGED
|
@@ -31,7 +31,7 @@ function getConfigPath() {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Load telemetry config from disk
|
|
34
|
-
* @returns {{ telemetryEnabled: boolean, distinctId
|
|
34
|
+
* @returns {{ telemetryEnabled: boolean, distinctId?: string, promptedAt: string } | null}
|
|
35
35
|
*/
|
|
36
36
|
function loadTelemetryConfig() {
|
|
37
37
|
try {
|
|
@@ -48,7 +48,14 @@ function loadTelemetryConfig() {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Save telemetry config to disk
|
|
51
|
-
*
|
|
51
|
+
*
|
|
52
|
+
* `distinctId` is optional: when the user declines telemetry we persist
|
|
53
|
+
* `{ telemetryEnabled: false, promptedAt }` only — no persistent identifier
|
|
54
|
+
* lands on disk for declining users. `getOrCreateDistinctId` already
|
|
55
|
+
* handles the missing case by minting a fresh UUID if the user later
|
|
56
|
+
* opts in.
|
|
57
|
+
*
|
|
58
|
+
* @param {{ telemetryEnabled: boolean, distinctId?: string, promptedAt: string }} config
|
|
52
59
|
*/
|
|
53
60
|
function saveTelemetryConfig(config) {
|
|
54
61
|
const dir = getConfigDir();
|
package/src/telemetry/index.js
CHANGED
|
@@ -58,25 +58,30 @@ async function promptIfNeeded(promptYesNo) {
|
|
|
58
58
|
console.log('\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518');
|
|
59
59
|
console.log('');
|
|
60
60
|
|
|
61
|
-
const distinctId = config.getOrCreateDistinctId();
|
|
62
|
-
|
|
63
|
-
// Fire analytics_prompt_shown event — always sent regardless of user's choice
|
|
64
|
-
analytics.captureAlways('analytics_prompt_shown', distinctId);
|
|
65
|
-
|
|
66
61
|
const answer = await promptYesNo('Enable anonymous telemetry to help improve Git Watchtower?', false);
|
|
67
62
|
|
|
68
|
-
// Fire analytics_decision event — always sent so we know opt-in/out rates
|
|
69
|
-
analytics.captureAlways('analytics_decision', distinctId, { opted_in: answer });
|
|
70
|
-
|
|
71
|
-
config.saveTelemetryConfig({
|
|
72
|
-
telemetryEnabled: answer,
|
|
73
|
-
distinctId,
|
|
74
|
-
promptedAt: new Date().toISOString(),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
63
|
if (answer) {
|
|
64
|
+
// Consent given: assign and persist a stable distinctId, then fire the
|
|
65
|
+
// prompt-shown and decision events so opt-in rates are observable.
|
|
66
|
+
const distinctId = config.getOrCreateDistinctId();
|
|
67
|
+
analytics.captureAlways('analytics_prompt_shown', distinctId);
|
|
68
|
+
analytics.captureAlways('analytics_decision', distinctId, { opted_in: true });
|
|
69
|
+
config.saveTelemetryConfig({
|
|
70
|
+
telemetryEnabled: true,
|
|
71
|
+
distinctId,
|
|
72
|
+
promptedAt: new Date().toISOString(),
|
|
73
|
+
});
|
|
78
74
|
console.log(' Thank you! Telemetry enabled.\n');
|
|
79
75
|
} else {
|
|
76
|
+
// No consent: don't ship any events to PostHog and don't persist a
|
|
77
|
+
// distinctId on disk. Previously the prompt-shown and decision events
|
|
78
|
+
// fired regardless of the answer, tagged with the user's persistent
|
|
79
|
+
// distinctId — which contradicted the README's "anonymous, opt-in"
|
|
80
|
+
// pitch and meant declining users still showed up in analytics.
|
|
81
|
+
config.saveTelemetryConfig({
|
|
82
|
+
telemetryEnabled: false,
|
|
83
|
+
promptedAt: new Date().toISOString(),
|
|
84
|
+
});
|
|
80
85
|
console.log(' No problem! Telemetry disabled.\n');
|
|
81
86
|
}
|
|
82
87
|
}
|