Skip to content

Commit ed316c1

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. Changes: - 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 ed316c1

3 files changed

Lines changed: 95 additions & 14 deletions

File tree

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

Lines changed: 41 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,27 @@ 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(
5920+
repository,
5921+
fetchType,
5922+
undefined,
5923+
true
5924+
)
5925+
})
5926+
}
5927+
59005928
/**
59015929
* Fetch a particular remote in a repository.
59025930
*
@@ -5923,7 +5951,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
59235951
private async performFetch(
59245952
repository: Repository,
59255953
fetchType: FetchType,
5926-
remotes?: IRemote[]
5954+
remotes?: IRemote[],
5955+
skipFastForward?: boolean
59275956
): Promise<void> {
59285957
await this.withPushPullFetch(repository, async () => {
59295958
const gitStore = this.gitStoreCache.get(repository)
@@ -5954,14 +5983,16 @@ export class AppStore extends TypedBaseStore<IAppState> {
59545983
? 'Refreshing Repository'
59555984
: 'Refreshing repository'
59565985

5957-
this.updatePushPullFetchProgress(repository, {
5958-
kind: 'generic',
5959-
title: refreshTitle,
5960-
description: 'Fast-forwarding branches',
5961-
value: fetchWeight,
5962-
})
5986+
if (!skipFastForward) {
5987+
this.updatePushPullFetchProgress(repository, {
5988+
kind: 'generic',
5989+
title: refreshTitle,
5990+
description: 'Fast-forwarding branches',
5991+
value: fetchWeight,
5992+
})
59635993

5964-
await this.fastForwardBranches(repository)
5994+
await this.fastForwardBranches(repository)
5995+
}
59655996

59665997
this.updatePushPullFetchProgress(repository, {
59675998
kind: 'generic',

app/src/ui/app.tsx

Lines changed: 43 additions & 4 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,22 +659,61 @@ 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
671684
this.props.dispatcher.mergeBranch(
672685
repository,
673-
contributionTargetDefaultBranch,
686+
branchToMerge,
674687
mergeStatus
675688
)
676689
}
677690

691+
/**
692+
* Returns the remote tracking branch for a given branch if available.
693+
* For remote branches, returns the branch itself.
694+
* For local branches, finds the corresponding remote branch in allBranches.
695+
* Falls back to the original branch if no remote tracking branch is found.
696+
*/
697+
private getRemoteTrackingBranch(
698+
branch: Branch,
699+
allBranches: ReadonlyArray<Branch>
700+
): Branch {
701+
if (branch.type === BranchType.Remote) {
702+
return branch
703+
}
704+
705+
const upstream = branch.upstream
706+
if (upstream === null) {
707+
return branch
708+
}
709+
710+
return (
711+
allBranches.find(
712+
b => b.type === BranchType.Remote && b.name === upstream
713+
) ?? branch
714+
)
715+
}
716+
678717
private mergeBranch(isSquash: boolean = false) {
679718
const selectedState = this.state.selectedState
680719
if (

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)