isomorphic-jj 1.0.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/LICENSE +21 -0
- package/README.md +971 -0
- package/dist/index.cjs +12609 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +12600 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +90 -0
- package/src/api/repository.js +6885 -0
- package/src/backend/isomorphic-git-backend.js +687 -0
- package/src/backend/lazy-git-backend.js +180 -0
- package/src/browser/helpers.js +189 -0
- package/src/browser/index.d.ts +174 -0
- package/src/browser/index.js +12 -0
- package/src/core/background-ops.js +272 -0
- package/src/core/bookmark-store.js +232 -0
- package/src/core/change-graph.js +285 -0
- package/src/core/conflict-model.js +505 -0
- package/src/core/jj-checkout.js +78 -0
- package/src/core/jj-operation-store.js +120 -0
- package/src/core/jj-tree-state.js +94 -0
- package/src/core/jj-view-store.js +105 -0
- package/src/core/merge-driver-registry.js +432 -0
- package/src/core/operation-log.js +272 -0
- package/src/core/revset-engine.js +1081 -0
- package/src/core/storage-manager.js +207 -0
- package/src/core/tag-store.js +167 -0
- package/src/core/user-config.js +142 -0
- package/src/core/working-copy.js +314 -0
- package/src/core/workspace-manager.js +283 -0
- package/src/drivers/built-in-drivers.js +296 -0
- package/src/index.js +15 -0
- package/src/protos/local_working_copy.proto +76 -0
- package/src/protos/simple_op_store.proto +147 -0
- package/src/types.d.ts +1129 -0
- package/src/utils/errors.js +21 -0
- package/src/utils/id-generation.js +54 -0
- package/src/utils/validation.js +124 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in merge drivers for common file types
|
|
3
|
+
*
|
|
4
|
+
* These drivers extend JJ's merge algorithm with domain-specific logic.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Deep merge for JSON objects
|
|
9
|
+
* Recursively merges objects, with theirs taking precedence on conflicts
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} base - Base object
|
|
12
|
+
* @param {Object} ours - Our changes
|
|
13
|
+
* @param {Object} theirs - Their changes
|
|
14
|
+
* @returns {Object} Merged object
|
|
15
|
+
*/
|
|
16
|
+
function deepMergeJSON(base, ours, theirs) {
|
|
17
|
+
// If all are identical, return any
|
|
18
|
+
if (JSON.stringify(ours) === JSON.stringify(theirs)) {
|
|
19
|
+
return ours;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// If ours is same as base, take theirs
|
|
23
|
+
if (JSON.stringify(ours) === JSON.stringify(base)) {
|
|
24
|
+
return theirs;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If theirs is same as base, take ours
|
|
28
|
+
if (JSON.stringify(theirs) === JSON.stringify(base)) {
|
|
29
|
+
return ours;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Both sides changed - need smart merge
|
|
33
|
+
if (typeof ours !== 'object' || typeof theirs !== 'object' || Array.isArray(ours) || Array.isArray(theirs)) {
|
|
34
|
+
// Primitives or arrays - conflict
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Merge objects
|
|
39
|
+
const result = { ...base };
|
|
40
|
+
|
|
41
|
+
// Get all keys
|
|
42
|
+
const allKeys = new Set([
|
|
43
|
+
...Object.keys(base || {}),
|
|
44
|
+
...Object.keys(ours || {}),
|
|
45
|
+
...Object.keys(theirs || {}),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
for (const key of allKeys) {
|
|
49
|
+
const baseVal = base?.[key];
|
|
50
|
+
const oursVal = ours?.[key];
|
|
51
|
+
const theirsVal = theirs?.[key];
|
|
52
|
+
|
|
53
|
+
if (JSON.stringify(oursVal) === JSON.stringify(theirsVal)) {
|
|
54
|
+
// Both sides agree
|
|
55
|
+
result[key] = oursVal;
|
|
56
|
+
} else if (JSON.stringify(oursVal) === JSON.stringify(baseVal)) {
|
|
57
|
+
// We didn't change it, take theirs
|
|
58
|
+
result[key] = theirsVal;
|
|
59
|
+
} else if (JSON.stringify(theirsVal) === JSON.stringify(baseVal)) {
|
|
60
|
+
// They didn't change it, take ours
|
|
61
|
+
result[key] = oursVal;
|
|
62
|
+
} else {
|
|
63
|
+
// Both changed differently
|
|
64
|
+
if (typeof oursVal === 'object' && typeof theirsVal === 'object' && !Array.isArray(oursVal) && !Array.isArray(theirsVal)) {
|
|
65
|
+
// Recurse for nested objects
|
|
66
|
+
const merged = deepMergeJSON(baseVal || {}, oursVal, theirsVal);
|
|
67
|
+
if (merged === null) {
|
|
68
|
+
return null; // Conflict in nested object
|
|
69
|
+
}
|
|
70
|
+
result[key] = merged;
|
|
71
|
+
} else {
|
|
72
|
+
// Can't merge - conflict
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generic JSON merge driver
|
|
83
|
+
*
|
|
84
|
+
* Attempts to merge JSON files intelligently by merging objects.
|
|
85
|
+
* Falls back to conflict markers if merge isn't possible.
|
|
86
|
+
*/
|
|
87
|
+
export async function jsonDriver({ path, content }) {
|
|
88
|
+
const { base, ours, theirs } = content;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const baseObj = base ? JSON.parse(base) : {};
|
|
92
|
+
const oursObj = ours ? JSON.parse(ours) : {};
|
|
93
|
+
const theirsObj = theirs ? JSON.parse(theirs) : {};
|
|
94
|
+
|
|
95
|
+
const merged = deepMergeJSON(baseObj, oursObj, theirsObj);
|
|
96
|
+
|
|
97
|
+
if (merged !== null) {
|
|
98
|
+
// Successful merge
|
|
99
|
+
return {
|
|
100
|
+
content: JSON.stringify(merged, null, 2) + '\n',
|
|
101
|
+
hasConflict: false,
|
|
102
|
+
};
|
|
103
|
+
} else {
|
|
104
|
+
// Conflict - return with markers
|
|
105
|
+
const conflictContent = `<<<<<<< ours\n${ours || ''}\n=======\n${theirs || ''}\n>>>>>>> theirs`;
|
|
106
|
+
return {
|
|
107
|
+
content: conflictContent,
|
|
108
|
+
hasConflict: true,
|
|
109
|
+
conflicts: [{
|
|
110
|
+
type: 'json-merge-conflict',
|
|
111
|
+
message: 'Both sides modified the same field with different values',
|
|
112
|
+
}],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// JSON parsing failed - fall back to text conflict
|
|
117
|
+
const conflictContent = `<<<<<<< ours\n${ours || ''}\n=======\n${theirs || ''}\n>>>>>>> theirs`;
|
|
118
|
+
return {
|
|
119
|
+
content: conflictContent,
|
|
120
|
+
hasConflict: true,
|
|
121
|
+
conflicts: [{
|
|
122
|
+
type: 'json-parse-error',
|
|
123
|
+
message: `JSON parsing failed: ${error.message}`,
|
|
124
|
+
}],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* package.json merge driver
|
|
131
|
+
*
|
|
132
|
+
* Smart merging for Node.js package.json files:
|
|
133
|
+
* - Union merge for dependencies/devDependencies
|
|
134
|
+
* - Take theirs for version (semver conflicts are hard)
|
|
135
|
+
* - Merge other fields intelligently
|
|
136
|
+
*/
|
|
137
|
+
export async function packageJsonDriver({ path, content }) {
|
|
138
|
+
const { base, ours, theirs } = content;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const baseObj = base ? JSON.parse(base) : {};
|
|
142
|
+
const oursObj = ours ? JSON.parse(ours) : {};
|
|
143
|
+
const theirsObj = theirs ? JSON.parse(theirs) : {};
|
|
144
|
+
|
|
145
|
+
// Start with base
|
|
146
|
+
const merged = { ...baseObj };
|
|
147
|
+
|
|
148
|
+
// Merge top-level fields
|
|
149
|
+
for (const key of Object.keys({ ...oursObj, ...theirsObj })) {
|
|
150
|
+
if (key === 'dependencies' || key === 'devDependencies' || key === 'peerDependencies' || key === 'optionalDependencies') {
|
|
151
|
+
// Union merge dependencies
|
|
152
|
+
merged[key] = {
|
|
153
|
+
...baseObj[key],
|
|
154
|
+
...oursObj[key],
|
|
155
|
+
...theirsObj[key], // theirs wins on conflicts
|
|
156
|
+
};
|
|
157
|
+
} else if (key === 'version') {
|
|
158
|
+
// Version conflicts are tricky - take theirs
|
|
159
|
+
merged[key] = theirsObj[key] || oursObj[key] || baseObj[key];
|
|
160
|
+
} else if (key === 'scripts') {
|
|
161
|
+
// Merge scripts
|
|
162
|
+
merged[key] = {
|
|
163
|
+
...baseObj[key],
|
|
164
|
+
...oursObj[key],
|
|
165
|
+
...theirsObj[key], // theirs wins
|
|
166
|
+
};
|
|
167
|
+
} else {
|
|
168
|
+
// Other fields - use standard logic
|
|
169
|
+
const oursVal = oursObj[key];
|
|
170
|
+
const theirsVal = theirsObj[key];
|
|
171
|
+
const baseVal = baseObj[key];
|
|
172
|
+
|
|
173
|
+
if (JSON.stringify(oursVal) === JSON.stringify(theirsVal)) {
|
|
174
|
+
merged[key] = oursVal;
|
|
175
|
+
} else if (JSON.stringify(oursVal) === JSON.stringify(baseVal)) {
|
|
176
|
+
merged[key] = theirsVal;
|
|
177
|
+
} else if (JSON.stringify(theirsVal) === JSON.stringify(baseVal)) {
|
|
178
|
+
merged[key] = oursVal;
|
|
179
|
+
} else {
|
|
180
|
+
// Conflict - take theirs
|
|
181
|
+
merged[key] = theirsVal;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
content: JSON.stringify(merged, null, 2) + '\n',
|
|
188
|
+
hasConflict: false,
|
|
189
|
+
metadata: {
|
|
190
|
+
driver: 'package.json',
|
|
191
|
+
mergedDependencies: Object.keys(merged.dependencies || {}).length,
|
|
192
|
+
mergedDevDependencies: Object.keys(merged.devDependencies || {}).length,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
} catch (error) {
|
|
196
|
+
// Parsing failed - fall back to conflict
|
|
197
|
+
const conflictContent = `<<<<<<< ours\n${ours || ''}\n=======\n${theirs || ''}\n>>>>>>> theirs`;
|
|
198
|
+
return {
|
|
199
|
+
content: conflictContent,
|
|
200
|
+
hasConflict: true,
|
|
201
|
+
conflicts: [{
|
|
202
|
+
type: 'parse-error',
|
|
203
|
+
message: `package.json parsing failed: ${error.message}`,
|
|
204
|
+
}],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* YAML merge driver (basic)
|
|
211
|
+
*
|
|
212
|
+
* Note: This is a simple implementation. For production use, consider
|
|
213
|
+
* using a proper YAML library like 'js-yaml'.
|
|
214
|
+
*/
|
|
215
|
+
export async function yamlDriver({ path, content }) {
|
|
216
|
+
const { base, ours, theirs } = content;
|
|
217
|
+
|
|
218
|
+
// Simple YAML merge - just do text-based for now
|
|
219
|
+
// A full implementation would parse YAML and do object merging
|
|
220
|
+
|
|
221
|
+
if (ours === theirs) {
|
|
222
|
+
return { content: ours, hasConflict: false };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (ours === base) {
|
|
226
|
+
return { content: theirs, hasConflict: false };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (theirs === base) {
|
|
230
|
+
return { content: ours, hasConflict: false };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Conflict
|
|
234
|
+
const conflictContent = `<<<<<<< ours\n${ours || ''}\n=======\n${theirs || ''}\n>>>>>>> theirs`;
|
|
235
|
+
return {
|
|
236
|
+
content: conflictContent,
|
|
237
|
+
hasConflict: true,
|
|
238
|
+
conflicts: [{
|
|
239
|
+
type: 'yaml-conflict',
|
|
240
|
+
message: 'YAML files modified on both sides',
|
|
241
|
+
}],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Markdown merge driver (section-based)
|
|
247
|
+
*
|
|
248
|
+
* Attempts to merge markdown files by sections (headers).
|
|
249
|
+
* Falls back to conflict markers for complex cases.
|
|
250
|
+
*/
|
|
251
|
+
export async function markdownDriver({ path, content }) {
|
|
252
|
+
const { base, ours, theirs } = content;
|
|
253
|
+
|
|
254
|
+
// Simple markdown merge - just text-based for now
|
|
255
|
+
// A full implementation would parse sections and merge by header
|
|
256
|
+
|
|
257
|
+
if (ours === theirs) {
|
|
258
|
+
return { content: ours, hasConflict: false };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (ours === base) {
|
|
262
|
+
return { content: theirs, hasConflict: false };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (theirs === base) {
|
|
266
|
+
return { content: ours, hasConflict: false };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// For now, fall back to conflict markers
|
|
270
|
+
// TODO: Implement section-based merging
|
|
271
|
+
const conflictContent = `<<<<<<< ours\n${ours || ''}\n=======\n${theirs || ''}\n>>>>>>> theirs`;
|
|
272
|
+
return {
|
|
273
|
+
content: conflictContent,
|
|
274
|
+
hasConflict: true,
|
|
275
|
+
conflicts: [{
|
|
276
|
+
type: 'markdown-conflict',
|
|
277
|
+
message: 'Markdown files modified on both sides',
|
|
278
|
+
}],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get all built-in drivers as a map
|
|
284
|
+
*
|
|
285
|
+
* @returns {Object} Map of pattern -> driver function
|
|
286
|
+
*/
|
|
287
|
+
export function getBuiltInDrivers() {
|
|
288
|
+
return {
|
|
289
|
+
'package.json': packageJsonDriver,
|
|
290
|
+
'*.json': jsonDriver,
|
|
291
|
+
'*.yaml': yamlDriver,
|
|
292
|
+
'*.yml': yamlDriver,
|
|
293
|
+
'*.md': markdownDriver,
|
|
294
|
+
'*.markdown': markdownDriver,
|
|
295
|
+
};
|
|
296
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* isomorphic-jj - Main entry point
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { createJJ } from './api/repository.js';
|
|
6
|
+
export { JJError } from './utils/errors.js';
|
|
7
|
+
|
|
8
|
+
// v0.5: Built-in merge drivers
|
|
9
|
+
export {
|
|
10
|
+
jsonDriver,
|
|
11
|
+
packageJsonDriver,
|
|
12
|
+
yamlDriver,
|
|
13
|
+
markdownDriver,
|
|
14
|
+
getBuiltInDrivers,
|
|
15
|
+
} from './drivers/built-in-drivers.js';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright 2020 The Jujutsu Authors
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
syntax = "proto3";
|
|
16
|
+
|
|
17
|
+
package local_working_copy;
|
|
18
|
+
|
|
19
|
+
enum FileType {
|
|
20
|
+
Normal = 0;
|
|
21
|
+
Symlink = 1;
|
|
22
|
+
Executable = 2;
|
|
23
|
+
Conflict = 3 [deprecated = true];
|
|
24
|
+
GitSubmodule = 4;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
message MaterializedConflictData {
|
|
28
|
+
// TODO: maybe we should store num_sides here as well
|
|
29
|
+
uint32 conflict_marker_len = 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
message FileState {
|
|
33
|
+
int64 mtime_millis_since_epoch = 1;
|
|
34
|
+
uint64 size = 2;
|
|
35
|
+
FileType file_type = 3;
|
|
36
|
+
MaterializedConflictData materialized_conflict_data = 5;
|
|
37
|
+
reserved 4;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
message FileStateEntry {
|
|
41
|
+
string path = 1;
|
|
42
|
+
FileState state = 2;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
message SparsePatterns {
|
|
46
|
+
repeated string prefixes = 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
message TreeState {
|
|
50
|
+
bytes legacy_tree_id = 1 [deprecated = true];
|
|
51
|
+
// Alternating positive and negative terms if there's a conflict, otherwise a
|
|
52
|
+
// single (positive) value
|
|
53
|
+
repeated bytes tree_ids = 5;
|
|
54
|
+
repeated FileStateEntry file_states = 2;
|
|
55
|
+
bool is_file_states_sorted = 6;
|
|
56
|
+
SparsePatterns sparse_patterns = 3;
|
|
57
|
+
WatchmanClock watchman_clock = 4;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
message WatchmanClock {
|
|
61
|
+
oneof watchman_clock {
|
|
62
|
+
string string_clock = 1;
|
|
63
|
+
int64 unix_timestamp = 2;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
message Checkout {
|
|
68
|
+
// The operation at which the working copy was updated.
|
|
69
|
+
bytes operation_id = 2;
|
|
70
|
+
// An identifier for this workspace. It is used for looking up the current
|
|
71
|
+
// working-copy commit in the repo view. Currently a human-readable name.
|
|
72
|
+
// TODO: Is it better to make this a UUID and a have map that to a name in
|
|
73
|
+
// config? That way users can rename a workspace.
|
|
74
|
+
string workspace_name = 3;
|
|
75
|
+
reserved 1;
|
|
76
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Copyright 2020 The Jujutsu Authors
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
syntax = "proto3";
|
|
16
|
+
|
|
17
|
+
package simple_op_store;
|
|
18
|
+
|
|
19
|
+
message RefConflictLegacy {
|
|
20
|
+
repeated bytes removes = 1 [deprecated = true];
|
|
21
|
+
repeated bytes adds = 2 [deprecated = true];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// This is redundant with `RefTargetTerm`. It exists for historical reasons.
|
|
25
|
+
// TODO: Consider deprecating it.
|
|
26
|
+
message RefConflict {
|
|
27
|
+
message Term {
|
|
28
|
+
optional bytes value = 1;
|
|
29
|
+
}
|
|
30
|
+
repeated Term removes = 1;
|
|
31
|
+
repeated Term adds = 2;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// RefTarget that may be serialized in legacy form.
|
|
35
|
+
message RefTarget {
|
|
36
|
+
// New `RefConflict` type represents both `commit_id` and `conflict_legacy`.
|
|
37
|
+
oneof value {
|
|
38
|
+
bytes commit_id = 1 [deprecated = true];
|
|
39
|
+
RefConflictLegacy conflict_legacy = 2 [deprecated = true];
|
|
40
|
+
RefConflict conflict = 3;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// RefTarget term that should be serialized in native form, in which positive
|
|
45
|
+
// and negative terms alternate.
|
|
46
|
+
message RefTargetTerm {
|
|
47
|
+
optional bytes value = 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
enum RemoteRefState {
|
|
51
|
+
New = 0;
|
|
52
|
+
Tracked = 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
message RemoteBookmark {
|
|
56
|
+
string remote_name = 1;
|
|
57
|
+
RefTarget target = 2;
|
|
58
|
+
// Introduced in jj 0.11.
|
|
59
|
+
optional RemoteRefState state = 3;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
message Bookmark {
|
|
63
|
+
string name = 1;
|
|
64
|
+
// Unset if the bookmark has been deleted locally.
|
|
65
|
+
RefTarget local_target = 2;
|
|
66
|
+
// Deprecated since jj 0.34.
|
|
67
|
+
repeated RemoteBookmark remote_bookmarks = 3 [deprecated = true];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
message GitRef {
|
|
71
|
+
string name = 1;
|
|
72
|
+
// This field is just for historical reasons (before we had the RefTarget
|
|
73
|
+
// type). New GitRefs have (only) the target field.
|
|
74
|
+
// TODO: Delete support for the old format.
|
|
75
|
+
bytes commit_id = 2 [deprecated = true];
|
|
76
|
+
RefTarget target = 3;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
message RemoteRef {
|
|
80
|
+
string name = 1;
|
|
81
|
+
repeated RefTargetTerm target_terms = 2;
|
|
82
|
+
RemoteRefState state = 3;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
message Tag {
|
|
86
|
+
string name = 1;
|
|
87
|
+
RefTarget target = 2;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
message View {
|
|
91
|
+
repeated bytes head_ids = 1;
|
|
92
|
+
reserved 4;
|
|
93
|
+
bytes wc_commit_id = 2 [deprecated = true];
|
|
94
|
+
map<string, bytes> wc_commit_ids = 8;
|
|
95
|
+
// Local bookmarks and remote bookmarks in legacy form.
|
|
96
|
+
repeated Bookmark bookmarks = 5;
|
|
97
|
+
repeated Tag local_tags = 6;
|
|
98
|
+
// Introduced in jj 0.34.
|
|
99
|
+
repeated RemoteView remote_views = 11;
|
|
100
|
+
// Only a subset of the refs. For example, does not include refs/notes/.
|
|
101
|
+
repeated GitRef git_refs = 3;
|
|
102
|
+
// This field is just for historical reasons (before we had the RefTarget
|
|
103
|
+
// type). New Views have (only) the target field.
|
|
104
|
+
// TODO: Delete support for the old format.
|
|
105
|
+
bytes git_head_legacy = 7 [deprecated = true];
|
|
106
|
+
RefTarget git_head = 9;
|
|
107
|
+
// Whether "@git" tags have been migrated to remote_views.
|
|
108
|
+
bool has_git_refs_migrated_to_remote_tags = 12;
|
|
109
|
+
reserved 10;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
message RemoteView {
|
|
113
|
+
string name = 1;
|
|
114
|
+
repeated RemoteRef bookmarks = 2;
|
|
115
|
+
repeated RemoteRef tags = 3;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
message Operation {
|
|
119
|
+
bytes view_id = 1;
|
|
120
|
+
repeated bytes parents = 2;
|
|
121
|
+
OperationMetadata metadata = 3;
|
|
122
|
+
// Introduced in jj 0.30.
|
|
123
|
+
repeated CommitPredecessors commit_predecessors = 4;
|
|
124
|
+
// Whether or not `commit_predecessors` is recorded.
|
|
125
|
+
bool stores_commit_predecessors = 5;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// TODO: Share with store.proto? Do we even need the timezone here?
|
|
129
|
+
message Timestamp {
|
|
130
|
+
int64 millis_since_epoch = 1;
|
|
131
|
+
int32 tz_offset = 2;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
message OperationMetadata {
|
|
135
|
+
Timestamp start_time = 1;
|
|
136
|
+
Timestamp end_time = 2;
|
|
137
|
+
string description = 3;
|
|
138
|
+
string hostname = 4;
|
|
139
|
+
string username = 5;
|
|
140
|
+
bool is_snapshot = 7;
|
|
141
|
+
map<string, string> tags = 6;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
message CommitPredecessors {
|
|
145
|
+
bytes commit_id = 1;
|
|
146
|
+
repeated bytes predecessor_ids = 2;
|
|
147
|
+
}
|