@@ -8,7 +8,8 @@ import { Repo } from '@sourcebot/db';
88import { createLogger } from '@sourcebot/shared' ;
99import path from 'path' ;
1010import { simpleGit } from 'simple-git' ;
11- import { FileTreeItem , FileTreeNode } from './types' ;
11+ import { FileTreeItem } from './types' ;
12+ import { buildFileTree , getPathspecs , isPathValid , normalizePath } from './utils' ;
1213
1314const logger = createLogger ( 'file-tree' ) ;
1415
@@ -41,17 +42,22 @@ export const getTree = async (params: { repoName: string, revisionName: string,
4142
4243 let result : string = '' ;
4344 try {
44- result = await git . raw ( [
45+
46+ const command = [
4547 // Disable quoting of non-ASCII characters in paths
4648 '-c' , 'core.quotePath=false' ,
4749 'ls-tree' ,
4850 revisionName ,
4951 // format as output as {type},{path}
5052 '--format=%(objecttype),%(path)' ,
53+ // include tree nodes
54+ '-t' ,
5155 '--' ,
5256 '.' ,
5357 ...pathSpecs ,
54- ] )
58+ ] ;
59+
60+ result = await git . raw ( command ) ;
5561 } catch ( error ) {
5662 logger . error ( 'git ls-tree failed.' , { error } ) ;
5763 return unexpectedError ( 'git ls-tree command failed.' ) ;
@@ -184,104 +190,6 @@ export const getFiles = async (params: { repoName: string, revisionName: string
184190
185191 } ) ) ;
186192
187- // @note : we don't allow directory traversal
188- // or null bytes in the path.
189- const isPathValid = ( path : string ) => {
190- return ! path . includes ( '..' ) && ! path . includes ( '\0' ) ;
191- }
192-
193- const normalizePath = ( path : string ) : string => {
194- // Normalize the path by...
195- let normalizedPath = path ;
196-
197- // ... adding a trailing slash if it doesn't have one.
198- // This is important since ls-tree won't return the contents
199- // of a directory if it doesn't have a trailing slash.
200- if ( ! normalizedPath . endsWith ( '/' ) ) {
201- normalizedPath = `${ normalizedPath } /` ;
202- }
203-
204- // ... removing any leading slashes. This is needed since
205- // the path is relative to the repository's root, so we
206- // need a relative path.
207- if ( normalizedPath . startsWith ( '/' ) ) {
208- normalizedPath = normalizedPath . slice ( 1 ) ;
209- }
210-
211- return normalizedPath ;
212- }
213-
214- const getPathspecs = ( path : string ) : string [ ] => {
215- const normalizedPath = normalizePath ( path ) ;
216- if ( normalizedPath . length === 0 ) {
217- return [ ] ;
218- }
219-
220- const parts = normalizedPath . split ( '/' ) . filter ( part => part . length > 0 ) ;
221- const pathspecs : string [ ] = [ ] ;
222-
223- for ( let i = 0 ; i < parts . length ; i ++ ) {
224- const prefix = parts . slice ( 0 , i + 1 ) . join ( '/' ) ;
225- pathspecs . push ( `${ prefix } /` ) ;
226- }
227-
228- return pathspecs ;
229- }
230-
231- const buildFileTree = ( flatList : { type : string , path : string } [ ] ) : FileTreeNode => {
232- const root : FileTreeNode = {
233- name : 'root' ,
234- path : '' ,
235- type : 'tree' ,
236- children : [ ] ,
237- } ;
238-
239- for ( const item of flatList ) {
240- const parts = item . path . split ( '/' ) ;
241- let current : FileTreeNode = root ;
242-
243- for ( let i = 0 ; i < parts . length ; i ++ ) {
244- const part = parts [ i ] ;
245- const isLeaf = i === parts . length - 1 ;
246- const nodeType = isLeaf ? item . type : 'tree' ;
247- let next = current . children . find ( ( child : FileTreeNode ) => child . name === part && child . type === nodeType ) ;
248-
249- if ( ! next ) {
250- next = {
251- name : part ,
252- path : item . path ,
253- type : nodeType ,
254- children : [ ] ,
255- } ;
256- current . children . push ( next ) ;
257- }
258- current = next ;
259- }
260- }
261-
262- const sortTree = ( node : FileTreeNode ) : FileTreeNode => {
263- if ( node . type === 'blob' ) {
264- return node ;
265- }
266-
267- const sortedChildren = node . children
268- . map ( sortTree )
269- . sort ( ( a : FileTreeNode , b : FileTreeNode ) => {
270- if ( a . type !== b . type ) {
271- return a . type === 'tree' ? - 1 : 1 ;
272- }
273- return a . name . localeCompare ( b . name , undefined , { sensitivity : 'base' } ) ;
274- } ) ;
275-
276- return {
277- ...node ,
278- children : sortedChildren ,
279- } ;
280- } ;
281-
282- return sortTree ( root ) ;
283- }
284-
285193// @todo : this is duplicated from the `getRepoPath` function in the
286194// backend's `utils.ts` file. Eventually we should move this to a shared
287195// package.
0 commit comments