@feng0531/dsh-vcs 0.5.1 → 0.7.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/vcs.mjs CHANGED
@@ -15,6 +15,8 @@ import {inside, confined} from './repository-path.mjs';
15
15
  import {readWorkspaceLargeText} from './workspace-large-text.mjs';
16
16
  import {createHistoricalAccess,validateHistoricalPath} from './src/historical-access.mjs';
17
17
  import {withComparisonPreview} from './src/preview-capabilities.mjs';
18
+ import {searchContentPage} from './src/content-search.mjs';
19
+ import {withOperationBudget} from './src/operation-budget.mjs';
18
20
 
19
21
  const historicalAccess = createHistoricalAccess({run:git});
20
22
  /** Host invalidates object bytes on grant replacement, expiry and plugin disposal. */
@@ -272,35 +274,18 @@ export async function listStashes(repo,{signal}={}){
272
274
  return {stashes};
273
275
  }
274
276
 
275
- export async function searchRepositoryContent(repo,{commit,query,signal}={}) {
277
+ export async function searchRepositoryContent(repo,{commit,query,cursor,limit=100,signal}={}) {
278
+ return withOperationBudget({signal},async()=>{
276
279
  signal?.throwIfAborted();
277
280
  if(typeof query!=='string'||!query||query.length>1024||/[\0\r\n]/.test(query))throw new Error('Invalid content search query');
281
+ if(cursor!==undefined&&!commit)throw Error('Content search continuation requires a fixed commit');
278
282
  repo=await checkedGitRepo(repo,signal,'Content search supports Git only');
279
283
  let snapshot=commit;
280
- if(!snapshot){
281
- try{snapshot=(await git(repo.root,['rev-parse','--verify','HEAD^{commit}'],MAX_TEXT,{signal})).toString('utf8').trim();}
282
- catch{throw new Error('Content search requires a resolved HEAD commit');}
283
- }
284
+ if(!snapshot) snapshot=await resolveHeadCommit(args=>git(repo.root,args,MAX_TEXT,{signal}));
284
285
  if(!isObjectId(snapshot))throw new Error('Invalid content search commit');
285
286
  if((await git(repo.root,['cat-file','-t',snapshot],MAX_TEXT,{signal})).toString('utf8').trim()!=='commit')throw Error('Content search requires a commit object');
286
- let text;
287
- try{text=(await git(repo.root,['-c','grep.column=false','grep','--no-color','-F','-n','-I','-z','--max-count=100','-e',query,snapshot,'--'],MAX_TEXT,{signal})).toString('utf8');}
288
- catch(error){if(error.code==='VCS_COMMAND'&&error.exitCode===1)return {snapshot,matches:[]};throw error;}
289
- signal?.throwIfAborted();
290
- const matches=[];
291
- // -z frames path and line with NUL; legal paths can contain LF.
292
- for(let position=0;position<text.length;){
293
- const pathEnd=text.indexOf(String.fromCharCode(0),position),lineEnd=text.indexOf(String.fromCharCode(0),pathEnd+1),recordEnd=text.indexOf(String.fromCharCode(10),lineEnd+1);
294
- if(pathEnd<position||lineEnd<0||recordEnd<0)throw new Error('Invalid content search result');
295
- const treePath=text.slice(position,pathEnd),lineNo=text.slice(pathEnd+1,lineEnd),lineText=text.slice(lineEnd+1,recordEnd);position=recordEnd+1;
296
- const cut=treePath.indexOf(':');
297
- if(cut<0)throw new Error('Invalid content search result');
298
- const path=treePath.slice(cut+1);
299
- if(!path||!/^\d+$/.test(lineNo))throw new Error('Invalid content search result');
300
- matches.push({path,line:Number(lineNo),text:lineText});
301
- if(matches.length>500)throw new Error('Content search exceeds match limit');
302
- }
303
- return {snapshot,matches};
287
+ return searchContentPage(git,repo.root,{snapshot,query,cursor,limit,signal});
288
+ });
304
289
  }
305
290
 
306
291
  export async function getCommitDetails(repo,options={}) {
@@ -349,11 +334,13 @@ function parseCommitChanges(text,scope){
349
334
  }
350
335
  return changes;
351
336
  }
352
- export async function getHistoricalSegment(repo, {commit,path:file,offset = 0,signal} = {}) {
337
+ export async function getHistoricalSegment(repo, {commit,path:file,offset,line,signal} = {}) {
338
+ return withOperationBudget({signal},async()=>{
353
339
  const expectedEpoch = historicalAccess.captureEpoch();
354
340
  // Capture before any await: revoked in-flight validation cannot refill object bytes.
355
341
  repo = await checkedGitRepo(repo, signal, 'Historical segments support Git only');
356
- return historicalAccess.segment(repo.root, {commit,path:file,offset,signal,expectedEpoch});
342
+ return historicalAccess.segment(repo.root, {commit,path:file,offset,line,signal,expectedEpoch});
343
+ });
357
344
  }
358
345
 
359
346
  export async function getHistoricalFile(repo,{commit,path:file,signal}={}) {