@@ -266,6 +266,8 @@ import {
266266 listWorktrees ,
267267 unstageAll ,
268268 git ,
269+ IGitStringExecutionOptions ,
270+ IGitStringResult ,
269271} from '../git'
270272import {
271273 installGlobalLFSFilters ,
@@ -451,6 +453,11 @@ import {
451453 gatherCommitContext ,
452454} from '../copilot-conflict-context'
453455import { resolveWithin } from '../path'
456+ import {
457+ executionOptionsWithProgress ,
458+ SingleBranchFetchProgressParser ,
459+ } from '../progress'
460+ import { envForRemoteOperation } from '../git/environment'
454461
455462const LastSelectedRepositoryIDKey = 'last-selected-repository-id'
456463
@@ -6075,6 +6082,171 @@ export class AppStore extends TypedBaseStore<IAppState> {
60756082 } )
60766083 }
60776084
6085+ public async _fetchSingleBranch (
6086+ repository : Repository ,
6087+ branch : Branch
6088+ ) : Promise < void > {
6089+ const state = this . repositoryStateCache . get ( repository )
6090+ // Don't allow concurrent network operations.
6091+ if ( state . isPushPullFetchInProgress ) {
6092+ this . _showPopup ( {
6093+ type : PopupType . Error ,
6094+ error : new Error (
6095+ 'Another push/pull/fetch request is in progress.\nTry again after the ongoing request is finished'
6096+ ) ,
6097+ } )
6098+ return
6099+ }
6100+
6101+ return this . withRefreshedGitHubRepository ( repository , repo => {
6102+ return this . performFetchSingleBranch ( repo , branch )
6103+ } )
6104+ }
6105+
6106+ /** This shouldn't be called directly. See `Dispatcher`. */
6107+ private async performFetchSingleBranch (
6108+ repository : Repository ,
6109+ branch : Branch
6110+ ) {
6111+ const isRemote = branch . type === BranchType . Remote
6112+
6113+ const remoteName = isRemote ? branch . remoteName : branch . upstreamRemoteName
6114+ const remoteBranchName = isRemote
6115+ ? branch . nameWithoutRemote
6116+ : branch . upstreamWithoutRemote
6117+
6118+ if ( ! remoteName ) {
6119+ throw new Error ( 'Remote name not found' )
6120+ }
6121+ if ( ! remoteBranchName ) {
6122+ throw new Error ( 'Remote branch not found' )
6123+ }
6124+
6125+ const isBackgroundTask = false
6126+ const gitStore = this . gitStoreCache . get ( repository )
6127+
6128+ // repository.url
6129+ const remote = { name : remoteName , url : 'file://' }
6130+
6131+ const progressCb = ( progress : IFetchProgress ) => {
6132+ this . updatePushPullFetchProgress ( repository , progress )
6133+ }
6134+ const progressTitle = isRemote
6135+ ? `Fetching ${ branch . name } `
6136+ : `Fetching ${ remoteBranchName } `
6137+ const kind = 'fetch'
6138+
6139+ const fetchFn = async ( isRemote : boolean ) : Promise < IGitStringResult > => {
6140+ let opts : IGitStringExecutionOptions = {
6141+ successExitCodes : new Set ( [ 0 ] ) ,
6142+ }
6143+ if ( remote . url ) {
6144+ opts = {
6145+ ...opts ,
6146+ env : await envForRemoteOperation ( remote . url ) ,
6147+ }
6148+ }
6149+ opts = await executionOptionsWithProgress (
6150+ { ...opts , trackLFSProgress : true , isBackgroundTask } ,
6151+ new SingleBranchFetchProgressParser ( ) ,
6152+ progress => {
6153+ if ( progress . kind === 'context' ) {
6154+ const text = progress . text
6155+ if (
6156+ ! text . startsWith ( 'remote: Counting objects' ) &&
6157+ ! text . startsWith ( 'remote: Compressing objects' )
6158+ ) {
6159+ return
6160+ }
6161+ }
6162+
6163+ const description =
6164+ progress . kind === 'progress' ? progress . details . text : progress . text
6165+ const value = progress . percent
6166+
6167+ progressCb ( {
6168+ kind,
6169+ title : progressTitle ,
6170+ description,
6171+ value,
6172+ remote : remote . name ,
6173+ } )
6174+ }
6175+ )
6176+ const flags = isRemote
6177+ ? [ 'fetch' , '--progress' , '--recurse-submodules=on-demand' , remoteName ]
6178+ : [
6179+ 'fetch' ,
6180+ '--progress' ,
6181+ '--show-forced-updates' ,
6182+ // '--no-write-fetch-head',
6183+ '--recurse-submodules=on-demand' ,
6184+ remoteName ,
6185+ ]
6186+
6187+ const branchTarget = isRemote
6188+ ? remoteBranchName
6189+ : `${ remoteBranchName } :${ remoteBranchName } `
6190+ const actionName = isRemote ? 'fetchRemoteBranch' : 'fetchLocalBranch'
6191+
6192+ const executionOpts = isRemote
6193+ ? opts
6194+ : {
6195+ ...opts ,
6196+ successExitCodes : new Set ( [ 0 , 1 ] ) ,
6197+ }
6198+
6199+ return await git (
6200+ [ ...flags , branchTarget ] ,
6201+ repository . path ,
6202+ actionName ,
6203+ executionOpts
6204+ )
6205+ }
6206+
6207+ const execFetchFn = async ( ) => {
6208+ // Initial progress
6209+ progressCb ( {
6210+ kind,
6211+ title : progressTitle ,
6212+ value : 0 ,
6213+ remote : remote . name ,
6214+ } )
6215+
6216+ await gitStore . performFailableOperation (
6217+ async ( ) => {
6218+ const result = await fetchFn ( isRemote )
6219+ if (
6220+ ! isRemote &&
6221+ result &&
6222+ ( result . stderr ?. includes ( 'rejected' ) ||
6223+ result . stderr ?. includes ( 'non-fast-forward' ) )
6224+ ) {
6225+ this . emitError (
6226+ new ErrorWithMetadata ( new Error ( result . stderr ) , { repository } )
6227+ )
6228+ }
6229+
6230+ await this . _refreshRepository ( repository )
6231+ } ,
6232+ {
6233+ backgroundTask : isBackgroundTask ,
6234+ }
6235+ )
6236+ }
6237+
6238+ try {
6239+ await this . withPushPullFetch ( repository , execFetchFn )
6240+ } catch ( error ) {
6241+ const errorWithMetadata = new ErrorWithMetadata ( error , {
6242+ repository,
6243+ } )
6244+ this . emitError ( errorWithMetadata )
6245+ } finally {
6246+ this . updatePushPullFetchProgress ( repository , null )
6247+ }
6248+ }
6249+
60786250 public async _resetHardToUpstream ( repository : Repository ) : Promise < void > {
60796251 const { branchesState } = this . repositoryStateCache . get ( repository )
60806252 const { tip } = branchesState
0 commit comments