import { GitCmdClient } from "./git-cmd-client" ; export type CommitNode = string [ ] ; export class CommitExplorer { private _gitCmdClient = new GitCmdClient ( ) ; private _commitNodes ! : CommitNode [ ] ; private _branchName ! : string ; private _branchNameCache : { [ hash ] : string [ ] ; } = { } ; getCommitNodes ( ) { return this . _gitCmdClient . logGraph ( ) . split ( "\n" ) . map ( ( hashes ) => hashes . replace ( /\*|\/|\||\\|_|-+\.|/g , "" ) . split ( " " ) . filter ( hash => ! ! hash ) ) . filter ( ( hashes ) => hashes . length ) ; } getCurrentBranchName ( ) { const currentName = this . _gitCmdClient . currentName ( ) . replace ( "\n" , "" ) ; if ( currentName . startsWith ( "(HEAD detached" ) || currentName . startsWith ( "(no branch" ) || currentName . startsWith ( "(detached from" ) || ( currentName . startsWith ( "[" ) && currentName . indexOf ( "detached" ) !== - 1 ) ) { throw new Error ( "Can't detect branch name because HEAD is on detached commit node." ) ; } return currentName ; } getCurrentCommitHash ( ) { const currentName = this . _branchName ; if ( ! currentName || ! currentName . length ) { throw new Error ( "Fail to detect the current branch." ) ; } return this . _gitCmdClient . revParse ( currentName ) . replace ( "\n" , "" ) ; } getBranchNames ( hash ) { if ( this . _branchNameCache [ hash ] ) return this . _branchNameCache [ hash ] ; const names = this . _gitCmdClient . containedBranches ( hash ) . split ( "\n" ) . filter ( h => ! ! h ) . map ( branch => branch . replace ( "*" , "" ) . trim ( ) ) ; this . _branchNameCache [ hash ] = names ; return names ; } getAllBranchNames ( ) { return this . _gitCmdClient . branches ( ) . split ( "\n" ) . map ( b => b . replace ( /^\*/ , "" ) . trim ( ) . split ( " " ) [ 0 ] ) . filter ( b => ! ! b || b === this . _branchName ) . filter ( ( x , i , self ) => self . indexOf ( x ) === i ) ; } getIntersection ( hash ) { try { return this . _gitCmdClient . mergeBase ( hash , this . _branchName ) . slice ( 0 , 8 ) ; } catch ( e ) { } } getBranchHash ( ) { const branches = this . getAllBranchNames ( ) ; return branches . map ( b => { const hash = this . getIntersection ( b ) ; const time = hash ? new Date ( this . _gitCmdClient . logTime ( hash ) . trim ( ) ) . getTime ( ) : Number . MAX_SAFE_INTEGER ; return { hash , time } ; } ) . filter ( a => ! ! a . hash ) . sort ( ( a , b ) => a . time - b . time ) . map ( b => b . hash ) [ 0 ] ; } getCandidateHashes ( ) { const mergedBranches = this . getBranchNames ( this . _commitNodes [ 0 ] [ 0 ] ) . filter ( b => ! b . endsWith ( "/" + this . _branchName ) && b !== this . _branchName ) ; return this . _commitNodes . map ( c => c [ 0 ] ) . filter ( c => { const branches = this . getBranchNames ( c ) ; const hasCurrent = ! ! branches . find ( b => this . _branchName === b ) ; const others = branches . filter ( b => { return ! ( b . endsWith ( this . _branchName ) || ( mergedBranches . length && mergedBranches . some ( c => b === c ) ) ) ; } ) ; return hasCurrent && ! ! others . length ; } ) ; } isReachable ( a , b ) { const between = this . _gitCmdClient . logBetween ( a , b ) . trim ( ) ; return ! between ; } findBaseCommitHash ( candidateHashes , branchHash ) { const traverseLog = ( candidateHash ) : boolean | undefined => { if ( candidateHash === branchHash ) return true ; return this . isReachable ( candidateHash , branchHash ) ; } ; const target = candidateHashes . find ( hash => ! ! traverseLog ( hash ) ) ; return target ; } getBaseCommitHash ( ) { this . _branchName = this . getCurrentBranchName ( ) ; this . _commitNodes = this . getCommitNodes ( ) ; const candidateHashes = this . getCandidateHashes ( ) ; const branchHash = this . getBranchHash ( ) ; if ( ! branchHash ) return null ; const baseHash = this . findBaseCommitHash ( candidateHashes , branchHash ) ; if ( ! baseHash ) return null ; const result = this . _gitCmdClient . revParse ( baseHash ) . replace ( "\n" , "" ) ; return result ? result : null ; } }