Skip to content

Commit 9067ba3

Browse files
guplemclaude
andcommitted
feat: merge from remote tracking branch in Update from Default Branch
Instead of merging the local default branch (e.g. `main`), merge the remote tracking branch (e.g. `origin/main`) after fetching. This avoids altering local branches as a side effect of the update operation. - Fetch without fast-forwarding local branches before merging - Find and merge the remote tracking branch instead of the local one - Update the menu label to show the remote ref (e.g. "Update from origin/main") Follow-up to PR #111 as suggested by @pol-rivero. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bce9f0a commit 9067ba3

3 files changed

Lines changed: 89 additions & 17 deletions

File tree

app/src/lib/stores/app-store.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2908,9 +2908,16 @@ export class AppStore extends TypedBaseStore<IAppState> {
29082908

29092909
let contributionTargetDefaultBranch: string | undefined
29102910
if (selectedRepository instanceof Repository) {
2911+
const targetBranch = findContributionTargetDefaultBranch(
2912+
selectedRepository,
2913+
branchesState
2914+
)
2915+
// Show the remote tracking branch name (e.g. "origin/main") in the
2916+
// menu label, since we merge from the remote ref, not the local one.
29112917
contributionTargetDefaultBranch =
2912-
findContributionTargetDefaultBranch(selectedRepository, branchesState)
2913-
?.name ?? undefined
2918+
targetBranch !== null
2919+
? targetBranch.upstream ?? targetBranch.name
2920+
: undefined
29142921
}
29152922

29162923
// From the menu, we'll offer to force-push whenever it's possible, regardless
@@ -5897,6 +5904,22 @@ export class AppStore extends TypedBaseStore<IAppState> {
58975904
})
58985905
}
58995906

5907+
/**
5908+
* Fetch all relevant remotes without fast-forwarding local branches.
5909+
*
5910+
* This updates remote tracking refs (e.g. origin/main) but leaves local
5911+
* branches untouched. Useful when you only need to merge from a remote
5912+
* ref without altering the local branch state.
5913+
*/
5914+
public _fetchWithoutFastForward(
5915+
repository: Repository,
5916+
fetchType: FetchType
5917+
): Promise<void> {
5918+
return this.withRefreshedGitHubRepository(repository, repository => {
5919+
return this.performFetch(repository, fetchType, undefined, true)
5920+
})
5921+
}
5922+
59005923
/**
59015924
* Fetch a particular remote in a repository.
59025925
*
@@ -5923,7 +5946,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
59235946
private async performFetch(
59245947
repository: Repository,
59255948
fetchType: FetchType,
5926-
remotes?: IRemote[]
5949+
remotes?: IRemote[],
5950+
skipFastForward?: boolean
59275951
): Promise<void> {
59285952
await this.withPushPullFetch(repository, async () => {
59295953
const gitStore = this.gitStoreCache.get(repository)
@@ -5954,14 +5978,16 @@ export class AppStore extends TypedBaseStore<IAppState> {
59545978
? 'Refreshing Repository'
59555979
: 'Refreshing repository'
59565980

5957-
this.updatePushPullFetchProgress(repository, {
5958-
kind: 'generic',
5959-
title: refreshTitle,
5960-
description: 'Fast-forwarding branches',
5961-
value: fetchWeight,
5962-
})
5981+
if (!skipFastForward) {
5982+
this.updatePushPullFetchProgress(repository, {
5983+
kind: 'generic',
5984+
title: refreshTitle,
5985+
description: 'Fast-forwarding branches',
5986+
value: fetchWeight,
5987+
})
59635988

5964-
await this.fastForwardBranches(repository)
5989+
await this.fastForwardBranches(repository)
5990+
}
59655991

59665992
this.updatePushPullFetchProgress(repository, {
59675993
kind: 'generic',

app/src/ui/app.tsx

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
getNonGitHubUrl,
3030
isRepositoryWithGitHubRepository,
3131
} from '../models/repository'
32-
import { Branch } from '../models/branch'
32+
import { Branch, BranchType } from '../models/branch'
3333
import { PreferencesTab } from '../models/preferences'
3434
import { findItemByAccessKey, itemIsSelectable } from '../models/app-menu'
3535
import { Account, isDotComAccount } from '../models/account'
@@ -659,19 +659,54 @@ export class App extends React.Component<IAppProps, IAppState> {
659659
return
660660
}
661661

662-
await this.props.dispatcher.fetch(repository, FetchType.UserInitiatedTask)
662+
// Fetch from the remote to update the remote tracking ref (e.g.
663+
// origin/main) without fast-forwarding local branches.
664+
await this.props.dispatcher.fetchWithoutFastForward(
665+
repository,
666+
FetchType.UserInitiatedTask
667+
)
668+
669+
// Merge the remote tracking branch (e.g. origin/main) instead of the
670+
// local branch, so that the local default branch is not altered as a
671+
// side effect.
672+
const branchToMerge = this.getRemoteTrackingBranch(
673+
contributionTargetDefaultBranch,
674+
state.branchesState.allBranches
675+
)
663676

664677
this.props.dispatcher.initializeMergeOperation(
665678
repository,
666679
false,
667-
contributionTargetDefaultBranch
680+
branchToMerge
668681
)
669682

670683
const { mergeStatus } = state.compareState
671-
this.props.dispatcher.mergeBranch(
672-
repository,
673-
contributionTargetDefaultBranch,
674-
mergeStatus
684+
this.props.dispatcher.mergeBranch(repository, branchToMerge, mergeStatus)
685+
}
686+
687+
/**
688+
* Returns the remote tracking branch for a given branch if available.
689+
* For remote branches, returns the branch itself.
690+
* For local branches, finds the corresponding remote branch in allBranches.
691+
* Falls back to the original branch if no remote tracking branch is found.
692+
*/
693+
private getRemoteTrackingBranch(
694+
branch: Branch,
695+
allBranches: ReadonlyArray<Branch>
696+
): Branch {
697+
if (branch.type === BranchType.Remote) {
698+
return branch
699+
}
700+
701+
const upstream = branch.upstream
702+
if (upstream === null) {
703+
return branch
704+
}
705+
706+
return (
707+
allBranches.find(
708+
b => b.type === BranchType.Remote && b.name === upstream
709+
) ?? branch
675710
)
676711
}
677712

app/src/ui/dispatcher/dispatcher.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,17 @@ export class Dispatcher {
788788
return this.appStore._fetch(repository, fetchType)
789789
}
790790

791+
/**
792+
* Fetch all refs without fast-forwarding local branches.
793+
* Updates remote tracking refs only, leaving local branches untouched.
794+
*/
795+
public fetchWithoutFastForward(
796+
repository: Repository,
797+
fetchType: FetchType
798+
): Promise<void> {
799+
return this.appStore._fetchWithoutFastForward(repository, fetchType)
800+
}
801+
791802
/** Publish the repository to GitHub with the given properties. */
792803
public publishRepository(
793804
repository: Repository,

0 commit comments

Comments
 (0)