Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions app/src/lib/stores/app-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2908,9 +2908,16 @@ export class AppStore extends TypedBaseStore<IAppState> {

let contributionTargetDefaultBranch: string | undefined
if (selectedRepository instanceof Repository) {
const targetBranch = findContributionTargetDefaultBranch(
selectedRepository,
branchesState
)
// Show the remote tracking branch name (e.g. "origin/main") in the
// menu label, since we merge from the remote ref, not the local one.
contributionTargetDefaultBranch =
findContributionTargetDefaultBranch(selectedRepository, branchesState)
?.name ?? undefined
targetBranch !== null
? targetBranch.upstream ?? targetBranch.name
: undefined
}

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

/**
* Fetch all relevant remotes without fast-forwarding local branches.
*
* This updates remote tracking refs (e.g. origin/main) but leaves local
* branches untouched. Useful when you only need to merge from a remote
* ref without altering the local branch state.
*/
public _fetchWithoutFastForward(
repository: Repository,
fetchType: FetchType
): Promise<void> {
return this.withRefreshedGitHubRepository(repository, repository => {
return this.performFetch(repository, fetchType, undefined, true)
})
}

/**
* Fetch a particular remote in a repository.
*
Expand All @@ -5923,7 +5946,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
private async performFetch(
repository: Repository,
fetchType: FetchType,
remotes?: IRemote[]
remotes?: IRemote[],
skipFastForward?: boolean
): Promise<void> {
await this.withPushPullFetch(repository, async () => {
const gitStore = this.gitStoreCache.get(repository)
Expand Down Expand Up @@ -5954,14 +5978,16 @@ export class AppStore extends TypedBaseStore<IAppState> {
? 'Refreshing Repository'
: 'Refreshing repository'

this.updatePushPullFetchProgress(repository, {
kind: 'generic',
title: refreshTitle,
description: 'Fast-forwarding branches',
value: fetchWeight,
})
if (!skipFastForward) {
this.updatePushPullFetchProgress(repository, {
kind: 'generic',
title: refreshTitle,
description: 'Fast-forwarding branches',
value: fetchWeight,
})

await this.fastForwardBranches(repository)
await this.fastForwardBranches(repository)
}

this.updatePushPullFetchProgress(repository, {
kind: 'generic',
Expand Down
49 changes: 42 additions & 7 deletions app/src/ui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
getNonGitHubUrl,
isRepositoryWithGitHubRepository,
} from '../models/repository'
import { Branch } from '../models/branch'
import { Branch, BranchType } from '../models/branch'
import { PreferencesTab } from '../models/preferences'
import { findItemByAccessKey, itemIsSelectable } from '../models/app-menu'
import { Account, isDotComAccount } from '../models/account'
Expand Down Expand Up @@ -659,19 +659,54 @@ export class App extends React.Component<IAppProps, IAppState> {
return
}

await this.props.dispatcher.fetch(repository, FetchType.UserInitiatedTask)
// Fetch from the remote to update the remote tracking ref (e.g.
// origin/main) without fast-forwarding local branches.
await this.props.dispatcher.fetchWithoutFastForward(
repository,
FetchType.UserInitiatedTask
)

// Merge the remote tracking branch (e.g. origin/main) instead of the
// local branch, so that the local default branch is not altered as a
// side effect.
const branchToMerge = this.getRemoteTrackingBranch(
contributionTargetDefaultBranch,
state.branchesState.allBranches
)

this.props.dispatcher.initializeMergeOperation(
repository,
false,
contributionTargetDefaultBranch
branchToMerge
)

const { mergeStatus } = state.compareState
this.props.dispatcher.mergeBranch(
repository,
contributionTargetDefaultBranch,
mergeStatus
this.props.dispatcher.mergeBranch(repository, branchToMerge, mergeStatus)
}

/**
* Returns the remote tracking branch for a given branch if available.
* For remote branches, returns the branch itself.
* For local branches, finds the corresponding remote branch in allBranches.
* Falls back to the original branch if no remote tracking branch is found.
*/
private getRemoteTrackingBranch(
branch: Branch,
allBranches: ReadonlyArray<Branch>
): Branch {
if (branch.type === BranchType.Remote) {
return branch
}

const upstream = branch.upstream
if (upstream === null) {
return branch
}

return (
allBranches.find(
b => b.type === BranchType.Remote && b.name === upstream
) ?? branch
)
}

Expand Down
11 changes: 11 additions & 0 deletions app/src/ui/dispatcher/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,17 @@ export class Dispatcher {
return this.appStore._fetch(repository, fetchType)
}

/**
* Fetch all refs without fast-forwarding local branches.
* Updates remote tracking refs only, leaving local branches untouched.
*/
public fetchWithoutFastForward(
repository: Repository,
fetchType: FetchType
): Promise<void> {
return this.appStore._fetchWithoutFastForward(repository, fetchType)
}

/** Publish the repository to GitHub with the given properties. */
public publishRepository(
repository: Repository,
Expand Down
Loading