brainfile 0.19.0 → 0.21.0
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/README.md +18 -24
- package/dist/__tests__/helpers/mcp-wire.d.ts +45 -0
- package/dist/__tests__/helpers/mcp-wire.d.ts.map +1 -0
- package/dist/__tests__/helpers/mcp-wire.js +133 -0
- package/dist/__tests__/helpers/mcp-wire.js.map +1 -0
- package/dist/{chunk-YTFLC673.mjs → chunk-FOLBAGJS.mjs} +2483 -1075
- package/dist/cli.mjs +2526 -1656
- package/dist/commands/adr.d.ts +1 -9
- package/dist/commands/adr.d.ts.map +1 -1
- package/dist/commands/adr.js +11 -88
- package/dist/commands/adr.js.map +1 -1
- package/dist/commands/archive.d.ts +2 -5
- package/dist/commands/archive.d.ts.map +1 -1
- package/dist/commands/archive.js +43 -11
- package/dist/commands/archive.js.map +1 -1
- package/dist/commands/brief.d.ts +32 -0
- package/dist/commands/brief.d.ts.map +1 -0
- package/dist/commands/brief.js +161 -0
- package/dist/commands/brief.js.map +1 -0
- package/dist/commands/complete.js +1 -1
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/init.d.ts +14 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +115 -15
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/log.js +31 -18
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/mcp.d.ts +9 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +72 -17
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/merge-driver.d.ts +26 -0
- package/dist/commands/merge-driver.d.ts.map +1 -0
- package/dist/commands/merge-driver.js +195 -0
- package/dist/commands/merge-driver.js.map +1 -0
- package/dist/commands/migrate-tracked.d.ts +8 -0
- package/dist/commands/migrate-tracked.d.ts.map +1 -0
- package/dist/commands/migrate-tracked.js +428 -0
- package/dist/commands/migrate-tracked.js.map +1 -0
- package/dist/commands/migrate.d.ts +6 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +19 -12
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/sync.d.ts +16 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +152 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/where.d.ts +21 -0
- package/dist/commands/where.d.ts.map +1 -0
- package/dist/commands/where.js +135 -0
- package/dist/commands/where.js.map +1 -0
- package/dist/mcp/helpers.d.ts +8 -0
- package/dist/mcp/helpers.d.ts.map +1 -1
- package/dist/mcp/helpers.js +44 -0
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/schemas.d.ts +297 -0
- package/dist/mcp/schemas.d.ts.map +1 -0
- package/dist/mcp/schemas.js +328 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/tools/brief_tool.d.ts +14 -0
- package/dist/mcp/tools/brief_tool.d.ts.map +1 -0
- package/dist/mcp/tools/brief_tool.js +96 -0
- package/dist/mcp/tools/brief_tool.js.map +1 -0
- package/dist/mcp/tools/contract_tool.d.ts.map +1 -1
- package/dist/mcp/tools/contract_tool.js +43 -18
- package/dist/mcp/tools/contract_tool.js.map +1 -1
- package/dist/mcp/tools/get_task_tool.d.ts.map +1 -1
- package/dist/mcp/tools/get_task_tool.js +7 -2
- package/dist/mcp/tools/get_task_tool.js.map +1 -1
- package/dist/mcp/tools/list_tasks_tool.d.ts.map +1 -1
- package/dist/mcp/tools/list_tasks_tool.js +9 -3
- package/dist/mcp/tools/list_tasks_tool.js.map +1 -1
- package/dist/mcp/tools/search_tool.d.ts.map +1 -1
- package/dist/mcp/tools/search_tool.js +17 -4
- package/dist/mcp/tools/search_tool.js.map +1 -1
- package/dist/mcp/tools/subtask_tool.d.ts.map +1 -1
- package/dist/mcp/tools/subtask_tool.js +52 -17
- package/dist/mcp/tools/subtask_tool.js.map +1 -1
- package/dist/mcp/tools/task_add_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_add_tool.js +13 -7
- package/dist/mcp/tools/task_add_tool.js.map +1 -1
- package/dist/mcp/tools/task_complete_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_complete_tool.js +39 -10
- package/dist/mcp/tools/task_complete_tool.js.map +1 -1
- package/dist/mcp/tools/task_delete_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_delete_tool.js +12 -6
- package/dist/mcp/tools/task_delete_tool.js.map +1 -1
- package/dist/mcp/tools/task_move_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_move_tool.js +21 -11
- package/dist/mcp/tools/task_move_tool.js.map +1 -1
- package/dist/mcp/tools/task_patch_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_patch_tool.js +23 -10
- package/dist/mcp/tools/task_patch_tool.js.map +1 -1
- package/dist/schemas/board.json +1 -1
- package/dist/tui/theme.d.ts +98 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +141 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/{tui-P56H4X7N.mjs → tui-M2KWIG2E.mjs} +1707 -2006
- package/dist/utils/board-autosync.d.ts +22 -0
- package/dist/utils/board-autosync.d.ts.map +1 -0
- package/dist/utils/board-autosync.js +166 -0
- package/dist/utils/board-autosync.js.map +1 -0
- package/dist/utils/board-commit-hooks.d.ts +12 -0
- package/dist/utils/board-commit-hooks.d.ts.map +1 -0
- package/dist/utils/board-commit-hooks.js +51 -0
- package/dist/utils/board-commit-hooks.js.map +1 -0
- package/dist/utils/board-repo.d.ts +108 -0
- package/dist/utils/board-repo.d.ts.map +1 -0
- package/dist/utils/board-repo.js +501 -0
- package/dist/utils/board-repo.js.map +1 -0
- package/dist/utils/board-sync.d.ts +111 -0
- package/dist/utils/board-sync.d.ts.map +1 -0
- package/dist/utils/board-sync.js +469 -0
- package/dist/utils/board-sync.js.map +1 -0
- package/dist/utils/brainfile-path.d.ts +11 -1
- package/dist/utils/brainfile-path.d.ts.map +1 -1
- package/dist/utils/brainfile-path.js +39 -2
- package/dist/utils/brainfile-path.js.map +1 -1
- package/dist/utils/cli-error.d.ts +20 -0
- package/dist/utils/cli-error.d.ts.map +1 -1
- package/dist/utils/cli-error.js +36 -0
- package/dist/utils/cli-error.js.map +1 -1
- package/package.json +3 -3
- package/dist/commands/rules.d.ts +0 -68
- package/dist/commands/rules.d.ts.map +0 -1
- package/dist/commands/rules.js +0 -322
- package/dist/commands/rules.js.map +0 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.mergeFields = mergeFields;
|
|
37
|
+
exports.mergeBodies = mergeBodies;
|
|
38
|
+
exports.mergeBoardDocuments = mergeBoardDocuments;
|
|
39
|
+
exports.summarizeFieldChanges = summarizeFieldChanges;
|
|
40
|
+
exports.mergeDriverCommand = mergeDriverCommand;
|
|
41
|
+
/**
|
|
42
|
+
* `brainfile merge-driver <base> <ours> <theirs>` — git merge driver for
|
|
43
|
+
* board documents (spec-9). Registered by init/migrate/sync as
|
|
44
|
+
* `merge.brainfile.driver`, selected by `.gitattributes` on the board branch.
|
|
45
|
+
*
|
|
46
|
+
* Frontmatter merges field by field: a field changed on one side takes that
|
|
47
|
+
* side; changed on both, the later `updatedAt` wins and a tie goes to theirs,
|
|
48
|
+
* so both machines converge on the same answer. The body goes through git's
|
|
49
|
+
* own three-way text merge; a conflict whose two sides are both list items
|
|
50
|
+
* (log entries, notes) resolves by union ordered by timestamp. Anything else
|
|
51
|
+
* keeps git's markers and exits 1 so `sync` can name the file.
|
|
52
|
+
*/
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const os = __importStar(require("os"));
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const child_process_1 = require("child_process");
|
|
57
|
+
const core_1 = require("@brainfile/core");
|
|
58
|
+
function parseDoc(content) {
|
|
59
|
+
const parsed = (0, core_1.parseFrontmatter)(content);
|
|
60
|
+
if (!parsed)
|
|
61
|
+
return null;
|
|
62
|
+
return { data: parsed.data, body: parsed.body };
|
|
63
|
+
}
|
|
64
|
+
function same(a, b) {
|
|
65
|
+
return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
66
|
+
}
|
|
67
|
+
function stamp(data) {
|
|
68
|
+
const value = data.updatedAt;
|
|
69
|
+
return typeof value === 'string' ? value : '';
|
|
70
|
+
}
|
|
71
|
+
/** Field-level last-writer-wins; exported for tests. */
|
|
72
|
+
function mergeFields(base, ours, theirs) {
|
|
73
|
+
const keys = [];
|
|
74
|
+
for (const key of [...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base)]) {
|
|
75
|
+
if (!keys.includes(key))
|
|
76
|
+
keys.push(key);
|
|
77
|
+
}
|
|
78
|
+
const oursWins = stamp(ours) > stamp(theirs);
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const key of keys) {
|
|
81
|
+
const b = base[key];
|
|
82
|
+
const o = ours[key];
|
|
83
|
+
const t = theirs[key];
|
|
84
|
+
let pick;
|
|
85
|
+
if (same(o, t))
|
|
86
|
+
pick = o;
|
|
87
|
+
else if (same(b, o))
|
|
88
|
+
pick = t;
|
|
89
|
+
else if (same(b, t))
|
|
90
|
+
pick = o;
|
|
91
|
+
else if (key === 'updatedAt')
|
|
92
|
+
pick = String(o ?? '') > String(t ?? '') ? o : t;
|
|
93
|
+
else
|
|
94
|
+
pick = oursWins ? o : t;
|
|
95
|
+
if (pick !== undefined)
|
|
96
|
+
out[key] = pick;
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
const CONFLICT = /^<{7}[^\n]*\n([\s\S]*?)^={7}\n([\s\S]*?)^>{7}[^\n]*\n?/gm;
|
|
101
|
+
const LIST_ITEM = /^\s*[-*]\s+/;
|
|
102
|
+
const LEADING_STAMP = /^\s*[-*]\s+(\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)\b/;
|
|
103
|
+
function unionLists(ours, theirs) {
|
|
104
|
+
const a = ours.split('\n').filter((l) => l.trim().length > 0);
|
|
105
|
+
const b = theirs.split('\n').filter((l) => l.trim().length > 0);
|
|
106
|
+
if (![...a, ...b].every((l) => LIST_ITEM.test(l)))
|
|
107
|
+
return null;
|
|
108
|
+
const merged = [...a];
|
|
109
|
+
for (const line of b)
|
|
110
|
+
if (!merged.includes(line))
|
|
111
|
+
merged.push(line);
|
|
112
|
+
if (merged.every((l) => LEADING_STAMP.test(l))) {
|
|
113
|
+
merged.sort((x, y) => {
|
|
114
|
+
const sx = LEADING_STAMP.exec(x)?.[1] ?? '';
|
|
115
|
+
const sy = LEADING_STAMP.exec(y)?.[1] ?? '';
|
|
116
|
+
return sx < sy ? -1 : sx > sy ? 1 : 0;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return `${merged.join('\n')}\n`;
|
|
120
|
+
}
|
|
121
|
+
/** Three-way text merge through git, then union-resolve list conflicts. */
|
|
122
|
+
function mergeBodies(base, ours, theirs) {
|
|
123
|
+
if (ours === theirs)
|
|
124
|
+
return { content: ours, clean: true };
|
|
125
|
+
if (base === ours)
|
|
126
|
+
return { content: theirs, clean: true };
|
|
127
|
+
if (base === theirs)
|
|
128
|
+
return { content: ours, clean: true };
|
|
129
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'brainfile-merge-'));
|
|
130
|
+
try {
|
|
131
|
+
const files = { base: path.join(dir, 'base'), ours: path.join(dir, 'ours'), theirs: path.join(dir, 'theirs') };
|
|
132
|
+
fs.writeFileSync(files.base, base);
|
|
133
|
+
fs.writeFileSync(files.ours, ours);
|
|
134
|
+
fs.writeFileSync(files.theirs, theirs);
|
|
135
|
+
const r = (0, child_process_1.spawnSync)('git', ['merge-file', '-p', '-L', 'ours', '-L', 'base', '-L', 'theirs', files.ours, files.base, files.theirs], {
|
|
136
|
+
encoding: 'utf-8',
|
|
137
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
138
|
+
});
|
|
139
|
+
if (r.status === null || r.status < 0) {
|
|
140
|
+
return { content: `<<<<<<< ours\n${ours}=======\n${theirs}>>>>>>> theirs\n`, clean: false };
|
|
141
|
+
}
|
|
142
|
+
if (r.status === 0)
|
|
143
|
+
return { content: r.stdout, clean: true };
|
|
144
|
+
let clean = true;
|
|
145
|
+
const content = r.stdout.replace(CONFLICT, (whole, oursHunk, theirsHunk) => {
|
|
146
|
+
const union = unionLists(oursHunk, theirsHunk);
|
|
147
|
+
if (union === null) {
|
|
148
|
+
clean = false;
|
|
149
|
+
return whole;
|
|
150
|
+
}
|
|
151
|
+
return union;
|
|
152
|
+
});
|
|
153
|
+
return { content, clean };
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Merge two board documents against their common base. */
|
|
160
|
+
function mergeBoardDocuments(base, ours, theirs) {
|
|
161
|
+
const b = parseDoc(base);
|
|
162
|
+
const o = parseDoc(ours);
|
|
163
|
+
const t = parseDoc(theirs);
|
|
164
|
+
if (!o || !t)
|
|
165
|
+
return mergeBodies(base, ours, theirs);
|
|
166
|
+
const data = mergeFields(b?.data ?? {}, o.data, t.data);
|
|
167
|
+
const body = mergeBodies(b?.body ?? '', o.body, t.body);
|
|
168
|
+
return { content: (0, core_1.serializeFrontmatter)(data, body.content), clean: body.clean };
|
|
169
|
+
}
|
|
170
|
+
/** "column: todo → review; title: …" for the fields that differ; exported for sync. */
|
|
171
|
+
function summarizeFieldChanges(before, after) {
|
|
172
|
+
const b = parseDoc(before)?.data ?? {};
|
|
173
|
+
const a = parseDoc(after)?.data ?? {};
|
|
174
|
+
const parts = [];
|
|
175
|
+
for (const key of Object.keys(a)) {
|
|
176
|
+
if (key === 'updatedAt' || same(b[key], a[key]))
|
|
177
|
+
continue;
|
|
178
|
+
const render = (v) => (typeof v === 'string' ? v : JSON.stringify(v));
|
|
179
|
+
parts.push(`${key}: ${b[key] === undefined ? '∅' : render(b[key])} → ${render(a[key])}`);
|
|
180
|
+
}
|
|
181
|
+
return parts.join('; ');
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Entry point for git: reads the three files, writes the result over `ours`,
|
|
185
|
+
* returns the exit code (0 clean, 1 conflicts remain, 2 usage).
|
|
186
|
+
*/
|
|
187
|
+
function mergeDriverCommand(options) {
|
|
188
|
+
const read = (p) => (fs.existsSync(p) ? fs.readFileSync(p, 'utf-8') : '');
|
|
189
|
+
if (!options.ours || !options.theirs)
|
|
190
|
+
return 2;
|
|
191
|
+
const outcome = mergeBoardDocuments(read(options.base), read(options.ours), read(options.theirs));
|
|
192
|
+
fs.writeFileSync(options.ours, outcome.content, 'utf-8');
|
|
193
|
+
return outcome.clean ? 0 : 1;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=merge-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-driver.js","sourceRoot":"","sources":["../../src/commands/merge-driver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,kCAoBC;AAuBD,kCA+BC;AAGD,kDAQC;AAGD,sDAUC;AAYD,gDAMC;AAnKD;;;;;;;;;;;GAWG;AACH,uCAAyB;AACzB,uCAAyB;AACzB,2CAA6B;AAC7B,iDAA0C;AAC1C,0CAAyE;AAezE,SAAS,QAAQ,CAAC,OAAe;IAC/B,MAAM,MAAM,GAAG,IAAA,uBAAgB,EAAS,OAAO,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;AAClD,CAAC;AAED,SAAS,IAAI,CAAC,CAAU,EAAE,CAAU;IAClC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,KAAK,CAAC,IAAY;IACzB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;IAC7B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAChD,CAAC;AAED,wDAAwD;AACxD,SAAgB,WAAW,CAAC,IAAY,EAAE,IAAY,EAAE,MAAc;IACpE,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACvF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,IAAa,CAAC;QAClB,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;YAAE,IAAI,GAAG,CAAC,CAAC;aACpB,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;YAAE,IAAI,GAAG,CAAC,CAAC;aACzB,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;YAAE,IAAI,GAAG,CAAC,CAAC;aACzB,IAAI,GAAG,KAAK,WAAW;YAAE,IAAI,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;YAC1E,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,IAAI,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,QAAQ,GAAG,0DAA0D,CAAC;AAC5E,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,aAAa,GAAG,6CAA6C,CAAC;AAEpE,SAAS,UAAU,CAAC,IAAY,EAAE,MAAc;IAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAChE,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/D,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACtB,KAAK,MAAM,IAAI,IAAI,CAAC;QAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACnB,MAAM,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,MAAM,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAClC,CAAC;AAED,2EAA2E;AAC3E,SAAgB,WAAW,CAAC,IAAY,EAAE,IAAY,EAAE,MAAc;IACpE,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3D,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3D,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3D,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACvE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC/G,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE;YACjI,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtC,OAAO,EAAE,OAAO,EAAE,iBAAiB,IAAI,YAAY,MAAM,kBAAkB,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC9F,CAAC;QACD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC9D,IAAI,KAAK,GAAG,IAAI,CAAC;QACjB,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAgB,EAAE,UAAkB,EAAE,EAAE;YACzF,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC/C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,KAAK,GAAG,KAAK,CAAC;gBACd,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,SAAgB,mBAAmB,CAAC,IAAY,EAAE,IAAY,EAAE,MAAc;IAC5E,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,EAAE,OAAO,EAAE,IAAA,2BAAoB,EAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;AAClF,CAAC;AAED,uFAAuF;AACvF,SAAgB,qBAAqB,CAAC,MAAc,EAAE,KAAa;IACjE,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;IACvC,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;IACtC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACjC,IAAI,GAAG,KAAK,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YAAE,SAAS;QAC1D,MAAM,MAAM,GAAG,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/E,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAQD;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,OAA2B;IAC5D,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClF,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAClG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface MigrateTrackedOptions {
|
|
2
|
+
dir?: string;
|
|
3
|
+
/** After `--to-branch` on a committed board, also commit its removal from the code branch. */
|
|
4
|
+
commit?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function migrateToBranch(options?: MigrateTrackedOptions): void;
|
|
7
|
+
export declare function migrateToPlain(options?: MigrateTrackedOptions): void;
|
|
8
|
+
//# sourceMappingURL=migrate-tracked.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-tracked.d.ts","sourceRoot":"","sources":["../../src/commands/migrate-tracked.ts"],"names":[],"mappings":"AA+BA,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8FAA8F;IAC9F,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA0LD,wBAAgB,eAAe,CAAC,OAAO,GAAE,qBAA0B,GAAG,IAAI,CAuIzE;AAkBD,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,IAAI,CAgCxE"}
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.migrateToBranch = migrateToBranch;
|
|
40
|
+
exports.migrateToPlain = migrateToPlain;
|
|
41
|
+
/**
|
|
42
|
+
* `brainfile migrate --to-branch` / `--to-plain` (spec-9, phase 1).
|
|
43
|
+
*
|
|
44
|
+
* Moves an existing board between plain-directory storage and tracked storage
|
|
45
|
+
* (a linked worktree on the board branch inside a repo, or a standalone
|
|
46
|
+
* repository outside one). Every path preserves the board's files as they are
|
|
47
|
+
* on disk, uncommitted edits included; the committed-on-main case also
|
|
48
|
+
* preserves its history through `git subtree split`. A failed `--to-branch`
|
|
49
|
+
* rolls back everything it did, so the repository is left as it was found.
|
|
50
|
+
*/
|
|
51
|
+
const crypto = __importStar(require("crypto"));
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const child_process_1 = require("child_process");
|
|
55
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
56
|
+
const core_1 = require("@brainfile/core");
|
|
57
|
+
const board_repo_1 = require("../utils/board-repo");
|
|
58
|
+
const board_sync_1 = require("../utils/board-sync");
|
|
59
|
+
const SKIP_TOP_LEVEL = new Set(['.git']);
|
|
60
|
+
const STATE_DIR = 'state';
|
|
61
|
+
function locateBoard(rootDir) {
|
|
62
|
+
const found = (0, core_1.findBrainfile)(rootDir, { stopAtGitRoot: true });
|
|
63
|
+
if (!found || found.kind !== 'dotdir') {
|
|
64
|
+
throw new Error(`No .brainfile/ board found from ${rootDir}.`);
|
|
65
|
+
}
|
|
66
|
+
return path.dirname(found.absolutePath);
|
|
67
|
+
}
|
|
68
|
+
function copyBoardContents(from, to) {
|
|
69
|
+
for (const entry of fs.readdirSync(from)) {
|
|
70
|
+
if (SKIP_TOP_LEVEL.has(entry))
|
|
71
|
+
continue;
|
|
72
|
+
// verbatimSymlinks: a relative link must not be rewritten to point into the copy being deleted.
|
|
73
|
+
fs.cpSync(path.join(from, entry), path.join(to, entry), { recursive: true, force: true, verbatimSymlinks: true });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Make `to` hold exactly what `from` holds on disk (its `.git` aside): files
|
|
78
|
+
* edited, added, or deleted since the last commit all carry over.
|
|
79
|
+
*/
|
|
80
|
+
function mirrorBoardContents(from, to) {
|
|
81
|
+
for (const entry of fs.readdirSync(to)) {
|
|
82
|
+
if (SKIP_TOP_LEVEL.has(entry))
|
|
83
|
+
continue;
|
|
84
|
+
fs.rmSync(path.join(to, entry), { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
copyBoardContents(from, to);
|
|
87
|
+
}
|
|
88
|
+
/** Every board file (local-only `state/` excluded) mapped to a sha256 of its contents. */
|
|
89
|
+
function hashBoardFiles(dir) {
|
|
90
|
+
const out = new Map();
|
|
91
|
+
const walk = (current, prefix) => {
|
|
92
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
93
|
+
if (prefix === '' && (SKIP_TOP_LEVEL.has(entry.name) || entry.name === STATE_DIR))
|
|
94
|
+
continue;
|
|
95
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
96
|
+
const full = path.join(current, entry.name);
|
|
97
|
+
if (entry.isDirectory())
|
|
98
|
+
walk(full, rel);
|
|
99
|
+
else if (entry.isSymbolicLink())
|
|
100
|
+
out.set(rel, `link:${fs.readlinkSync(full)}`);
|
|
101
|
+
else
|
|
102
|
+
out.set(rel, crypto.createHash('sha256').update(fs.readFileSync(full)).digest('hex'));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
walk(dir, '');
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
function asidePath(dotDir) {
|
|
109
|
+
const candidate = `${dotDir}.migrating`;
|
|
110
|
+
if (fs.existsSync(candidate))
|
|
111
|
+
throw new Error(`${candidate} already exists; remove it before migrating.`);
|
|
112
|
+
return candidate;
|
|
113
|
+
}
|
|
114
|
+
function restoreAside(aside, dotDir) {
|
|
115
|
+
if (fs.existsSync(dotDir))
|
|
116
|
+
fs.rmSync(dotDir, { recursive: true, force: true });
|
|
117
|
+
fs.renameSync(aside, dotDir);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Every original file must be in the migrated board with the same contents.
|
|
121
|
+
* The move may add `.gitattributes` (merge strategies, appended to an
|
|
122
|
+
* existing one) and `.gitkeep` files; nothing else may differ. Throws on a
|
|
123
|
+
* mismatch and leaves both copies in place for the caller to roll back.
|
|
124
|
+
*/
|
|
125
|
+
function verifyBoardContents(aside, dotDir) {
|
|
126
|
+
const before = hashBoardFiles(aside);
|
|
127
|
+
const after = hashBoardFiles(dotDir);
|
|
128
|
+
const problems = [];
|
|
129
|
+
for (const [file, hash] of before) {
|
|
130
|
+
if (!after.has(file))
|
|
131
|
+
problems.push(`missing ${file}`);
|
|
132
|
+
else if (after.get(file) === hash)
|
|
133
|
+
continue;
|
|
134
|
+
else if (file === '.gitattributes' && attributesExtend(aside, dotDir))
|
|
135
|
+
continue;
|
|
136
|
+
else
|
|
137
|
+
problems.push(`changed ${file}`);
|
|
138
|
+
}
|
|
139
|
+
for (const file of after.keys()) {
|
|
140
|
+
if (before.has(file))
|
|
141
|
+
continue;
|
|
142
|
+
if (file === '.gitattributes' || file.endsWith('/.gitkeep'))
|
|
143
|
+
continue;
|
|
144
|
+
problems.push(`unexpected ${file}`);
|
|
145
|
+
}
|
|
146
|
+
if (problems.length > 0) {
|
|
147
|
+
const shown = problems.slice(0, 5).join(', ');
|
|
148
|
+
const more = problems.length > 5 ? ` (+${problems.length - 5} more)` : '';
|
|
149
|
+
throw new Error(`Board contents differ after migration: ${shown}${more}.`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function attributesExtend(aside, dotDir) {
|
|
153
|
+
const lines = (dir) => fs.readFileSync(path.join(dir, '.gitattributes'), 'utf-8').split('\n').map((l) => l.trim());
|
|
154
|
+
const after = new Set(lines(dotDir));
|
|
155
|
+
return lines(aside).every((line) => after.has(line));
|
|
156
|
+
}
|
|
157
|
+
/** Staged changes under `relPath` as a binary patch, untrimmed so it applies back verbatim. */
|
|
158
|
+
function stagedPatch(relPath, repoRoot) {
|
|
159
|
+
const r = (0, child_process_1.spawnSync)('git', ['diff', '--cached', '--binary', '--', relPath], { cwd: repoRoot, encoding: 'utf-8' });
|
|
160
|
+
return r.status === 0 ? r.stdout : '';
|
|
161
|
+
}
|
|
162
|
+
/** `git apply --cached` with a patch on stdin. */
|
|
163
|
+
function applyToIndex(patch, repoRoot) {
|
|
164
|
+
const r = (0, child_process_1.spawnSync)('git', ['apply', '--cached', '--binary', '-'], { cwd: repoRoot, input: patch, encoding: 'utf-8' });
|
|
165
|
+
return r.status === 0;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Undo log for `--to-branch`. Each step that changes the repository records
|
|
169
|
+
* itself; `undo()` reverses them so a failure leaves the repo as it was found.
|
|
170
|
+
*/
|
|
171
|
+
class MigrationRollback {
|
|
172
|
+
constructor(repoRoot, dotDir, aside, branch, relDotDir) {
|
|
173
|
+
this.repoRoot = repoRoot;
|
|
174
|
+
this.dotDir = dotDir;
|
|
175
|
+
this.aside = aside;
|
|
176
|
+
this.branch = branch;
|
|
177
|
+
this.relDotDir = relDotDir;
|
|
178
|
+
this.branchMayExist = false;
|
|
179
|
+
this.indexChanged = false;
|
|
180
|
+
this.stagedBefore = '';
|
|
181
|
+
this.movedAside = false;
|
|
182
|
+
}
|
|
183
|
+
/** Call just before anything may create the board branch (it did not exist when the migration started). */
|
|
184
|
+
creatingBranch() {
|
|
185
|
+
this.branchMayExist = true;
|
|
186
|
+
}
|
|
187
|
+
/** Call just before unstaging the board from the code branch. */
|
|
188
|
+
changingIndex() {
|
|
189
|
+
// Board changes the user had already staged are put back as they were.
|
|
190
|
+
this.stagedBefore = stagedPatch(this.relDotDir, this.repoRoot);
|
|
191
|
+
this.indexChanged = true;
|
|
192
|
+
}
|
|
193
|
+
moveAside() {
|
|
194
|
+
fs.renameSync(this.dotDir, this.aside);
|
|
195
|
+
this.movedAside = true;
|
|
196
|
+
}
|
|
197
|
+
/** Reverse every recorded step. Returns what could not be undone (empty when fully restored). */
|
|
198
|
+
undo() {
|
|
199
|
+
const problems = [];
|
|
200
|
+
const attempt = (what, step) => {
|
|
201
|
+
try {
|
|
202
|
+
step();
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
problems.push(`${what}: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
// Once the original is aside, whatever sits at dotDir was made by this
|
|
209
|
+
// migration. Judge by what exists, not by what reported success: `git
|
|
210
|
+
// worktree add` can register the worktree and still fail (a failing
|
|
211
|
+
// post-checkout hook, for one).
|
|
212
|
+
if (this.movedAside && (0, board_repo_1.boardRepoKind)(this.dotDir) === 'linked') {
|
|
213
|
+
attempt('remove the board worktree', () => {
|
|
214
|
+
const removed = (0, board_repo_1.git)(['worktree', 'remove', '--force', this.dotDir], this.repoRoot);
|
|
215
|
+
if (!removed.ok)
|
|
216
|
+
fs.rmSync(this.dotDir, { recursive: true, force: true });
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (this.movedAside)
|
|
220
|
+
attempt(`move ${this.aside} back`, () => restoreAside(this.aside, this.dotDir));
|
|
221
|
+
// The branch did not exist before, so a worktree still claiming it is ours and now stale.
|
|
222
|
+
if (this.branchMayExist && (0, board_repo_1.listWorktrees)(this.repoRoot).some((w) => w.branch === `refs/heads/${this.branch}`)) {
|
|
223
|
+
(0, board_repo_1.git)(['worktree', 'prune'], this.repoRoot);
|
|
224
|
+
}
|
|
225
|
+
if (this.branchMayExist && (0, board_repo_1.git)(['rev-parse', '--verify', '--quiet', `refs/heads/${this.branch}`], this.repoRoot).ok) {
|
|
226
|
+
attempt(`delete branch '${this.branch}'`, () => {
|
|
227
|
+
const deleted = (0, board_repo_1.git)(['branch', '-D', this.branch], this.repoRoot);
|
|
228
|
+
if (!deleted.ok)
|
|
229
|
+
throw new Error(deleted.stderr);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (this.indexChanged) {
|
|
233
|
+
attempt(`restore the index for ${this.relDotDir}`, () => {
|
|
234
|
+
const reset = (0, board_repo_1.git)(['reset', '-q', '--', this.relDotDir], this.repoRoot);
|
|
235
|
+
if (!reset.ok)
|
|
236
|
+
throw new Error(reset.stderr);
|
|
237
|
+
if (this.stagedBefore && !applyToIndex(this.stagedBefore, this.repoRoot)) {
|
|
238
|
+
throw new Error('could not re-stage the board changes that were staged before');
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return problems;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function migrateToBranch(options = {}) {
|
|
246
|
+
const rootDir = path.resolve(options.dir || process.cwd());
|
|
247
|
+
const dotDir = locateBoard(rootDir);
|
|
248
|
+
const kind = (0, board_repo_1.boardRepoKind)(dotDir);
|
|
249
|
+
const repoRoot = (0, board_repo_1.gitToplevel)(path.dirname(dotDir));
|
|
250
|
+
if (kind === 'linked') {
|
|
251
|
+
console.log(chalk_1.default.green('Board is already a worktree on its own branch.'));
|
|
252
|
+
console.log(chalk_1.default.gray(` ${dotDir}`));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!repoRoot) {
|
|
256
|
+
if (kind === 'standalone') {
|
|
257
|
+
console.log(chalk_1.default.green('Board is already its own git repository.'));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
(0, board_repo_1.initStandaloneBoardRepo)(dotDir);
|
|
261
|
+
(0, board_sync_1.ensureBoardAttributes)(dotDir);
|
|
262
|
+
(0, board_sync_1.registerMergeDriver)(dotDir);
|
|
263
|
+
(0, board_repo_1.commitBoard)(dotDir, { message: 'import board' });
|
|
264
|
+
console.log(chalk_1.default.green('Board is now its own git repository.'));
|
|
265
|
+
printMigratedStory(dotDir, null);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const branch = (0, board_repo_1.boardBranch)(repoRoot);
|
|
269
|
+
if ((0, board_repo_1.git)(['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], repoRoot).ok) {
|
|
270
|
+
const checkedOut = (0, board_repo_1.listWorktrees)(repoRoot).find((w) => w.branch === `refs/heads/${branch}`);
|
|
271
|
+
throw new Error(checkedOut
|
|
272
|
+
? `Branch '${branch}' is already checked out at ${checkedOut.path}.`
|
|
273
|
+
: `Branch '${branch}' already exists. Delete it (git branch -D ${branch}) or set brainfile.branch to another name.`);
|
|
274
|
+
}
|
|
275
|
+
const relDotDir = path.relative(repoRoot, dotDir).split(path.sep).join('/');
|
|
276
|
+
const trackedOnCodeBranch = (0, board_repo_1.git)(['ls-files', '--error-unmatch', relDotDir], repoRoot).ok;
|
|
277
|
+
const aside = asidePath(dotDir);
|
|
278
|
+
const rollback = new MigrationRollback(repoRoot, dotDir, aside, branch, relDotDir);
|
|
279
|
+
let done;
|
|
280
|
+
let uncommittedImported = false;
|
|
281
|
+
try {
|
|
282
|
+
if (trackedOnCodeBranch) {
|
|
283
|
+
// History-preserving split of the board's commits onto the new branch.
|
|
284
|
+
rollback.creatingBranch();
|
|
285
|
+
const split = (0, board_repo_1.git)(['subtree', 'split', '--quiet', `--prefix=${relDotDir}`, '-b', branch], repoRoot);
|
|
286
|
+
if (!split.ok) {
|
|
287
|
+
console.log(chalk_1.default.yellow('git subtree is unavailable; importing the board without its history.'));
|
|
288
|
+
}
|
|
289
|
+
rollback.changingIndex();
|
|
290
|
+
const rm = (0, board_repo_1.git)(['rm', '-r', '--quiet', '--cached', relDotDir], repoRoot);
|
|
291
|
+
if (!rm.ok)
|
|
292
|
+
throw new Error(`git rm --cached failed: ${rm.stderr}`);
|
|
293
|
+
rollback.moveAside();
|
|
294
|
+
if (split.ok) {
|
|
295
|
+
const add = (0, board_repo_1.git)(['worktree', 'add', '--quiet', dotDir, branch], repoRoot);
|
|
296
|
+
if (!add.ok)
|
|
297
|
+
throw new Error(`git worktree add failed: ${add.stderr || `exit status ${add.status}`}.`);
|
|
298
|
+
// The split carries committed history only. Bring the board over as it
|
|
299
|
+
// is on disk (uncommitted edits, new or git-ignored files, deletions)
|
|
300
|
+
// and record the difference as one commit on top of that history.
|
|
301
|
+
mirrorBoardContents(aside, dotDir);
|
|
302
|
+
if ((0, board_repo_1.boardIsDirty)(dotDir)) {
|
|
303
|
+
if (!(0, board_repo_1.commitBoard)(dotDir, { message: 'import uncommitted board changes' }) || (0, board_repo_1.boardIsDirty)(dotDir)) {
|
|
304
|
+
throw new Error('Could not commit the uncommitted board changes on the board branch.');
|
|
305
|
+
}
|
|
306
|
+
uncommittedImported = true;
|
|
307
|
+
}
|
|
308
|
+
// History carried over; the merge setup every tracked board needs did not.
|
|
309
|
+
(0, board_sync_1.ensureBoardAttributes)(dotDir);
|
|
310
|
+
(0, board_sync_1.registerMergeDriver)(dotDir);
|
|
311
|
+
(0, board_repo_1.commitBoard)(dotDir, { message: 'chore: merge attributes' });
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
(0, board_repo_1.createOrphanBoardWorktree)(repoRoot, dotDir, branch);
|
|
315
|
+
mirrorBoardContents(aside, dotDir);
|
|
316
|
+
(0, board_sync_1.ensureBoardAttributes)(dotDir);
|
|
317
|
+
(0, board_sync_1.registerMergeDriver)(dotDir);
|
|
318
|
+
(0, board_repo_1.commitBoard)(dotDir, { message: 'import board' });
|
|
319
|
+
}
|
|
320
|
+
verifyBoardContents(aside, dotDir);
|
|
321
|
+
done = `Board moved to branch '${branch}'${split.ok ? ' with its history' : ''}.`;
|
|
322
|
+
}
|
|
323
|
+
else if (kind === 'standalone') {
|
|
324
|
+
// Fold the standalone repository's history into the outer repo as the board branch.
|
|
325
|
+
rollback.creatingBranch();
|
|
326
|
+
const fetch = (0, board_repo_1.git)(['fetch', '--quiet', dotDir, `HEAD:refs/heads/${branch}`], repoRoot);
|
|
327
|
+
if (!fetch.ok)
|
|
328
|
+
throw new Error(`Could not import the board's history: ${fetch.stderr}`);
|
|
329
|
+
rollback.moveAside();
|
|
330
|
+
const add = (0, board_repo_1.git)(['worktree', 'add', '--quiet', dotDir, branch], repoRoot);
|
|
331
|
+
if (!add.ok)
|
|
332
|
+
throw new Error(`git worktree add failed: ${add.stderr || `exit status ${add.status}`}.`);
|
|
333
|
+
// Uncommitted work in the standalone repo comes along too.
|
|
334
|
+
mirrorBoardContents(aside, dotDir);
|
|
335
|
+
(0, board_sync_1.ensureBoardAttributes)(dotDir);
|
|
336
|
+
(0, board_sync_1.registerMergeDriver)(dotDir);
|
|
337
|
+
(0, board_repo_1.commitBoard)(dotDir, { message: 'import board' });
|
|
338
|
+
verifyBoardContents(aside, dotDir);
|
|
339
|
+
done = `Board history folded into branch '${branch}'.`;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Plain, untracked (usually gitignored) directory: import as an orphan branch.
|
|
343
|
+
rollback.moveAside();
|
|
344
|
+
rollback.creatingBranch();
|
|
345
|
+
(0, board_repo_1.createOrphanBoardWorktree)(repoRoot, dotDir, branch);
|
|
346
|
+
mirrorBoardContents(aside, dotDir);
|
|
347
|
+
(0, board_sync_1.ensureBoardAttributes)(dotDir);
|
|
348
|
+
(0, board_sync_1.registerMergeDriver)(dotDir);
|
|
349
|
+
(0, board_repo_1.commitBoard)(dotDir, { message: 'import board' });
|
|
350
|
+
verifyBoardContents(aside, dotDir);
|
|
351
|
+
done = `Board imported onto branch '${branch}'.`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
356
|
+
const leftovers = rollback.undo();
|
|
357
|
+
throw new Error(leftovers.length === 0
|
|
358
|
+
? `${message} Nothing was changed: the board is back where it was.`
|
|
359
|
+
: `${message} Rolling back was incomplete; could not ${leftovers.join('; ')}. The original board is in ${fs.existsSync(aside) ? aside : dotDir}.`);
|
|
360
|
+
}
|
|
361
|
+
fs.rmSync(aside, { recursive: true, force: true });
|
|
362
|
+
(0, board_repo_1.ensureExcludeEntry)(repoRoot);
|
|
363
|
+
console.log(chalk_1.default.green(done));
|
|
364
|
+
if (uncommittedImported) {
|
|
365
|
+
console.log(chalk_1.default.gray(' Board changes that were not committed yet came along as one more commit on that branch.'));
|
|
366
|
+
}
|
|
367
|
+
if (trackedOnCodeBranch) {
|
|
368
|
+
if (options.commit) {
|
|
369
|
+
const commit = (0, board_repo_1.git)(['commit', '--quiet', '--no-verify', '-m', `chore: move the board to the ${branch} branch`], repoRoot);
|
|
370
|
+
if (!commit.ok)
|
|
371
|
+
throw new Error(`Could not commit the removal on the code branch: ${commit.stderr}`);
|
|
372
|
+
console.log(chalk_1.default.gray(' The old copy on your code branch was removed in a commit; the board itself is on its own branch.'));
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
console.log(chalk_1.default.gray(' Your code branch still tracks the old copy. It is staged to be dropped there (the board itself is safe). Commit that:'));
|
|
376
|
+
console.log(chalk_1.default.cyan(` git commit -m "chore: move the board to the ${branch} branch"`));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
printMigratedStory(dotDir, branch);
|
|
380
|
+
}
|
|
381
|
+
function printMigratedStory(dotDir, branch) {
|
|
382
|
+
const say = (text = '') => console.log(text ? ` ${text}` : '');
|
|
383
|
+
say();
|
|
384
|
+
say(`Same files, same place: ${path.relative(process.cwd(), dotDir) || '.'}/`);
|
|
385
|
+
if (branch) {
|
|
386
|
+
say(`Every change is now a git commit on a separate '${branch}' branch, kept out`);
|
|
387
|
+
say('of your code and pull requests. Only on this machine until you share it.');
|
|
388
|
+
say(chalk_1.default.gray('(A plain git push never sends it; git push --all or --mirror would.)'));
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
say('Every change is now a git commit. It is only on this machine until you share it.');
|
|
392
|
+
}
|
|
393
|
+
say();
|
|
394
|
+
say(`${chalk_1.default.gray('Share it:')} ${chalk_1.default.cyan('brainfile sync --set-remote origin')}`);
|
|
395
|
+
say(`${chalk_1.default.gray('Check it:')} ${chalk_1.default.cyan('brainfile where')}`);
|
|
396
|
+
}
|
|
397
|
+
function migrateToPlain(options = {}) {
|
|
398
|
+
const rootDir = path.resolve(options.dir || process.cwd());
|
|
399
|
+
const dotDir = locateBoard(rootDir);
|
|
400
|
+
const kind = (0, board_repo_1.boardRepoKind)(dotDir);
|
|
401
|
+
if (kind === null) {
|
|
402
|
+
console.log(chalk_1.default.green('Board is already a plain directory.'));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (kind === 'standalone') {
|
|
406
|
+
fs.rmSync(path.join(dotDir, '.git'), { recursive: true, force: true });
|
|
407
|
+
console.log(chalk_1.default.green('Board is now a plain directory (its git history was removed).'));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const repoRoot = (0, board_repo_1.gitToplevel)(path.dirname(dotDir));
|
|
411
|
+
if (!repoRoot)
|
|
412
|
+
throw new Error('Could not find the repository that owns this board worktree.');
|
|
413
|
+
const branch = (0, board_repo_1.boardBranch)(repoRoot);
|
|
414
|
+
const aside = asidePath(dotDir);
|
|
415
|
+
fs.mkdirSync(aside);
|
|
416
|
+
copyBoardContents(dotDir, aside);
|
|
417
|
+
const remove = (0, board_repo_1.git)(['worktree', 'remove', '--force', dotDir], repoRoot);
|
|
418
|
+
if (!remove.ok) {
|
|
419
|
+
fs.rmSync(aside, { recursive: true, force: true });
|
|
420
|
+
throw new Error(`git worktree remove failed: ${remove.stderr}`);
|
|
421
|
+
}
|
|
422
|
+
fs.renameSync(aside, dotDir);
|
|
423
|
+
(0, board_repo_1.removeExcludeEntry)(repoRoot);
|
|
424
|
+
console.log(chalk_1.default.green('Board is now a plain directory.'));
|
|
425
|
+
console.log(chalk_1.default.gray(` Branch '${branch}' still holds its history. Delete it with: git branch -D ${branch}`));
|
|
426
|
+
console.log(chalk_1.default.gray(' Add .brainfile/ to .gitignore if it should stay out of the code branch.'));
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=migrate-tracked.js.map
|