@@ -155,20 +155,38 @@ exports.registerApi = env => {
155155 { maxWait : 2000 }
156156 ) ;
157157
158- const autoStashExecuteAndPop = ( commands , repoPath , allowedCodes , outPipe , inPipe , timeout ) => {
158+ const repoPs = { } ;
159+
160+ /**
161+ * memoize nodegit opened repos
162+ * @param {string } repoPath the path to the repository
163+ * @returns {Promise<nodegit.Repository> }
164+ */
165+ const getRepo = repoPath => {
166+ if ( ! repoPs [ repoPath ] ) {
167+ repoPs [ repoPath ] = nodegit . Repository . open ( repoPath ) ;
168+ }
169+ return repoPs [ repoPath ] ;
170+ } ;
171+
172+ const autoStash = async ( repoPath , fn ) => {
159173 if ( config . autoStashAndPop ) {
160- return gitPromise . stashExecuteAndPop (
161- commands ,
162- repoPath ,
163- allowedCodes ,
164- outPipe ,
165- inPipe ,
166- timeout
167- ) ;
174+ const repo = await getRepo ( repoPath ) ;
175+ const signature = await repo . defaultSignature ( ) ;
176+ const oid = await nodegit . Stash . save ( repo , signature , 'Ungit: automatic stash' , 0 ) ;
177+ const out = await fn ( ) ;
178+ if ( ! oid ) return out ;
179+ let index ;
180+ await nodegit . Stash . foreach ( repo , ( i , _msg , stashOid ) => {
181+ if ( stashOid === oid ) index = i ;
182+ } ) ;
183+ if ( index != null ) await nodegit . Stash . pop ( repo , index ) ;
184+ return out ;
168185 } else {
169- return gitPromise ( commands , repoPath , allowedCodes , outPipe , inPipe , timeout ) ;
186+ return fn ( ) ;
170187 }
171188 } ;
189+
172190 const jsonResultOrFailProm = ( res , promise ) =>
173191 // TODO shouldn't this be a boolean instead of an object?
174192 promise
@@ -307,14 +325,19 @@ exports.registerApi = env => {
307325 }
308326 ) ;
309327
310- app . post ( `${ exports . pathPrefix } /reset` , ensureAuthenticated , ensurePathExists , ( req , res ) => {
311- jsonResultOrFailProm (
312- res ,
313- autoStashExecuteAndPop ( [ 'reset' , `--${ req . body . mode } ` , req . body . to ] , req . body . path )
314- )
315- . then ( emitGitDirectoryChanged . bind ( null , req . body . path ) )
316- . then ( emitWorkingTreeChanged . bind ( null , req . body . path ) ) ;
317- } ) ;
328+ app . post (
329+ `${ exports . pathPrefix } /reset` ,
330+ ensureAuthenticated ,
331+ ensurePathExists ,
332+ jw ( async req => {
333+ const repoPath = req . body . path ;
334+ await autoStash ( repoPath , ( ) =>
335+ gitPromise ( [ 'reset' , `--${ req . body . mode } ` , req . body . to ] , repoPath )
336+ ) ;
337+ await emitGitDirectoryChanged ( repoPath ) ;
338+ await emitWorkingTreeChanged ( repoPath ) ;
339+ } )
340+ ) ;
318341
319342 app . get ( `${ exports . pathPrefix } /diff` , ensureAuthenticated , ensurePathExists , ( req , res ) => {
320343 const isIgnoreWhiteSpace = req . query . whiteSpace === 'true' ? true : false ;
@@ -564,12 +587,15 @@ exports.registerApi = env => {
564587 }
565588 ) ;
566589
567- app . get ( `${ exports . pathPrefix } /tags` , ensureAuthenticated , ensurePathExists , ( req , res ) => {
568- let pathToRepo = req . query . path ;
569- nodegit . Repository . open ( pathToRepo ) . then ( function ( repo ) {
570- jsonResultOrFailProm ( res , nodegit . Tag . list ( repo ) ) ;
571- } ) ;
572- } ) ;
590+ app . get (
591+ `${ exports . pathPrefix } /tags` ,
592+ ensureAuthenticated ,
593+ ensurePathExists ,
594+ jw ( req => {
595+ let pathToRepo = req . query . path ;
596+ return nodegit . Repository . open ( pathToRepo ) . then ( repo => nodegit . Tag . list ( repo ) ) ;
597+ } )
598+ ) ;
573599
574600 app . get (
575601 `${ exports . pathPrefix } /remote/tags` ,
@@ -643,28 +669,31 @@ exports.registerApi = env => {
643669 }
644670 ) ;
645671
646- app . post ( `${ exports . pathPrefix } /checkout` , ensureAuthenticated , ensurePathExists , ( req , res ) => {
647- const arg = ! ! req . body . sha1
648- ? [ 'checkout' , '-b' , req . body . name . trim ( ) , req . body . sha1 ]
649- : [ 'checkout' , req . body . name . trim ( ) ] ;
650-
651- jsonResultOrFailProm ( res , autoStashExecuteAndPop ( arg , req . body . path ) )
652- . then ( emitGitDirectoryChanged . bind ( null , req . body . path ) )
653- . then ( emitWorkingTreeChanged . bind ( null , req . body . path ) ) ;
654- } ) ;
672+ app . post (
673+ `${ exports . pathPrefix } /checkout` ,
674+ ensureAuthenticated ,
675+ ensurePathExists ,
676+ jw ( async req => {
677+ const arg = ! ! req . body . sha1
678+ ? [ 'checkout' , '-b' , req . body . name . trim ( ) , req . body . sha1 ]
679+ : [ 'checkout' , req . body . name . trim ( ) ] ;
680+ const repoPath = req . body . path ;
681+ await autoStash ( repoPath , ( ) => gitPromise ( arg , repoPath ) ) ;
682+ await emitGitDirectoryChanged ( repoPath ) ;
683+ await emitWorkingTreeChanged ( repoPath ) ;
684+ } )
685+ ) ;
655686
656687 app . post (
657688 `${ exports . pathPrefix } /cherrypick` ,
658689 ensureAuthenticated ,
659690 ensurePathExists ,
660- ( req , res ) => {
661- jsonResultOrFailProm (
662- res ,
663- autoStashExecuteAndPop ( [ 'cherry-pick' , req . body . name . trim ( ) ] , req . body . path )
664- )
665- . then ( emitGitDirectoryChanged . bind ( null , req . body . path ) )
666- . then ( emitWorkingTreeChanged . bind ( null , req . body . path ) ) ;
667- }
691+ jw ( async req => {
692+ const repoPath = req . body . path ;
693+ await autoStash ( repoPath , ( ) => gitPromise ( [ 'cherry-pick' , req . body . name . trim ( ) ] , repoPath ) ) ;
694+ await emitGitDirectoryChanged ( repoPath ) ;
695+ await emitWorkingTreeChanged ( repoPath ) ;
696+ } )
668697 ) ;
669698
670699 app . get ( `${ exports . pathPrefix } /checkout` , ensureAuthenticated , ensurePathExists , ( req , res ) => {
@@ -919,13 +948,60 @@ exports.registerApi = env => {
919948 jsonResultOrFailProm ( res , task ) ;
920949 } ) ;
921950
922- app . get ( `${ exports . pathPrefix } /stashes` , ensureAuthenticated , ensurePathExists , ( req , res ) => {
923- const task = gitPromise (
924- [ 'stash' , 'list' , '--decorate=full' , '--pretty=fuller' , '-z' , '--parents' , '--numstat' ] ,
925- req . query . path
926- ) . then ( gitParser . parseGitLog ) ;
927- jsonResultOrFailProm ( res , task ) ;
951+ /**
952+ * @param {nodegit.Commit } c
953+ */
954+ const formatCommit = c => ( {
955+ commitDate : c . date ( ) . toJSON ( ) ,
956+ message : c . message ( ) ,
957+ sha1 : c . sha ( ) ,
928958 } ) ;
959+ /**
960+ * @param {nodegit.Commit } c
961+ */
962+ const getFileStats = async c => {
963+ const diffList = await c . getDiff ( ) ;
964+ // Each diff has the entire patch set for some reason
965+ const patches = await diffList [ 0 ] ?. patches ( ) ;
966+ if ( ! patches ?. length ) return [ ] ;
967+
968+ return patches . map ( patch => {
969+ const stats = patch . lineStats ( ) ;
970+ const oldFileName = patch . oldFile ( ) . path ( ) ;
971+ const displayName = patch . newFile ( ) . path ( ) ;
972+ return {
973+ additions : stats . total_additions ,
974+ deletions : stats . total_deletions ,
975+ fileName : displayName ,
976+ oldFileName,
977+ displayName,
978+ // TODO figure out how to get this
979+ type : 'text' ,
980+ } ;
981+ } ) ;
982+ } ;
983+
984+ app . get (
985+ `${ exports . pathPrefix } /stashes` ,
986+ ensureAuthenticated ,
987+ ensurePathExists ,
988+ jw ( async req => {
989+ const repo = await getRepo ( req . query . path ) ;
990+ const oids = [ ] ;
991+ await nodegit . Stash . foreach ( repo , ( index , message , oid ) => {
992+ oids . push ( oid ) ;
993+ } ) ;
994+ const stashes = await Promise . all ( oids . map ( oid => repo . getCommit ( oid ) ) ) ;
995+ return Promise . all (
996+ stashes . map ( async ( stash , index ) => ( {
997+ ...formatCommit ( stash ) ,
998+ reflogId : `${ index } ` ,
999+ reflogName : `stash@{${ index } }` ,
1000+ fileLineDiffs : await getFileStats ( stash ) ,
1001+ } ) )
1002+ ) ;
1003+ } )
1004+ ) ;
9291005
9301006 app . post ( `${ exports . pathPrefix } /stashes` , ensureAuthenticated , ensurePathExists , ( req , res ) => {
9311007 jsonResultOrFailProm (
0 commit comments