import chalk from 'chalk' ; import fs from 'fs-extra' ; import path from 'path' ; import { isMatch } from 'micromatch' ; import { ExitFailedError } from '../errors' ; import { gpExecSync } from '../utils' ; import { getRepoRootPath } from './repo_root_path' ; const CONFIG_NAME = '.graphite_repo_config' ; const CURRENT_REPO_CONFIG_PATH = path . join ( getRepoRootPath ( ) , CONFIG_NAME ) ; type RepoConfigT = { owner ? : string ; name ? : string ; trunk ? : string ; ignoreBranches ? : string [ ] ; logSettings ? : { maxStacksShownBehindTrunk ? : number ; maxDaysShownBehindTrunk ? : number ; } ; maxStacksShownBehindTrunk ? : number ; maxDaysShownBehindTrunk ? : number ; maxBranchLength ? : number ; lastFetchedPRInfoMs ? : number ; } ; class RepoConfig { _data : RepoConfigT ; public graphiteInitialized ( ) { return fs . existsSync ( CURRENT_REPO_CONFIG_PATH ) ; } constructor ( data ) { this . _data = data ; } private save ( ) { fs . writeFileSync ( CURRENT_REPO_CONFIG_PATH , JSON . stringify ( this . _data , null , 2 ) ) ; } public getRepoOwner ( ) { const configOwner = this . _data . owner ; if ( configOwner ) { return configOwner ; } const inferredInfo = inferRepoGitHubInfo ( ) ; if ( inferredInfo ?. repoOwner ) { return inferredInfo . repoOwner ; } throw new ExitFailedError ( "Could not determine the owner of this repo (e.g. 'screenplaydev' in the repo 'screenplaydev/graphite-cli'). Please run `gt repo owner --set <owner>` to manually set the repo owner." ) ; } public path ( ) { return CURRENT_REPO_CONFIG_PATH ; } public setTrunk ( trunkName ) { this . _data . trunk = trunkName ; this . save ( ) ; } public getTrunk ( ) { return this . _data . trunk ; } public addIgnoreBranchPatterns ( ignoreBranches ) { if ( ! this . _data . ignoreBranches ) { this . _data . ignoreBranches = [ ] ; } this . _data . ignoreBranches = this . getIgnoreBranches ( ) . concat ( ignoreBranches ) ; this . save ( ) ; } public removeIgnoreBranches ( branchPatternToRemove ) { const ignoredBranches = this . getIgnoreBranches ( ) ; this . _data . ignoreBranches = ignoredBranches . filter ( function ( pattern ) { return pattern != branchPatternToRemove ; } ) ; this . save ( ) ; } public getIgnoreBranches ( ) { return this . _data . ignoreBranches || [ ] ; } public setRepoOwner ( owner ) { this . _data . owner = owner ; this . save ( ) ; } public getRepoName ( ) { if ( this . _data . name ) { return this . _data . name ; } const inferredInfo = inferRepoGitHubInfo ( ) ; if ( inferredInfo ?. repoName ) { return inferredInfo . repoName ; } throw new ExitFailedError ( "Could not determine the name of this repo (e.g. 'graphite-cli' in the repo 'screenplaydev/graphite-cli'). Please run `gt repo name --set <owner>` to manually set the repo name." ) ; } public setRepoName ( name ) { this . _data . name = name ; this . save ( ) ; } public getMaxDaysShownBehindTrunk ( ) { this . migrateLogSettings ( ) ; return this . _data . maxDaysShownBehindTrunk ?? 30 ; } public setMaxDaysShownBehindTrunk ( n ) { this . migrateLogSettings ( ) ; this . _data . maxDaysShownBehindTrunk = n ; this . save ( ) ; } public getMaxStacksShownBehindTrunk ( ) { this . migrateLogSettings ( ) ; return this . _data . maxStacksShownBehindTrunk ?? 10 ; } public setMaxStacksShownBehindTrunk ( n ) { this . migrateLogSettings ( ) ; this . _data . maxStacksShownBehindTrunk = n ; this . save ( ) ; } public branchIsIgnored ( branchName ) { return isMatch ( branchName , this . getIgnoreBranches ( ) ) ; } /**
     * These settings used to (briefly) live in logSettings. Moving these to live
     * in the top-level namespace now that they're shared between multiple
     * commands (e.g. log and stacks).
     */ public migrateLogSettings ( ) { const maxStacksShownBehindTrunk = this . _data . logSettings ?. maxStacksShownBehindTrunk ; if ( maxStacksShownBehindTrunk !== undefined ) { this . _data . maxStacksShownBehindTrunk = maxStacksShownBehindTrunk ; } const maxDaysShownBehindTrunk = this . _data . logSettings ?. maxDaysShownBehindTrunk ; if ( maxDaysShownBehindTrunk !== undefined ) { this . _data . maxDaysShownBehindTrunk = maxDaysShownBehindTrunk ; } this . _data . logSettings = undefined ; this . save ( ) ; } public getMaxBranchLength ( ) { return this . _data . maxBranchLength ?? 50 ; } public setMaxBranchLength ( numCommits ) { this . _data . maxBranchLength = numCommits ; this . save ( ) ; } public getLastFetchedPRInfoMs ( ) { return this . _data . lastFetchedPRInfoMs ; } public setLastFetchedPRInfoMs ( time ) { this . _data . lastFetchedPRInfoMs = time ; this . save ( ) ; } } function inferRepoGitHubInfo ( ) { const url = gpExecSync ( { command : `git config --get remote.origin.url` , } , ( _ ) => { return Buffer . alloc ( 0 ) ; } ) . toString ( ) . trim ( ) ; const inferError = new ExitFailedError ( `Failed to infer the owner and name of this repo from remote origin "${ url }". Please run \`gt repo owner --set <owner>\` and \`gt repo name --set <name>\` to manually set the repo owner/name. (e.g. in the repo 'screenplaydev/graphite-cli', 'screenplaydev' is the repo owner and 'graphite-cli' is the repo name)` ) ; if ( ! url || url . length === 0 ) { throw inferError ; } const { owner , name } = getOwnerAndNameFromURL ( url ) ; if ( owner === undefined || name === undefined ) { throw inferError ; } return { repoOwner : owner , repoName : name , } ; } function getOwnerAndNameFromURL ( originURL ) { let regex = undefined ; let url = originURL ; if ( url . endsWith ( '.git' ) ) { url = url . slice ( 0 , - '.git' . length ) ; } if ( url . startsWith ( 'git@github.com' ) ) { regex = /git@github.com:([^/]+)\/(.+)/ ; } else if ( url . startsWith ( 'https://' ) ) { regex = /https:\/\/github.com\/([^/]+)\/(.+)/ ; } else { return { owner : undefined , name : undefined , } ; } const matches = regex . exec ( url ) ; return { owner : matches ?. [ 1 ] , name : matches ?. [ 2 ] , } ; } export function getOwnerAndNameFromURLForTesting ( originURL ) { return getOwnerAndNameFromURL ( originURL ) ; } function readRepoConfig ( ) { if ( fs . existsSync ( CURRENT_REPO_CONFIG_PATH ) ) { const repoConfigRaw = fs . readFileSync ( CURRENT_REPO_CONFIG_PATH ) ; try { const parsedConfig = JSON . parse ( repoConfigRaw . toString ( ) . trim ( ) ) as RepoConfigT ; return new RepoConfig ( parsedConfig ) ; } catch ( e ) { console . log ( chalk . yellow ( `Warning: Malformed ${ CURRENT_REPO_CONFIG_PATH }` ) ) ; } } return new RepoConfig ( { } ) ; } const repoConfigSingleton = readRepoConfig ( ) ; export default repoConfigSingleton ;