pagan-artifact 0.2.8 → 0.3.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/bin/art.js +15 -2
- package/branching/index.js +117 -46
- package/caches/index.js +112 -68
- package/changes/index.js +49 -15
- package/contributions/index.js +125 -95
- package/index.js +2 -2
- package/package.json +1 -1
- package/setup/index.js +197 -157
- package/utils/getStateByHash/index.js +61 -41
- package/workflow/index.js +199 -150
package/workflow/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* art - Modern version control.
|
|
3
|
-
* Module: Workflow (v0.
|
|
3
|
+
* Module: Workflow (v0.3.0)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
@@ -10,16 +10,44 @@ const crypto = require('crypto');
|
|
|
10
10
|
const getStateByHash = require('../utils/getStateByHash');
|
|
11
11
|
const shouldIgnore = require('../utils/shouldIgnore');
|
|
12
12
|
|
|
13
|
+
const MAX_PART_SIZE = 32000000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Helper to load all changes from a paginated stage directory.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function getStagedChanges(artPath) {
|
|
20
|
+
const stageDir = path.join(artPath, 'stage');
|
|
21
|
+
const manifestPath = path.join(stageDir, 'manifest.json');
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(stageDir) || !fs.existsSync(manifestPath)) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
28
|
+
let allChanges = {};
|
|
29
|
+
|
|
30
|
+
for (const partName of manifest.parts) {
|
|
31
|
+
const partPath = path.join(stageDir, partName);
|
|
32
|
+
|
|
33
|
+
if (fs.existsSync(partPath)) {
|
|
34
|
+
const partData = JSON.parse(fs.readFileSync(partPath, 'utf8'));
|
|
35
|
+
|
|
36
|
+
Object.assign(allChanges, partData.changes);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return allChanges;
|
|
41
|
+
}
|
|
42
|
+
|
|
13
43
|
/**
|
|
14
44
|
* Compares the working directory against the last commit and pending stage.
|
|
15
45
|
*/
|
|
16
46
|
|
|
17
|
-
|
|
47
|
+
function status () {
|
|
18
48
|
const root = process.cwd();
|
|
19
49
|
const artPath = path.join(root, '.art');
|
|
20
50
|
const artJsonPath = path.join(artPath, 'art.json');
|
|
21
|
-
const shouldIgnore = require('../utils/shouldIgnore');
|
|
22
|
-
const getStateByHash = require('../utils/getStateByHash');
|
|
23
51
|
|
|
24
52
|
if (!fs.existsSync(artJsonPath)) {
|
|
25
53
|
throw new Error('No art repository found.');
|
|
@@ -27,13 +55,8 @@ const shouldIgnore = require('../utils/shouldIgnore');
|
|
|
27
55
|
|
|
28
56
|
const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
|
|
29
57
|
const activeBranch = artJson.active.branch;
|
|
30
|
-
const stagePath = path.join(artPath, 'stage.json');
|
|
31
|
-
|
|
32
|
-
let stagedFiles = {};
|
|
33
|
-
if (fs.existsSync(stagePath)) {
|
|
34
|
-
stagedFiles = JSON.parse(fs.readFileSync(stagePath, 'utf8')).changes;
|
|
35
|
-
}
|
|
36
58
|
|
|
59
|
+
const stagedFiles = getStagedChanges(artPath);
|
|
37
60
|
const activeState = getStateByHash(activeBranch, artJson.active.parent) || {};
|
|
38
61
|
|
|
39
62
|
const allFiles = fs.readdirSync(root, { recursive: true })
|
|
@@ -47,14 +70,13 @@ const shouldIgnore = require('../utils/shouldIgnore');
|
|
|
47
70
|
const isStaged = !!stagedFiles[file];
|
|
48
71
|
const isActive = !!activeState[file];
|
|
49
72
|
|
|
50
|
-
if (file === '.art' || file.startsWith(`.art${path.sep}`))
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
73
|
+
if (file === '.art' || file.startsWith(`.art${path.sep}`)) continue;
|
|
53
74
|
|
|
54
75
|
const isIgnored = shouldIgnore(file);
|
|
55
76
|
|
|
56
77
|
if (isIgnored && !isActive && !isStaged) {
|
|
57
78
|
ignored.push(file);
|
|
79
|
+
|
|
58
80
|
continue;
|
|
59
81
|
}
|
|
60
82
|
|
|
@@ -80,175 +102,202 @@ const shouldIgnore = require('../utils/shouldIgnore');
|
|
|
80
102
|
}
|
|
81
103
|
|
|
82
104
|
/**
|
|
83
|
-
* Updates or creates a JSON diff in the stage
|
|
84
|
-
* Implements character-precise position tracking.
|
|
105
|
+
* Updates or creates a JSON diff in the stage directory.
|
|
85
106
|
*/
|
|
86
107
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
108
|
+
function add (targetPath) {
|
|
109
|
+
const root = process.cwd();
|
|
110
|
+
const artPath = path.join(root, '.art');
|
|
111
|
+
const stageDir = path.join(artPath, 'stage');
|
|
112
|
+
const artJsonPath = path.join(artPath, 'art.json');
|
|
113
|
+
const fullPath = path.resolve(root, targetPath);
|
|
114
|
+
|
|
115
|
+
if (!fs.existsSync(fullPath)) {
|
|
116
|
+
throw new Error(`Path does not exist: ${targetPath}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
|
|
120
|
+
const activeState = getStateByHash(artJson.active.branch, artJson.active.parent) || {};
|
|
121
|
+
const currentStaged = getStagedChanges(artPath);
|
|
122
|
+
|
|
123
|
+
const stats = fs.statSync(fullPath);
|
|
124
|
+
const relativeTarget = path.relative(root, fullPath);
|
|
125
|
+
|
|
126
|
+
if (!stats.isDirectory() && shouldIgnore(relativeTarget) && !activeState[relativeTarget]) {
|
|
127
|
+
return `${relativeTarget} is being ignored.`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let filesToProcess = [];
|
|
131
|
+
|
|
132
|
+
if (stats.isDirectory()) {
|
|
133
|
+
filesToProcess = fs.readdirSync(fullPath, { recursive: true })
|
|
134
|
+
.filter(f => {
|
|
135
|
+
const absF = path.join(fullPath, f);
|
|
136
|
+
const relF = path.relative(root, absF);
|
|
137
|
+
|
|
138
|
+
return !fs.statSync(absF).isDirectory() && !relF.startsWith('.art') && (!shouldIgnore(relF) || !!activeState[relF]);
|
|
139
|
+
})
|
|
140
|
+
.map(f => path.relative(root, path.join(fullPath, f)));
|
|
141
|
+
} else {
|
|
142
|
+
filesToProcess = [relativeTarget];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (filesToProcess.length === 0) return "No changes to add.";
|
|
146
|
+
|
|
147
|
+
for (const relPath of filesToProcess) {
|
|
148
|
+
const currentContent = fs.readFileSync(path.join(root, relPath), 'utf8');
|
|
149
|
+
const previousContent = activeState[relPath];
|
|
150
|
+
|
|
151
|
+
if (previousContent === undefined) {
|
|
152
|
+
currentStaged[relPath] = { type: 'createFile', content: currentContent };
|
|
153
|
+
} else if (currentContent !== previousContent) {
|
|
154
|
+
let start = 0;
|
|
155
|
+
|
|
156
|
+
while (start < previousContent.length && start < currentContent.length && previousContent[start] === currentContent[start]) {
|
|
157
|
+
start++;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let oldEnd = previousContent.length - 1;
|
|
161
|
+
let newEnd = currentContent.length - 1;
|
|
162
|
+
|
|
163
|
+
while (oldEnd >= start && newEnd >= start && previousContent[oldEnd] === currentContent[newEnd]) {
|
|
164
|
+
oldEnd--; newEnd--;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const operations = [];
|
|
168
|
+
const deletionLength = oldEnd - start + 1;
|
|
169
|
+
|
|
170
|
+
if (deletionLength > 0) {
|
|
171
|
+
operations.push({ type: 'delete', position: start, length: deletionLength });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const insertionContent = currentContent.slice(start, newEnd + 1);
|
|
175
|
+
|
|
176
|
+
if (insertionContent.length > 0) {
|
|
177
|
+
operations.push({ type: 'insert', position: start, content: insertionContent });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (operations.length > 0) {
|
|
181
|
+
currentStaged[relPath] = operations;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (fs.existsSync(stageDir)) fs.rmSync(stageDir, { recursive: true, force: true });
|
|
187
|
+
|
|
188
|
+
fs.mkdirSync(stageDir, { recursive: true });
|
|
189
|
+
|
|
190
|
+
const stageParts = [];
|
|
191
|
+
|
|
192
|
+
let currentPartChanges = {};
|
|
193
|
+
let currentSize = 0;
|
|
194
|
+
|
|
195
|
+
const savePart = () => {
|
|
196
|
+
const partName = `part.${stageParts.length}.json`;
|
|
197
|
+
|
|
198
|
+
fs.writeFileSync(path.join(stageDir, partName), JSON.stringify({ changes: currentPartChanges }, null, 2));
|
|
199
|
+
stageParts.push(partName);
|
|
200
|
+
currentPartChanges = {};
|
|
201
|
+
currentSize = 0;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
for (const [file, changes] of Object.entries(currentStaged)) {
|
|
205
|
+
const size = JSON.stringify(changes).length;
|
|
206
|
+
|
|
207
|
+
if (currentSize + size > MAX_PART_SIZE && Object.keys(currentPartChanges).length > 0) {
|
|
208
|
+
savePart();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
currentPartChanges[file] = changes;
|
|
212
|
+
currentSize += size;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
savePart();
|
|
216
|
+
|
|
217
|
+
fs.writeFileSync(path.join(stageDir, 'manifest.json'), JSON.stringify({ parts: stageParts }, null, 2));
|
|
218
|
+
|
|
219
|
+
return `Added ${filesToProcess.length} file(s) to paginated stage.`;
|
|
220
|
+
}
|
|
196
221
|
|
|
197
222
|
/**
|
|
198
|
-
* Finalizes the stage into a commit
|
|
223
|
+
* Finalizes the paginated stage into a paginated commit structure.
|
|
199
224
|
*/
|
|
200
225
|
|
|
201
226
|
function commit (message) {
|
|
202
|
-
if (!message)
|
|
203
|
-
throw new Error('A commit message is required.');
|
|
204
|
-
}
|
|
227
|
+
if (!message) throw new Error('A commit message is required.');
|
|
205
228
|
|
|
229
|
+
const MAX_PART_SIZE = 32000000;
|
|
206
230
|
const artPath = path.join(process.cwd(), '.art');
|
|
207
|
-
const
|
|
231
|
+
const stageDir = path.join(artPath, 'stage');
|
|
208
232
|
const artJsonPath = path.join(artPath, 'art.json');
|
|
209
233
|
|
|
210
|
-
if (!fs.existsSync(
|
|
211
|
-
throw new Error('Nothing to commit (stage is empty).');
|
|
212
|
-
}
|
|
234
|
+
if (!fs.existsSync(stageDir)) throw new Error('Nothing to commit (stage is empty).');
|
|
213
235
|
|
|
214
|
-
const
|
|
236
|
+
const stagedChanges = getStagedChanges(artPath);
|
|
215
237
|
const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
|
|
216
238
|
const branch = artJson.active.branch;
|
|
217
239
|
const timestamp = Date.now();
|
|
218
240
|
|
|
219
|
-
const
|
|
241
|
+
const commitHash = crypto
|
|
220
242
|
.createHash('sha1')
|
|
221
|
-
.update(JSON.stringify(
|
|
243
|
+
.update(JSON.stringify(stagedChanges) + timestamp + message)
|
|
222
244
|
.digest('hex');
|
|
223
245
|
|
|
224
|
-
const
|
|
225
|
-
|
|
246
|
+
const branchHistoryDir = path.join(artPath, 'history', 'local', branch);
|
|
247
|
+
const commitParts = [];
|
|
248
|
+
|
|
249
|
+
let currentPartChanges = {};
|
|
250
|
+
let currentPartSize = 0;
|
|
251
|
+
|
|
252
|
+
const saveCommitPart = () => {
|
|
253
|
+
if (Object.keys(currentPartChanges).length === 0) return;
|
|
254
|
+
|
|
255
|
+
const partName = `${commitHash}.part.${commitParts.length}.json`;
|
|
256
|
+
|
|
257
|
+
fs.writeFileSync(path.join(branchHistoryDir, partName), JSON.stringify({ changes: currentPartChanges }, null, 2));
|
|
258
|
+
commitParts.push(partName);
|
|
259
|
+
|
|
260
|
+
currentPartChanges = {};
|
|
261
|
+
currentPartSize = 0;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
for (const [filePath, changeSet] of Object.entries(stagedChanges)) {
|
|
265
|
+
const changeSize = JSON.stringify(changeSet).length;
|
|
266
|
+
|
|
267
|
+
if (currentPartSize + changeSize > MAX_PART_SIZE && Object.keys(currentPartChanges).length > 0) {
|
|
268
|
+
saveCommitPart();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
currentPartChanges[filePath] = changeSet;
|
|
272
|
+
currentPartSize += changeSize;
|
|
273
|
+
}
|
|
274
|
+
saveCommitPart();
|
|
275
|
+
|
|
276
|
+
const commitMaster = {
|
|
277
|
+
hash: commitHash,
|
|
226
278
|
message,
|
|
227
279
|
timestamp,
|
|
228
280
|
parent: artJson.active.parent,
|
|
229
|
-
|
|
281
|
+
parts: commitParts
|
|
230
282
|
};
|
|
231
283
|
|
|
232
|
-
|
|
233
|
-
const commitFilePath = path.join(branchHistoryDir, `${hash}.json`);
|
|
234
|
-
const manifestPath = path.join(branchHistoryDir, 'manifest.json');
|
|
284
|
+
fs.writeFileSync(path.join(branchHistoryDir, `${commitHash}.json`), JSON.stringify(commitMaster, null, 2));
|
|
235
285
|
|
|
236
|
-
fs.
|
|
286
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(branchHistoryDir, 'manifest.json'), 'utf8'));
|
|
237
287
|
|
|
238
|
-
|
|
288
|
+
manifest.commits.push(commitHash);
|
|
289
|
+
fs.writeFileSync(path.join(branchHistoryDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
239
290
|
|
|
240
|
-
|
|
241
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
242
|
-
|
|
243
|
-
artJson.active.parent = hash;
|
|
291
|
+
artJson.active.parent = commitHash;
|
|
244
292
|
fs.writeFileSync(artJsonPath, JSON.stringify(artJson, null, 2));
|
|
245
|
-
fs.unlinkSync(stagePath);
|
|
246
293
|
|
|
247
|
-
|
|
294
|
+
fs.rmSync(stageDir, { recursive: true, force: true });
|
|
295
|
+
|
|
296
|
+
return `[${branch} ${commitHash.slice(0, 7)}] ${message} (${commitParts.length} parts)`;
|
|
248
297
|
}
|
|
249
298
|
|
|
250
299
|
module.exports = {
|
|
251
|
-
__libraryVersion: '0.
|
|
300
|
+
__libraryVersion: '0.3.0',
|
|
252
301
|
__libraryAPIName: 'Workflow',
|
|
253
302
|
status,
|
|
254
303
|
add,
|