isomorphic-jj 1.1.2 → 1.2.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 +48 -0
- package/dist/index.cjs +260 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +260 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/api/repository.js +151 -17
- package/src/core/bookmark-store.js +16 -4
- package/src/core/jj-checkout.js +7 -1
- package/src/core/jj-operation-store.js +7 -1
- package/src/core/jj-tree-state.js +7 -1
- package/src/core/jj-view-store.js +7 -1
- package/src/core/revset-engine.js +18 -0
- package/src/core/user-config.js +47 -2
package/README.md
CHANGED
|
@@ -202,6 +202,54 @@ await jj.amend();
|
|
|
202
202
|
await jj.undo();
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
+
### Understanding Working Copy vs Parent
|
|
206
|
+
|
|
207
|
+
A common source of confusion: **the working copy and its parent are different changes**.
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
// After init, you have a working copy change (with changeId)
|
|
211
|
+
const status1 = await jj.status();
|
|
212
|
+
console.log(status1.workingCopy.changeId); // e.g., "abc123..."
|
|
213
|
+
console.log(status1.workingCopy.description); // "(no description set)"
|
|
214
|
+
|
|
215
|
+
// When you describe(), you're updating the PARENT change
|
|
216
|
+
await jj.describe({ message: 'Add feature' });
|
|
217
|
+
|
|
218
|
+
// The working copy changeId is STILL the same!
|
|
219
|
+
const status2 = await jj.status();
|
|
220
|
+
console.log(status2.workingCopy.changeId); // SAME: "abc123..."
|
|
221
|
+
console.log(status2.workingCopy.description); // UPDATED: "Add feature"
|
|
222
|
+
|
|
223
|
+
// When you new(), you create a NEW working copy
|
|
224
|
+
await jj.new({ message: 'Next change' });
|
|
225
|
+
|
|
226
|
+
const status3 = await jj.status();
|
|
227
|
+
console.log(status3.workingCopy.changeId); // DIFFERENT: "def456..."
|
|
228
|
+
console.log(status3.workingCopy.description); // "Next change"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Key points:**
|
|
232
|
+
- **Working copy IS a change** with its own `changeId` (always `@` in revsets)
|
|
233
|
+
- **`describe()`** updates the parent's description, not the working copy's changeId
|
|
234
|
+
- **`status()`** returns the working copy changeId (which may or may not have a description)
|
|
235
|
+
- **`new()`** creates a new working copy change and makes the previous one the parent
|
|
236
|
+
- **Reading metadata immediately after write** shows working copy state, not parent state
|
|
237
|
+
|
|
238
|
+
**Common pattern:**
|
|
239
|
+
```javascript
|
|
240
|
+
// 1. Edit files
|
|
241
|
+
await jj.write({ path: 'file.js', data: 'content' });
|
|
242
|
+
|
|
243
|
+
// 2. Describe the change (updates parent)
|
|
244
|
+
await jj.describe({ message: 'Add file' });
|
|
245
|
+
|
|
246
|
+
// 3. Create new working copy for next change
|
|
247
|
+
await jj.new({ message: 'Next feature' });
|
|
248
|
+
|
|
249
|
+
// Or use commit() to combine describe() + new()
|
|
250
|
+
await jj.commit({ message: 'Add file', nextMessage: 'Next feature' });
|
|
251
|
+
```
|
|
252
|
+
|
|
205
253
|
---
|
|
206
254
|
|
|
207
255
|
## Features
|
package/dist/index.cjs
CHANGED
|
@@ -1312,6 +1312,7 @@ class BookmarkStore {
|
|
|
1312
1312
|
this.storage = storage;
|
|
1313
1313
|
this.local = new Map();
|
|
1314
1314
|
this.remote = new Map(); // remote → (bookmark → changeId)
|
|
1315
|
+
this.tracking = {}; // bookmark → { remote, ref } tracking info (v0.35.0)
|
|
1315
1316
|
}
|
|
1316
1317
|
|
|
1317
1318
|
/**
|
|
@@ -1354,6 +1355,7 @@ class BookmarkStore {
|
|
|
1354
1355
|
|
|
1355
1356
|
this.local.clear();
|
|
1356
1357
|
this.remote.clear();
|
|
1358
|
+
this.tracking = data.tracked || {}; // Load tracking info (v0.35.0)
|
|
1357
1359
|
|
|
1358
1360
|
// Load local bookmarks
|
|
1359
1361
|
for (const [name, target] of Object.entries(data.local)) {
|
|
@@ -1391,7 +1393,7 @@ class BookmarkStore {
|
|
|
1391
1393
|
version: 1,
|
|
1392
1394
|
local,
|
|
1393
1395
|
remote,
|
|
1394
|
-
tracked: {},
|
|
1396
|
+
tracked: this.tracking || {}, // Save tracking info (v0.35.0)
|
|
1395
1397
|
});
|
|
1396
1398
|
}
|
|
1397
1399
|
|
|
@@ -1504,13 +1506,23 @@ class BookmarkStore {
|
|
|
1504
1506
|
async list() {
|
|
1505
1507
|
const result = [];
|
|
1506
1508
|
|
|
1507
|
-
// Add local bookmarks
|
|
1509
|
+
// Add local bookmarks with tracking info (v0.35.0)
|
|
1508
1510
|
for (const [name, target] of this.local.entries()) {
|
|
1509
|
-
|
|
1511
|
+
const bookmark = {
|
|
1510
1512
|
name,
|
|
1511
1513
|
changeId: target,
|
|
1512
1514
|
remote: null,
|
|
1513
|
-
}
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
// Include tracking info if available
|
|
1518
|
+
if (this.tracking && this.tracking[name]) {
|
|
1519
|
+
bookmark.tracking = {
|
|
1520
|
+
remote: this.tracking[name].remote,
|
|
1521
|
+
ref: this.tracking[name].remoteName || name,
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
result.push(bookmark);
|
|
1514
1526
|
}
|
|
1515
1527
|
|
|
1516
1528
|
// Add remote bookmarks
|
|
@@ -1832,6 +1844,24 @@ class RevsetEngine {
|
|
|
1832
1844
|
}
|
|
1833
1845
|
}
|
|
1834
1846
|
|
|
1847
|
+
// v0.36.0: visible() - non-abandoned changes
|
|
1848
|
+
if (trimmed === 'visible()') {
|
|
1849
|
+
await this.graph.load();
|
|
1850
|
+
const all = this.graph.getAll();
|
|
1851
|
+
return all
|
|
1852
|
+
.filter(c => !c.abandoned)
|
|
1853
|
+
.map(c => c.changeId);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
// v0.36.0: hidden() - abandoned changes
|
|
1857
|
+
if (trimmed === 'hidden()') {
|
|
1858
|
+
await this.graph.load();
|
|
1859
|
+
const all = this.graph.getAll();
|
|
1860
|
+
return all
|
|
1861
|
+
.filter(c => c.abandoned === true)
|
|
1862
|
+
.map(c => c.changeId);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1835
1865
|
// ancestors(changeId) - all ancestors including the change itself
|
|
1836
1866
|
const ancestorsMatch = trimmed.match(/^ancestors\(([0-9a-f]{32})\)$/);
|
|
1837
1867
|
if (ancestorsMatch) {
|
|
@@ -4279,9 +4309,15 @@ class UserConfig {
|
|
|
4279
4309
|
}
|
|
4280
4310
|
|
|
4281
4311
|
/**
|
|
4282
|
-
* Load config from storage
|
|
4312
|
+
* Load config from storage and/or programmatic object
|
|
4313
|
+
* v0.35.0: Also loads workspace-specific config from workspace-config.json
|
|
4314
|
+
* v0.36.0: Accepts optional config object for programmatic configuration
|
|
4315
|
+
*
|
|
4316
|
+
* @param {Object} [opts] - Optional config options
|
|
4317
|
+
* @param {Object} [opts.workspace] - Workspace-specific config to merge (highest priority)
|
|
4318
|
+
* @param {Object} [opts.override] - Config object to merge over loaded config
|
|
4283
4319
|
*/
|
|
4284
|
-
async load() {
|
|
4320
|
+
async load(opts = {}) {
|
|
4285
4321
|
try {
|
|
4286
4322
|
const data = await this.storage.read('config.json');
|
|
4287
4323
|
if (data) {
|
|
@@ -4291,6 +4327,45 @@ class UserConfig {
|
|
|
4291
4327
|
// No config file yet, use defaults
|
|
4292
4328
|
await this.init();
|
|
4293
4329
|
}
|
|
4330
|
+
|
|
4331
|
+
// Merge programmatic override config if provided
|
|
4332
|
+
if (opts.override) {
|
|
4333
|
+
this.config = this._deepMerge(this.config, opts.override);
|
|
4334
|
+
}
|
|
4335
|
+
|
|
4336
|
+
// Load workspace-specific config from file (v0.35.0)
|
|
4337
|
+
try {
|
|
4338
|
+
const workspaceData = await this.storage.read('workspace-config.json');
|
|
4339
|
+
if (workspaceData) {
|
|
4340
|
+
// Merge workspace config over global config (workspace takes precedence)
|
|
4341
|
+
this.config = this._deepMerge(this.config, workspaceData);
|
|
4342
|
+
}
|
|
4343
|
+
} catch (error) {
|
|
4344
|
+
// No workspace config, that's fine
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
// Merge programmatic workspace config if provided (highest priority)
|
|
4348
|
+
if (opts.workspace) {
|
|
4349
|
+
this.config = this._deepMerge(this.config, opts.workspace);
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4353
|
+
/**
|
|
4354
|
+
* Deep merge two config objects (workspace overrides global)
|
|
4355
|
+
* @private
|
|
4356
|
+
*/
|
|
4357
|
+
_deepMerge(target, source) {
|
|
4358
|
+
const result = { ...target };
|
|
4359
|
+
|
|
4360
|
+
for (const key in source) {
|
|
4361
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
4362
|
+
result[key] = this._deepMerge(target[key] || {}, source[key]);
|
|
4363
|
+
} else {
|
|
4364
|
+
result[key] = source[key];
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
return result;
|
|
4294
4369
|
}
|
|
4295
4370
|
|
|
4296
4371
|
/**
|
|
@@ -4394,11 +4469,17 @@ class UserConfig {
|
|
|
4394
4469
|
const __filename$4 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
4395
4470
|
const __dirname$4 = path.dirname(__filename$4);
|
|
4396
4471
|
|
|
4472
|
+
// Determine proto path based on whether we're running from dist or src
|
|
4473
|
+
const inDist$3 = __dirname$4.includes('/dist') || __dirname$4.includes('\\dist');
|
|
4474
|
+
const protoDir$3 = inDist$3
|
|
4475
|
+
? path.join(__dirname$4, '..', 'src', 'protos')
|
|
4476
|
+
: path.join(__dirname$4, '..', 'protos');
|
|
4477
|
+
|
|
4397
4478
|
class JJCheckout {
|
|
4398
4479
|
constructor(fs, dir) {
|
|
4399
4480
|
this.fs = fs;
|
|
4400
4481
|
this.dir = dir;
|
|
4401
|
-
this.protoPath = path.join(
|
|
4482
|
+
this.protoPath = path.join(protoDir$3, 'local_working_copy.proto');
|
|
4402
4483
|
}
|
|
4403
4484
|
|
|
4404
4485
|
/**
|
|
@@ -4470,11 +4551,17 @@ class JJCheckout {
|
|
|
4470
4551
|
const __filename$3 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
4471
4552
|
const __dirname$3 = path.dirname(__filename$3);
|
|
4472
4553
|
|
|
4554
|
+
// Determine proto path based on whether we're running from dist or src
|
|
4555
|
+
const inDist$2 = __dirname$3.includes('/dist') || __dirname$3.includes('\\dist');
|
|
4556
|
+
const protoDir$2 = inDist$2
|
|
4557
|
+
? path.join(__dirname$3, '..', 'src', 'protos')
|
|
4558
|
+
: path.join(__dirname$3, '..', 'protos');
|
|
4559
|
+
|
|
4473
4560
|
class JJTreeState {
|
|
4474
4561
|
constructor(fs, dir) {
|
|
4475
4562
|
this.fs = fs;
|
|
4476
4563
|
this.dir = dir;
|
|
4477
|
-
this.protoPath = path.join(
|
|
4564
|
+
this.protoPath = path.join(protoDir$2, 'local_working_copy.proto');
|
|
4478
4565
|
}
|
|
4479
4566
|
|
|
4480
4567
|
/**
|
|
@@ -4562,11 +4649,17 @@ class JJTreeState {
|
|
|
4562
4649
|
const __filename$2 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
4563
4650
|
const __dirname$2 = path.dirname(__filename$2);
|
|
4564
4651
|
|
|
4652
|
+
// Determine proto path based on whether we're running from dist or src
|
|
4653
|
+
const inDist$1 = __dirname$2.includes('/dist') || __dirname$2.includes('\\dist');
|
|
4654
|
+
const protoDir$1 = inDist$1
|
|
4655
|
+
? path.join(__dirname$2, '..', 'src', 'protos')
|
|
4656
|
+
: path.join(__dirname$2, '..', 'protos');
|
|
4657
|
+
|
|
4565
4658
|
class JJOperationStore {
|
|
4566
4659
|
constructor(fs, dir) {
|
|
4567
4660
|
this.fs = fs;
|
|
4568
4661
|
this.dir = dir;
|
|
4569
|
-
this.protoPath = path.join(
|
|
4662
|
+
this.protoPath = path.join(protoDir$1, 'simple_op_store.proto');
|
|
4570
4663
|
}
|
|
4571
4664
|
|
|
4572
4665
|
/**
|
|
@@ -4680,11 +4773,17 @@ class JJOperationStore {
|
|
|
4680
4773
|
const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
4681
4774
|
const __dirname$1 = path.dirname(__filename$1);
|
|
4682
4775
|
|
|
4776
|
+
// Determine proto path based on whether we're running from dist or src
|
|
4777
|
+
const inDist = __dirname$1.includes('/dist') || __dirname$1.includes('\\dist');
|
|
4778
|
+
const protoDir = inDist
|
|
4779
|
+
? path.join(__dirname$1, '..', 'src', 'protos')
|
|
4780
|
+
: path.join(__dirname$1, '..', 'protos');
|
|
4781
|
+
|
|
4683
4782
|
class JJViewStore {
|
|
4684
4783
|
constructor(fs, dir) {
|
|
4685
4784
|
this.fs = fs;
|
|
4686
4785
|
this.dir = dir;
|
|
4687
|
-
this.protoPath = path.join(
|
|
4786
|
+
this.protoPath = path.join(protoDir, 'simple_op_store.proto');
|
|
4688
4787
|
}
|
|
4689
4788
|
|
|
4690
4789
|
/**
|
|
@@ -6993,7 +7092,8 @@ async function createJJ(options) {
|
|
|
6993
7092
|
* @param {Object} args - Arguments
|
|
6994
7093
|
* @param {string} [args.revset='all()'] - Revset expression to filter changes
|
|
6995
7094
|
* @param {number} [args.limit] - Maximum number of changes to return
|
|
6996
|
-
* @
|
|
7095
|
+
* @param {boolean} [args.count=false] - If true, return count instead of changes (v0.36.0)
|
|
7096
|
+
* @returns {Promise<Array|number>} Array of changes or count if args.count is true
|
|
6997
7097
|
*/
|
|
6998
7098
|
async log(args = {}) {
|
|
6999
7099
|
await graph.load();
|
|
@@ -7002,6 +7102,11 @@ async function createJJ(options) {
|
|
|
7002
7102
|
const revsetExpr = args.revset || 'all()';
|
|
7003
7103
|
const changeIds = await revset.evaluate(revsetExpr);
|
|
7004
7104
|
|
|
7105
|
+
// If count mode, return count early (v0.36.0 feature)
|
|
7106
|
+
if (args.count) {
|
|
7107
|
+
return changeIds.length;
|
|
7108
|
+
}
|
|
7109
|
+
|
|
7005
7110
|
let changes = [];
|
|
7006
7111
|
for (const changeId of changeIds) {
|
|
7007
7112
|
const change = await graph.getChange(changeId);
|
|
@@ -8022,9 +8127,18 @@ async function createJJ(options) {
|
|
|
8022
8127
|
* @returns {Promise<Object>} The new backout change including changeId, description, and backedOut field
|
|
8023
8128
|
*/
|
|
8024
8129
|
async backout(args) {
|
|
8130
|
+
// Normalize: accept common parameter aliases
|
|
8131
|
+
if (args && args.change && !args.revision) {
|
|
8132
|
+
args = { ...args, revision: args.change };
|
|
8133
|
+
} else if (args && args.changeId && !args.revision) {
|
|
8134
|
+
args = { ...args, revision: args.changeId };
|
|
8135
|
+
} else if (args && args.target && !args.revision) {
|
|
8136
|
+
args = { ...args, revision: args.target };
|
|
8137
|
+
}
|
|
8138
|
+
|
|
8025
8139
|
if (!args || !args.revision) {
|
|
8026
8140
|
throw new JJError('INVALID_ARGUMENT', 'Missing revision argument', {
|
|
8027
|
-
suggestion: 'Provide the change ID to back out: { revision: "abc123..." }',
|
|
8141
|
+
suggestion: 'Provide the change ID to back out: { revision: "abc123..." } (or use change/changeId/target)',
|
|
8028
8142
|
});
|
|
8029
8143
|
}
|
|
8030
8144
|
|
|
@@ -9059,6 +9173,15 @@ async function createJJ(options) {
|
|
|
9059
9173
|
* @param {string[]} [args.paths] - Specific paths to include in first split (full implementation planned for future)
|
|
9060
9174
|
*/
|
|
9061
9175
|
async split(args) {
|
|
9176
|
+
// Normalize: accept common parameter aliases
|
|
9177
|
+
if (args && args.change && !args.changeId) {
|
|
9178
|
+
args = { ...args, changeId: args.change };
|
|
9179
|
+
} else if (args && args.revision && !args.changeId) {
|
|
9180
|
+
args = { ...args, changeId: args.revision };
|
|
9181
|
+
} else if (args && args.target && !args.changeId) {
|
|
9182
|
+
args = { ...args, changeId: args.target };
|
|
9183
|
+
}
|
|
9184
|
+
|
|
9062
9185
|
// Check for interactive mode (not supported)
|
|
9063
9186
|
if (args.interactive) {
|
|
9064
9187
|
throw new JJError(
|
|
@@ -10770,9 +10893,21 @@ async function createJJ(options) {
|
|
|
10770
10893
|
* @returns {Promise<Object>} Bookmark info
|
|
10771
10894
|
*/
|
|
10772
10895
|
async set(args) {
|
|
10896
|
+
// Normalize: accept common parameter aliases
|
|
10897
|
+
if (args) {
|
|
10898
|
+
if (args.target && !args.changeId) {
|
|
10899
|
+
args = { ...args, changeId: args.target };
|
|
10900
|
+
} else if (args.change && !args.changeId) {
|
|
10901
|
+
args = { ...args, changeId: args.change };
|
|
10902
|
+
} else if (args.revision && !args.changeId) {
|
|
10903
|
+
args = { ...args, changeId: args.revision };
|
|
10904
|
+
}
|
|
10905
|
+
}
|
|
10906
|
+
|
|
10773
10907
|
if (!args || !args.name || !args.changeId) {
|
|
10774
10908
|
throw new JJError('INVALID_ARGUMENT', 'Missing name or changeId', {
|
|
10775
|
-
|
|
10909
|
+
args,
|
|
10910
|
+
suggestion: 'Provide both: { name: "main", changeId: "abc123..." } (or use target/change/revision instead of changeId)',
|
|
10776
10911
|
});
|
|
10777
10912
|
}
|
|
10778
10913
|
|
|
@@ -10808,6 +10943,17 @@ async function createJJ(options) {
|
|
|
10808
10943
|
* @returns {Promise<Object>} Bookmark info
|
|
10809
10944
|
*/
|
|
10810
10945
|
async create(args) {
|
|
10946
|
+
// Normalize: accept common parameter aliases
|
|
10947
|
+
if (args) {
|
|
10948
|
+
if (args.target && !args.changeId) {
|
|
10949
|
+
args = { ...args, changeId: args.target };
|
|
10950
|
+
} else if (args.change && !args.changeId) {
|
|
10951
|
+
args = { ...args, changeId: args.change };
|
|
10952
|
+
} else if (args.revision && !args.changeId) {
|
|
10953
|
+
args = { ...args, changeId: args.revision };
|
|
10954
|
+
}
|
|
10955
|
+
}
|
|
10956
|
+
|
|
10811
10957
|
if (!args || !args.name) {
|
|
10812
10958
|
throw new JJError('INVALID_ARGUMENT', 'Missing name argument', {
|
|
10813
10959
|
suggestion: 'Provide: { name: "main", changeId: "abc123..." } or { name: "main" } for working copy',
|
|
@@ -10860,9 +11006,23 @@ async function createJJ(options) {
|
|
|
10860
11006
|
* @returns {Promise<Object>} Move result
|
|
10861
11007
|
*/
|
|
10862
11008
|
async move(args) {
|
|
11009
|
+
// Normalize: accept common parameter aliases
|
|
11010
|
+
if (args) {
|
|
11011
|
+
if (args.target && !args.to) {
|
|
11012
|
+
args = { ...args, to: args.target };
|
|
11013
|
+
} else if (args.changeId && !args.to) {
|
|
11014
|
+
args = { ...args, to: args.changeId };
|
|
11015
|
+
} else if (args.change && !args.to) {
|
|
11016
|
+
args = { ...args, to: args.change };
|
|
11017
|
+
} else if (args.revision && !args.to) {
|
|
11018
|
+
args = { ...args, to: args.revision };
|
|
11019
|
+
}
|
|
11020
|
+
}
|
|
11021
|
+
|
|
10863
11022
|
if (!args || !args.name || !args.to) {
|
|
10864
11023
|
throw new JJError('INVALID_ARGUMENT', 'Missing name or to', {
|
|
10865
|
-
|
|
11024
|
+
args,
|
|
11025
|
+
suggestion: 'Provide both: { name: "main", to: "abc123..." } (or use target/changeId/change/revision instead of to)',
|
|
10866
11026
|
});
|
|
10867
11027
|
}
|
|
10868
11028
|
|
|
@@ -11263,21 +11423,13 @@ async function createJJ(options) {
|
|
|
11263
11423
|
});
|
|
11264
11424
|
}
|
|
11265
11425
|
|
|
11266
|
-
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
const parts = key.split('.');
|
|
11270
|
-
let value = userConfig.config;
|
|
11271
|
-
|
|
11272
|
-
for (const part of parts) {
|
|
11273
|
-
if (value && typeof value === 'object') {
|
|
11274
|
-
value = value[part];
|
|
11275
|
-
} else {
|
|
11276
|
-
return null;
|
|
11277
|
-
}
|
|
11426
|
+
// Ensure config is loaded at least once
|
|
11427
|
+
if (!userConfig.config) {
|
|
11428
|
+
await userConfig.load();
|
|
11278
11429
|
}
|
|
11279
11430
|
|
|
11280
|
-
|
|
11431
|
+
// Use userConfig.get() to preserve programmatic config
|
|
11432
|
+
return userConfig.get(key);
|
|
11281
11433
|
},
|
|
11282
11434
|
|
|
11283
11435
|
/**
|
|
@@ -11326,6 +11478,87 @@ async function createJJ(options) {
|
|
|
11326
11478
|
await userConfig.load();
|
|
11327
11479
|
return userConfig.config;
|
|
11328
11480
|
},
|
|
11481
|
+
|
|
11482
|
+
/**
|
|
11483
|
+
* Reload configuration (including workspace-specific config)
|
|
11484
|
+
* v0.35.0: Loads workspace-config.json if present
|
|
11485
|
+
* v0.36.0: Accepts optional config object for programmatic configuration
|
|
11486
|
+
*
|
|
11487
|
+
* @param {Object} [opts] - Optional config options
|
|
11488
|
+
* @param {Object} [opts.workspace] - Workspace-specific config to merge (highest priority)
|
|
11489
|
+
* @param {Object} [opts.override] - Config object to merge over loaded config
|
|
11490
|
+
* @returns {Promise<void>}
|
|
11491
|
+
*
|
|
11492
|
+
* @example
|
|
11493
|
+
* // Load from files only
|
|
11494
|
+
* await repo.config.load();
|
|
11495
|
+
*
|
|
11496
|
+
* @example
|
|
11497
|
+
* // Programmatic workspace config (no file I/O needed)
|
|
11498
|
+
* await repo.config.load({
|
|
11499
|
+
* workspace: { user: { email: 'test@example.com' } }
|
|
11500
|
+
* });
|
|
11501
|
+
*
|
|
11502
|
+
* @example
|
|
11503
|
+
* // Override global config programmatically
|
|
11504
|
+
* await repo.config.load({
|
|
11505
|
+
* override: { test: { value: 'override' } }
|
|
11506
|
+
* });
|
|
11507
|
+
*/
|
|
11508
|
+
async load(opts) {
|
|
11509
|
+
await userConfig.load(opts);
|
|
11510
|
+
},
|
|
11511
|
+
},
|
|
11512
|
+
|
|
11513
|
+
/**
|
|
11514
|
+
* Template helper functions (v0.36.0)
|
|
11515
|
+
*
|
|
11516
|
+
* Utilities for displaying and formatting JJ data
|
|
11517
|
+
*/
|
|
11518
|
+
template: {
|
|
11519
|
+
/**
|
|
11520
|
+
* Get files modified in a change
|
|
11521
|
+
*
|
|
11522
|
+
* @param {string} changeId - Change ID
|
|
11523
|
+
* @returns {Promise<string[]>} Array of file paths
|
|
11524
|
+
*/
|
|
11525
|
+
async files(changeId) {
|
|
11526
|
+
await graph.load();
|
|
11527
|
+
const change = await graph.getChange(changeId);
|
|
11528
|
+
if (!change || !change.fileSnapshot) {
|
|
11529
|
+
return [];
|
|
11530
|
+
}
|
|
11531
|
+
return Object.keys(change.fileSnapshot);
|
|
11532
|
+
},
|
|
11533
|
+
|
|
11534
|
+
/**
|
|
11535
|
+
* Join array elements with a separator
|
|
11536
|
+
*
|
|
11537
|
+
* @param {string[]} array - Array of strings
|
|
11538
|
+
* @param {string} separator - Separator string
|
|
11539
|
+
* @returns {string} Joined string
|
|
11540
|
+
*/
|
|
11541
|
+
join(array, separator) {
|
|
11542
|
+
if (!Array.isArray(array)) {
|
|
11543
|
+
return '';
|
|
11544
|
+
}
|
|
11545
|
+
return array.join(separator);
|
|
11546
|
+
},
|
|
11547
|
+
|
|
11548
|
+
/**
|
|
11549
|
+
* Format path for display
|
|
11550
|
+
*
|
|
11551
|
+
* @param {string} path - File path
|
|
11552
|
+
* @returns {string} Formatted path
|
|
11553
|
+
*/
|
|
11554
|
+
format_path(path) {
|
|
11555
|
+
if (!path) {
|
|
11556
|
+
return '';
|
|
11557
|
+
}
|
|
11558
|
+
// Simple formatting: just return the path as-is for now
|
|
11559
|
+
// In real JJ, this might apply colors, truncation, etc.
|
|
11560
|
+
return path;
|
|
11561
|
+
},
|
|
11329
11562
|
},
|
|
11330
11563
|
|
|
11331
11564
|
/**
|