@@ -3,10 +3,16 @@ import { getGitEnv } from './gitAsync';
33
44type GitFetchParams = {
55 cwd : string ;
6- /** Remote to fetch from. If not specified, fetches all remotes. */
7- remote ?: string ;
8- /** Branch to fetch. This will be ignored if `remote` is not also specified. */
9- branch ?: string ;
6+ /**
7+ * Remote to fetch from. This should almost always be set but might be an empty string
8+ * if the repo somehow has no remotes configured.
9+ */
10+ remote : string ;
11+ /**
12+ * Branch to fetch. It will be converted to a full refspec for fetching:
13+ * e.g. `branch: 'main', remote: 'origin'` will be converted to `+main:refs/remotes/origin/main`.
14+ */
15+ branch : string ;
1016 /** Set depth to this number of commits (mutually exclusive with `deepen` and `unshallow`) */
1117 depth ?: number ;
1218 /** Deepen a shallow clone by this number of commits (mutually exclusive with `depth` and `unshallow`) */
@@ -20,6 +26,9 @@ type GitFetchParams = {
2026 * Wrapper for `git fetch`. If `verbose` is true, log the command before starting, and display output
2127 * on stdout (except in tests). In tests with `verbose`, the output will be logged all together to
2228 * `console.log` when the command finishes (for easier mocking/capturing).
29+ *
30+ * This converts `remote` and `branch` into a fully qualified refspec, so it doesn't matter if
31+ * the remote branch is tracked or not in the local repository.
2332 */
2433export function gitFetch ( params : GitFetchParams ) : GitProcessOutput & { errorMessage ?: string } {
2534 const { remote, branch, depth, deepen, unshallow, cwd, verbose } = params ;
@@ -31,22 +40,29 @@ export function gitFetch(params: GitFetchParams): GitProcessOutput & { errorMess
3140
3241 const extraArgs = depth ? [ `--depth=${ depth } ` ] : deepen ? [ `--deepen=${ deepen } ` ] : unshallow ? [ '--unshallow' ] : [ ] ;
3342
34- let description = remote
35- ? `Fetching ${ branch ? `branch "${ branch } " from ` : '' } remote "${ remote } "`
36- : 'Fetching all remotes' ;
43+ // Be specific with the ref being fetched, so we don't have to worry about tracking configs.
44+ // In git fetch <remote> +<src>:<dst>...
45+ // - The + means allow non-fast-forward updates (in case the remote was force pushed).
46+ // - <src> refs/heads/${branch} is resolved against the remote's advertised refs. The fully
47+ // qualified ref is unambiguous, whereas bare branch names can be silently misresolved,
48+ // causing git to treat the ref as absent and delete the local tracking ref.
49+ // - <dst> refs/remotes/${remote}/${branch} is resolved locally and only moves the tracking ref
50+ // for the remote branch, not the local refs/heads/${branch} or its tracking config.
51+ const resolvedBranch = remote ? `+refs/heads/${ branch } :refs/remotes/${ remote } /${ branch } ` : undefined ;
3752
38- if ( extraArgs . length ) {
39- description += ` (with ${ extraArgs . join ( ' ' ) } )` ;
40- }
53+ const shortDescription = `Fetching ${ resolvedBranch ? `branch "${ branch } " from remote "${ remote } "` : 'all remotes' } ` ;
4154
55+ let description = shortDescription ;
56+ resolvedBranch && ( description += ` (${ resolvedBranch } )` ) ;
57+ extraArgs . length && ( description += ` (with ${ extraArgs . join ( ' ' ) } )` ) ;
4258 shouldLog && console . log ( description + '...' ) ;
4359
4460 const result : ReturnType < typeof gitFetch > = git (
4561 [
4662 'fetch' ,
4763 ...extraArgs ,
4864 // If the remote is unknown, don't specify the branch (fetching a branch without a remote is invalid)
49- ...( remote && branch ? [ remote , branch ] : remote ? [ remote ] : [ ] ) ,
65+ ...( resolvedBranch ? [ remote , resolvedBranch ] : [ ] ) ,
5066 ] ,
5167 { cwd, stdio : shouldLog === 'live' ? 'inherit' : 'pipe' }
5268 ) ;
@@ -59,7 +75,7 @@ export function gitFetch(params: GitFetchParams): GitProcessOutput & { errorMess
5975 result . stderr && log ( result . stderr ) ;
6076 }
6177
62- let message = `${ description } ${ result . success ? 'completed successfully' : `failed (code ${ result . status } )` } ` ;
78+ let message = `${ shortDescription } ${ result . success ? 'completed successfully' : `failed (code ${ result . status } )` } ` ;
6379 if ( shouldLog ) {
6480 log ( message ) ;
6581 message += ' - see above for details' ;
0 commit comments