@feng0531/dsh-vcs 0.3.51 → 0.3.54
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/dist/client.js +2 -2
- package/dist/editor.js +69 -69
- package/git-history.mjs +16 -0
- package/index.mjs +4 -2
- package/package.json +1 -1
- package/vcs.mjs +4 -3
package/git-history.mjs
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
// Search values are literal, bounded and never revision/options or pathspec expressions.
|
|
2
|
+
export function historySearchArgs(search){
|
|
3
|
+
if(search===undefined)return {args:[],paths:[]};
|
|
4
|
+
if(!search||typeof search!=='object'||Array.isArray(search)||Object.keys(search).some(k=>!['message','author','path'].includes(k)))throw new Error('Invalid history search');
|
|
5
|
+
const args=['--fixed-strings','--regexp-ignore-case'],paths=[];
|
|
6
|
+
for(const key of ['message','author','path']){
|
|
7
|
+
const value=search[key];if(value===undefined)continue;
|
|
8
|
+
if(typeof value!=='string'||value.length>1024||/[\0\r\n]/.test(value))throw new Error('Invalid history search value');
|
|
9
|
+
if(!value)continue;
|
|
10
|
+
if(key==='path'){
|
|
11
|
+
if(value.startsWith('/')||value.includes('\\')||value.split('/').some(p=>!p||p==='.'||p==='..'))throw new Error('Invalid history search path');
|
|
12
|
+
paths.push(value);
|
|
13
|
+
}else args.push((key==='message'?'--grep=':'--author=')+value);
|
|
14
|
+
}
|
|
15
|
+
return {args,paths};
|
|
16
|
+
}
|
|
1
17
|
// Each line-porcelain record carries its own metadata; never interpret filename as an access path.
|
|
2
18
|
export function parseBlame(text,maxLines=500){
|
|
3
19
|
if(typeof text!=='string'||Buffer.byteLength(text,'utf8')>2*1024*1024)throw new Error('Invalid blame output size');
|
package/index.mjs
CHANGED
|
@@ -22,9 +22,10 @@ export async function resolveSessionCwd(ctx, sessionId, signal) {
|
|
|
22
22
|
|
|
23
23
|
const rediscover = () => Object.assign(new Error('Repository authorization is missing, expired, or changed. Rediscover repositories for this Session and retry.'), { code: 'vcs/rediscover-required' });
|
|
24
24
|
|
|
25
|
+
import {historySearchArgs} from './git-history.mjs';
|
|
25
26
|
function validatePayload(endpoint, payload) {
|
|
26
27
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) throw invalid('An object payload is required.');
|
|
27
|
-
const fields = endpoint==='vcs/tree-file'?['sessionId','repositoryId','commit','path'] : endpoint==='vcs/references'?['sessionId','repositoryId'] : endpoint==='vcs/commit-image'?['sessionId','repositoryId','commit','parentIndex','id','side'] : endpoint==='vcs/tree'?['sessionId','repositoryId','commit'] : endpoint==='vcs/file-history'?['sessionId','repositoryId','commit','parentIndex','id','offset','limit'] : ['vcs/revision-changes','vcs/revision-compare'].includes(endpoint) ? ['sessionId','repositoryId','base','target',...(endpoint==='vcs/revision-compare'?['id']:[])] : ['vcs/commit-compare','vcs/blame'].includes(endpoint) ? ['sessionId','repositoryId','commit','parentIndex','id'] : endpoint === 'vcs/commit' ? ['sessionId','repositoryId','commit','parentIndex'] : endpoint === 'vcs/history' ? ['sessionId','repositoryId','snapshot','offset','limit'] : endpoint === 'vcs/repositories' ? ['sessionId', 'subdirectory'] : ['sessionId', 'repositoryId', 'mode', ...(endpoint === 'vcs/compare' ? ['id'] : [])];
|
|
28
|
+
const fields = endpoint==='vcs/tree-file'?['sessionId','repositoryId','commit','path'] : endpoint==='vcs/references'?['sessionId','repositoryId'] : endpoint==='vcs/commit-image'?['sessionId','repositoryId','commit','parentIndex','id','side'] : endpoint==='vcs/tree'?['sessionId','repositoryId','commit'] : endpoint==='vcs/file-history'?['sessionId','repositoryId','commit','parentIndex','id','offset','limit'] : ['vcs/revision-changes','vcs/revision-compare'].includes(endpoint) ? ['sessionId','repositoryId','base','target',...(endpoint==='vcs/revision-compare'?['id']:[])] : ['vcs/commit-compare','vcs/blame'].includes(endpoint) ? ['sessionId','repositoryId','commit','parentIndex','id'] : endpoint === 'vcs/commit' ? ['sessionId','repositoryId','commit','parentIndex'] : endpoint === 'vcs/history' ? ['sessionId','repositoryId','snapshot','offset','limit','search'] : endpoint === 'vcs/repositories' ? ['sessionId', 'subdirectory'] : ['sessionId', 'repositoryId', 'mode', ...(endpoint === 'vcs/compare' ? ['id'] : [])];
|
|
28
29
|
if (Object.keys(payload).some(key => !fields.includes(key))) throw invalid('Only Session-addressed repository requests are supported; unknown payload field.');
|
|
29
30
|
if (endpoint === 'vcs/repositories') {
|
|
30
31
|
if (payload.subdirectory !== undefined) {
|
|
@@ -44,6 +45,7 @@ function validatePayload(endpoint, payload) {
|
|
|
44
45
|
if(typeof payload.commit!=='string'||!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(payload.commit))throw invalid('Invalid commit id.');
|
|
45
46
|
if(payload.parentIndex!==undefined&&(!Number.isInteger(payload.parentIndex)||payload.parentIndex<0||payload.parentIndex>100))throw invalid('Invalid parent index.');
|
|
46
47
|
}
|
|
48
|
+
if(endpoint==='vcs/history'){try{historySearchArgs(payload.search);}catch{throw invalid('Invalid history search.');}}
|
|
47
49
|
if(['vcs/history','vcs/file-history'].includes(endpoint)){
|
|
48
50
|
if(payload.offset===null||payload.limit===null)throw invalid('Invalid history pagination.');
|
|
49
51
|
if(payload.snapshot!==undefined&&(typeof payload.snapshot!=='string'||!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(payload.snapshot)))throw invalid('Invalid history snapshot.');
|
|
@@ -142,7 +144,7 @@ export function createHandler(ctx, api = adapter, maxActive = 4, { now = Date.no
|
|
|
142
144
|
: endpoint === 'vcs/commit'
|
|
143
145
|
? await api.getCommitDetails({...repository},{commit:payload.commit,parentIndex:payload.parentIndex??0,signal})
|
|
144
146
|
: endpoint === 'vcs/history'
|
|
145
|
-
? await api.listHistory({...repository},{snapshot:payload.snapshot,offset:payload.offset??0,limit:payload.limit??50,signal})
|
|
147
|
+
? await api.listHistory({...repository},{snapshot:payload.snapshot,search:payload.search,offset:payload.offset??0,limit:payload.limit??50,signal})
|
|
146
148
|
: endpoint === 'vcs/status'
|
|
147
149
|
? { cwd, repository: { ...repository }, changes: await api.listChanges({ ...repository }, mode), mode }
|
|
148
150
|
: await api.getComparison({ ...repository }, { mode, id: payload.id });
|
package/package.json
CHANGED
package/vcs.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {preparePng} from './image-preview.mjs';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import {parseHistory,HISTORY_FORMAT,historyPage,parseBlame,parseReferences,REFS_FORMAT} from './git-history.mjs';
|
|
3
|
+
import {parseHistory,HISTORY_FORMAT,historyPage,historySearchArgs,parseBlame,parseReferences,REFS_FORMAT} from './git-history.mjs';
|
|
4
4
|
import * as fs from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
@@ -221,7 +221,8 @@ async function checkedRepo(repo, signal) {
|
|
|
221
221
|
if (!found || found.type !== repo.type || path.resolve(found.root) !== path.resolve(repo.root)) throw new Error('Repository root changed or is invalid');
|
|
222
222
|
return found;
|
|
223
223
|
}
|
|
224
|
-
export async function listHistory(repo, {snapshot,offset=0,limit=50,signal} = {}) {
|
|
224
|
+
export async function listHistory(repo, {snapshot,offset=0,limit=50,search,signal} = {}) {
|
|
225
|
+
const query=historySearchArgs(search);
|
|
225
226
|
if(!Number.isInteger(offset)||offset<0||offset>10000||!Number.isInteger(limit)||limit<1||limit>100) throw new Error('Invalid history pagination');
|
|
226
227
|
if(snapshot!==undefined && (typeof snapshot!=='string'||!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(snapshot))) throw new Error('Invalid history snapshot');
|
|
227
228
|
if(signal?.aborted) throw Object.assign(new Error('History cancelled'),{code:'ABORT_ERR'});
|
|
@@ -238,7 +239,7 @@ export async function listHistory(repo, {snapshot,offset=0,limit=50,signal} = {}
|
|
|
238
239
|
throw e;
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
|
-
const text=await git(repo.root,['log','-z','--no-show-signature','--encoding=UTF-8','--topo-order','--max-count='+String(limit+1),'--skip='+String(offset),'--format='+HISTORY_FORMAT,snapshot,'--'],MAX_TEXT,{signal});
|
|
242
|
+
const text=await git(repo.root,['--literal-pathspecs','log','-z','--no-show-signature','--encoding=UTF-8','--topo-order','--max-count='+String(limit+1),'--skip='+String(offset),'--format='+HISTORY_FORMAT,...query.args,snapshot,'--',...query.paths],MAX_TEXT,{signal});
|
|
242
243
|
const rows=parseHistory(text.toString('utf8'),limit+1);
|
|
243
244
|
return historyPage(rows,snapshot,offset,limit);
|
|
244
245
|
}
|