isomorphic-jj 1.1.3 → 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 +193 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +193 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/api/repository.js +112 -15
- package/src/core/bookmark-store.js +16 -4
- package/src/core/revset-engine.js +18 -0
- package/src/core/user-config.js +47 -2
package/package.json
CHANGED
package/src/api/repository.js
CHANGED
|
@@ -1554,7 +1554,8 @@ export async function createJJ(options) {
|
|
|
1554
1554
|
* @param {Object} args - Arguments
|
|
1555
1555
|
* @param {string} [args.revset='all()'] - Revset expression to filter changes
|
|
1556
1556
|
* @param {number} [args.limit] - Maximum number of changes to return
|
|
1557
|
-
* @
|
|
1557
|
+
* @param {boolean} [args.count=false] - If true, return count instead of changes (v0.36.0)
|
|
1558
|
+
* @returns {Promise<Array|number>} Array of changes or count if args.count is true
|
|
1558
1559
|
*/
|
|
1559
1560
|
async log(args = {}) {
|
|
1560
1561
|
await graph.load();
|
|
@@ -1563,6 +1564,11 @@ export async function createJJ(options) {
|
|
|
1563
1564
|
const revsetExpr = args.revset || 'all()';
|
|
1564
1565
|
const changeIds = await revset.evaluate(revsetExpr);
|
|
1565
1566
|
|
|
1567
|
+
// If count mode, return count early (v0.36.0 feature)
|
|
1568
|
+
if (args.count) {
|
|
1569
|
+
return changeIds.length;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1566
1572
|
let changes = [];
|
|
1567
1573
|
for (const changeId of changeIds) {
|
|
1568
1574
|
const change = await graph.getChange(changeId);
|
|
@@ -2583,9 +2589,18 @@ export async function createJJ(options) {
|
|
|
2583
2589
|
* @returns {Promise<Object>} The new backout change including changeId, description, and backedOut field
|
|
2584
2590
|
*/
|
|
2585
2591
|
async backout(args) {
|
|
2592
|
+
// Normalize: accept common parameter aliases
|
|
2593
|
+
if (args && args.change && !args.revision) {
|
|
2594
|
+
args = { ...args, revision: args.change };
|
|
2595
|
+
} else if (args && args.changeId && !args.revision) {
|
|
2596
|
+
args = { ...args, revision: args.changeId };
|
|
2597
|
+
} else if (args && args.target && !args.revision) {
|
|
2598
|
+
args = { ...args, revision: args.target };
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2586
2601
|
if (!args || !args.revision) {
|
|
2587
2602
|
throw new JJError('INVALID_ARGUMENT', 'Missing revision argument', {
|
|
2588
|
-
suggestion: 'Provide the change ID to back out: { revision: "abc123..." }',
|
|
2603
|
+
suggestion: 'Provide the change ID to back out: { revision: "abc123..." } (or use change/changeId/target)',
|
|
2589
2604
|
});
|
|
2590
2605
|
}
|
|
2591
2606
|
|
|
@@ -3620,6 +3635,15 @@ export async function createJJ(options) {
|
|
|
3620
3635
|
* @param {string[]} [args.paths] - Specific paths to include in first split (full implementation planned for future)
|
|
3621
3636
|
*/
|
|
3622
3637
|
async split(args) {
|
|
3638
|
+
// Normalize: accept common parameter aliases
|
|
3639
|
+
if (args && args.change && !args.changeId) {
|
|
3640
|
+
args = { ...args, changeId: args.change };
|
|
3641
|
+
} else if (args && args.revision && !args.changeId) {
|
|
3642
|
+
args = { ...args, changeId: args.revision };
|
|
3643
|
+
} else if (args && args.target && !args.changeId) {
|
|
3644
|
+
args = { ...args, changeId: args.target };
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3623
3647
|
// Check for interactive mode (not supported)
|
|
3624
3648
|
if (args.interactive) {
|
|
3625
3649
|
throw new JJError(
|
|
@@ -5861,21 +5885,13 @@ export async function createJJ(options) {
|
|
|
5861
5885
|
});
|
|
5862
5886
|
}
|
|
5863
5887
|
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
const parts = key.split('.');
|
|
5868
|
-
let value = userConfig.config;
|
|
5869
|
-
|
|
5870
|
-
for (const part of parts) {
|
|
5871
|
-
if (value && typeof value === 'object') {
|
|
5872
|
-
value = value[part];
|
|
5873
|
-
} else {
|
|
5874
|
-
return null;
|
|
5875
|
-
}
|
|
5888
|
+
// Ensure config is loaded at least once
|
|
5889
|
+
if (!userConfig.config) {
|
|
5890
|
+
await userConfig.load();
|
|
5876
5891
|
}
|
|
5877
5892
|
|
|
5878
|
-
|
|
5893
|
+
// Use userConfig.get() to preserve programmatic config
|
|
5894
|
+
return userConfig.get(key);
|
|
5879
5895
|
},
|
|
5880
5896
|
|
|
5881
5897
|
/**
|
|
@@ -5924,6 +5940,87 @@ export async function createJJ(options) {
|
|
|
5924
5940
|
await userConfig.load();
|
|
5925
5941
|
return userConfig.config;
|
|
5926
5942
|
},
|
|
5943
|
+
|
|
5944
|
+
/**
|
|
5945
|
+
* Reload configuration (including workspace-specific config)
|
|
5946
|
+
* v0.35.0: Loads workspace-config.json if present
|
|
5947
|
+
* v0.36.0: Accepts optional config object for programmatic configuration
|
|
5948
|
+
*
|
|
5949
|
+
* @param {Object} [opts] - Optional config options
|
|
5950
|
+
* @param {Object} [opts.workspace] - Workspace-specific config to merge (highest priority)
|
|
5951
|
+
* @param {Object} [opts.override] - Config object to merge over loaded config
|
|
5952
|
+
* @returns {Promise<void>}
|
|
5953
|
+
*
|
|
5954
|
+
* @example
|
|
5955
|
+
* // Load from files only
|
|
5956
|
+
* await repo.config.load();
|
|
5957
|
+
*
|
|
5958
|
+
* @example
|
|
5959
|
+
* // Programmatic workspace config (no file I/O needed)
|
|
5960
|
+
* await repo.config.load({
|
|
5961
|
+
* workspace: { user: { email: 'test@example.com' } }
|
|
5962
|
+
* });
|
|
5963
|
+
*
|
|
5964
|
+
* @example
|
|
5965
|
+
* // Override global config programmatically
|
|
5966
|
+
* await repo.config.load({
|
|
5967
|
+
* override: { test: { value: 'override' } }
|
|
5968
|
+
* });
|
|
5969
|
+
*/
|
|
5970
|
+
async load(opts) {
|
|
5971
|
+
await userConfig.load(opts);
|
|
5972
|
+
},
|
|
5973
|
+
},
|
|
5974
|
+
|
|
5975
|
+
/**
|
|
5976
|
+
* Template helper functions (v0.36.0)
|
|
5977
|
+
*
|
|
5978
|
+
* Utilities for displaying and formatting JJ data
|
|
5979
|
+
*/
|
|
5980
|
+
template: {
|
|
5981
|
+
/**
|
|
5982
|
+
* Get files modified in a change
|
|
5983
|
+
*
|
|
5984
|
+
* @param {string} changeId - Change ID
|
|
5985
|
+
* @returns {Promise<string[]>} Array of file paths
|
|
5986
|
+
*/
|
|
5987
|
+
async files(changeId) {
|
|
5988
|
+
await graph.load();
|
|
5989
|
+
const change = await graph.getChange(changeId);
|
|
5990
|
+
if (!change || !change.fileSnapshot) {
|
|
5991
|
+
return [];
|
|
5992
|
+
}
|
|
5993
|
+
return Object.keys(change.fileSnapshot);
|
|
5994
|
+
},
|
|
5995
|
+
|
|
5996
|
+
/**
|
|
5997
|
+
* Join array elements with a separator
|
|
5998
|
+
*
|
|
5999
|
+
* @param {string[]} array - Array of strings
|
|
6000
|
+
* @param {string} separator - Separator string
|
|
6001
|
+
* @returns {string} Joined string
|
|
6002
|
+
*/
|
|
6003
|
+
join(array, separator) {
|
|
6004
|
+
if (!Array.isArray(array)) {
|
|
6005
|
+
return '';
|
|
6006
|
+
}
|
|
6007
|
+
return array.join(separator);
|
|
6008
|
+
},
|
|
6009
|
+
|
|
6010
|
+
/**
|
|
6011
|
+
* Format path for display
|
|
6012
|
+
*
|
|
6013
|
+
* @param {string} path - File path
|
|
6014
|
+
* @returns {string} Formatted path
|
|
6015
|
+
*/
|
|
6016
|
+
format_path(path) {
|
|
6017
|
+
if (!path) {
|
|
6018
|
+
return '';
|
|
6019
|
+
}
|
|
6020
|
+
// Simple formatting: just return the path as-is for now
|
|
6021
|
+
// In real JJ, this might apply colors, truncation, etc.
|
|
6022
|
+
return path;
|
|
6023
|
+
},
|
|
5927
6024
|
},
|
|
5928
6025
|
|
|
5929
6026
|
/**
|
|
@@ -15,6 +15,7 @@ export class BookmarkStore {
|
|
|
15
15
|
this.storage = storage;
|
|
16
16
|
this.local = new Map();
|
|
17
17
|
this.remote = new Map(); // remote → (bookmark → changeId)
|
|
18
|
+
this.tracking = {}; // bookmark → { remote, ref } tracking info (v0.35.0)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -57,6 +58,7 @@ export class BookmarkStore {
|
|
|
57
58
|
|
|
58
59
|
this.local.clear();
|
|
59
60
|
this.remote.clear();
|
|
61
|
+
this.tracking = data.tracked || {}; // Load tracking info (v0.35.0)
|
|
60
62
|
|
|
61
63
|
// Load local bookmarks
|
|
62
64
|
for (const [name, target] of Object.entries(data.local)) {
|
|
@@ -94,7 +96,7 @@ export class BookmarkStore {
|
|
|
94
96
|
version: 1,
|
|
95
97
|
local,
|
|
96
98
|
remote,
|
|
97
|
-
tracked: {},
|
|
99
|
+
tracked: this.tracking || {}, // Save tracking info (v0.35.0)
|
|
98
100
|
});
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -207,13 +209,23 @@ export class BookmarkStore {
|
|
|
207
209
|
async list() {
|
|
208
210
|
const result = [];
|
|
209
211
|
|
|
210
|
-
// Add local bookmarks
|
|
212
|
+
// Add local bookmarks with tracking info (v0.35.0)
|
|
211
213
|
for (const [name, target] of this.local.entries()) {
|
|
212
|
-
|
|
214
|
+
const bookmark = {
|
|
213
215
|
name,
|
|
214
216
|
changeId: target,
|
|
215
217
|
remote: null,
|
|
216
|
-
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Include tracking info if available
|
|
221
|
+
if (this.tracking && this.tracking[name]) {
|
|
222
|
+
bookmark.tracking = {
|
|
223
|
+
remote: this.tracking[name].remote,
|
|
224
|
+
ref: this.tracking[name].remoteName || name,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
result.push(bookmark);
|
|
217
229
|
}
|
|
218
230
|
|
|
219
231
|
// Add remote bookmarks
|
|
@@ -129,6 +129,24 @@ export class RevsetEngine {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// v0.36.0: visible() - non-abandoned changes
|
|
133
|
+
if (trimmed === 'visible()') {
|
|
134
|
+
await this.graph.load();
|
|
135
|
+
const all = this.graph.getAll();
|
|
136
|
+
return all
|
|
137
|
+
.filter(c => !c.abandoned)
|
|
138
|
+
.map(c => c.changeId);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// v0.36.0: hidden() - abandoned changes
|
|
142
|
+
if (trimmed === 'hidden()') {
|
|
143
|
+
await this.graph.load();
|
|
144
|
+
const all = this.graph.getAll();
|
|
145
|
+
return all
|
|
146
|
+
.filter(c => c.abandoned === true)
|
|
147
|
+
.map(c => c.changeId);
|
|
148
|
+
}
|
|
149
|
+
|
|
132
150
|
// ancestors(changeId) - all ancestors including the change itself
|
|
133
151
|
const ancestorsMatch = trimmed.match(/^ancestors\(([0-9a-f]{32})\)$/);
|
|
134
152
|
if (ancestorsMatch) {
|
package/src/core/user-config.js
CHANGED
|
@@ -37,9 +37,15 @@ export class UserConfig {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Load config from storage
|
|
40
|
+
* Load config from storage and/or programmatic object
|
|
41
|
+
* v0.35.0: Also loads workspace-specific config from workspace-config.json
|
|
42
|
+
* v0.36.0: Accepts optional config object for programmatic configuration
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} [opts] - Optional config options
|
|
45
|
+
* @param {Object} [opts.workspace] - Workspace-specific config to merge (highest priority)
|
|
46
|
+
* @param {Object} [opts.override] - Config object to merge over loaded config
|
|
41
47
|
*/
|
|
42
|
-
async load() {
|
|
48
|
+
async load(opts = {}) {
|
|
43
49
|
try {
|
|
44
50
|
const data = await this.storage.read('config.json');
|
|
45
51
|
if (data) {
|
|
@@ -49,6 +55,45 @@ export class UserConfig {
|
|
|
49
55
|
// No config file yet, use defaults
|
|
50
56
|
await this.init();
|
|
51
57
|
}
|
|
58
|
+
|
|
59
|
+
// Merge programmatic override config if provided
|
|
60
|
+
if (opts.override) {
|
|
61
|
+
this.config = this._deepMerge(this.config, opts.override);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Load workspace-specific config from file (v0.35.0)
|
|
65
|
+
try {
|
|
66
|
+
const workspaceData = await this.storage.read('workspace-config.json');
|
|
67
|
+
if (workspaceData) {
|
|
68
|
+
// Merge workspace config over global config (workspace takes precedence)
|
|
69
|
+
this.config = this._deepMerge(this.config, workspaceData);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
// No workspace config, that's fine
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Merge programmatic workspace config if provided (highest priority)
|
|
76
|
+
if (opts.workspace) {
|
|
77
|
+
this.config = this._deepMerge(this.config, opts.workspace);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Deep merge two config objects (workspace overrides global)
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
_deepMerge(target, source) {
|
|
86
|
+
const result = { ...target };
|
|
87
|
+
|
|
88
|
+
for (const key in source) {
|
|
89
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
90
|
+
result[key] = this._deepMerge(target[key] || {}, source[key]);
|
|
91
|
+
} else {
|
|
92
|
+
result[key] = source[key];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
52
97
|
}
|
|
53
98
|
|
|
54
99
|
/**
|