Skip to content
Open
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
115 changes: 115 additions & 0 deletions app/src/lib/stores/app-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ import {
listWorktrees,
unstageAll,
git,
IGitStringExecutionOptions,
} from '../git'
import {
installGlobalLFSFilters,
Expand Down Expand Up @@ -450,6 +451,8 @@ import {
gatherCommitContext,
} from '../copilot-conflict-context'
import { resolveWithin } from '../path'
import { executionOptionsWithProgress, FetchProgressParser } from '../progress'
import { envForRemoteOperation } from '../git/environment'

const LastSelectedRepositoryIDKey = 'last-selected-repository-id'

Expand Down Expand Up @@ -6074,6 +6077,118 @@ export class AppStore extends TypedBaseStore<IAppState> {
})
}

public async _fetchRemoteBranch(
repository: Repository,
branch: Branch
): Promise<void> {
return this.withRefreshedGitHubRepository(repository, repo => {
return this.performFetchRemoteBranch(repo, branch)
})
}

/** This shouldn't be called directly. See `Dispatcher`. */
private async performFetchRemoteBranch(
repository: Repository,
branch: Branch
) {
const isRemote = branch.type === BranchType.Remote
if (!isRemote) {
return
}

const remoteName = branch.remoteName
const remoteBranchName = branch.nameWithoutRemote

if (!remoteName) {
throw new Error('Remote name not found')
}

const isBackgroundTask = false
const gitStore = this.gitStoreCache.get(repository)

// repository.url
const remote = { name: remoteName, url: 'file://' }

const _fetchRemoteBranchProgressCallback = (progress: any) => {
console.log(progress, ' progress ')
}

const title = `Fetching ${remoteName}`
const kind = 'fetch'
let opts: IGitStringExecutionOptions = {
successExitCodes: new Set([0]),
}
if (remote.url) {
opts = {
...opts,
env: await envForRemoteOperation(remote.url),
}
}

opts = await executionOptionsWithProgress(
{ ...opts, trackLFSProgress: true, isBackgroundTask },
new FetchProgressParser(),
progress => {
// In addition to progress output from the remote end and from
// git itself, the stderr output from pull contains information
// about ref updates. We don't need to bring those into the progress
// stream so we'll just punt on anything we don't know about for now.
if (progress.kind === 'context') {
if (!progress.text.startsWith('remote: Counting objects')) {
return
}
}

const description =
progress.kind === 'progress' ? progress.details.text : progress.text
const value = progress.percent

_fetchRemoteBranchProgressCallback({
kind,
title,
description,
value,
remote: remote.name,
})
}
)

// Initial progress
_fetchRemoteBranchProgressCallback({
kind,
title,
value: 0,
remote: remote.name,
})

const fetchFn = async () => {
await git(
[
'fetch',
'--progress',
'--prune',
'--recurse-submodules=on-demand',
remoteName,
remoteBranchName,
],
repository.path,
'fetchRemoteBranch',
opts
)
return true
}

const fetchSucceeded = await gitStore.performFailableOperation(fetchFn, {
backgroundTask: isBackgroundTask,
})

if (fetchSucceeded) {
await this._refreshRepository(repository)
} else {
console.error('Fetch did not succeed')
}
}

public async _resetHardToUpstream(repository: Repository): Promise<void> {
const { branchesState } = this.repositoryStateCache.get(repository)
const { tip } = branchesState
Expand Down
16 changes: 16 additions & 0 deletions app/src/ui/branches/branch-list-item-context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { assertNever } from '../../lib/fatal-error'

interface IBranchContextMenuConfig {
name: string
remoteName?: string | null
nameWithoutRemote: string
isLocal: boolean
repoType: RepoType | undefined
Expand All @@ -14,6 +15,7 @@ interface IBranchContextMenuConfig {
onViewPullRequestOnGitHub?: () => void
onSetAsDefaultBranch?: (branchName: string) => void
onDeleteBranch?: (branchName: string) => void
onFetchRemoteBranch?: (branchName: string) => void
}

export function generateBranchContextMenuItems(
Expand All @@ -22,6 +24,7 @@ export function generateBranchContextMenuItems(
const {
name,
nameWithoutRemote,
remoteName,
isLocal,
repoType,
isInUseByOtherWorktree,
Expand All @@ -30,6 +33,7 @@ export function generateBranchContextMenuItems(
onViewPullRequestOnGitHub,
onSetAsDefaultBranch,
onDeleteBranch,
onFetchRemoteBranch,
} = config
const items = new Array<IMenuItem>()

Expand All @@ -41,6 +45,14 @@ export function generateBranchContextMenuItems(
})
}

if (!isLocal && onFetchRemoteBranch !== undefined) {
items.push({
label: getRemoteFetchBranchLabel(),
action: () => onFetchRemoteBranch(name),
enabled: !!remoteName,
})
}

items.push({
label: __DARWIN__ ? 'Copy Branch Name' : 'Copy branch name',
action: () => clipboard.writeText(name),
Expand Down Expand Up @@ -104,3 +116,7 @@ function getViewPullRequestLabel(repoType: RepoType): string {
return assertNever(repoType, `Unknown repo type: ${repoType}`)
}
}

function getRemoteFetchBranchLabel(): string {
return `Fetch branch`
}
14 changes: 12 additions & 2 deletions app/src/ui/branches/branch-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ interface IBranchListProps {

/** Optional: Callback for if delete context menu should exist */
readonly onDeleteBranch?: (branchName: string) => void

/** Optional: Callback if pull option for remote branch context menu should exist */
readonly onFetchRemoteBranch?: (branchName: string) => void
}

/** The Branches list component. */
Expand Down Expand Up @@ -234,7 +237,12 @@ export class BranchList extends React.Component<IBranchListProps> {
) => {
event.preventDefault()

const { onRenameBranch, onDeleteBranch, onSetAsDefaultBranch } = this.props
const {
onRenameBranch,
onDeleteBranch,
onSetAsDefaultBranch,
onFetchRemoteBranch,
} = this.props

if (
onRenameBranch === undefined &&
Expand All @@ -244,12 +252,13 @@ export class BranchList extends React.Component<IBranchListProps> {
return
}

const { type, name, nameWithoutRemote } = item.branch
const { type, name, nameWithoutRemote, remoteName } = item.branch
const isLocal = type === BranchType.Local
const isInUseByOtherWorktree = !!this.inUseByOtherWorktreeName(item)

const items = generateBranchContextMenuItems({
name,
remoteName,
nameWithoutRemote,
isLocal,
repoType: this.props.repository.gitHubRepository?.type,
Expand All @@ -260,6 +269,7 @@ export class BranchList extends React.Component<IBranchListProps> {
? undefined
: onSetAsDefaultBranch,
onDeleteBranch,
onFetchRemoteBranch,
})

showContextualMenu(items)
Expand Down
2 changes: 2 additions & 0 deletions app/src/ui/branches/branches-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ interface IBranchesContainerProps {
readonly onRenameBranch: (branchName: string) => void
readonly onSetAsDefaultBranch: (branchName: string) => void
readonly onDeleteBranch: (branchName: string) => void
readonly onFetchRemoteBranch: (branchName: string) => void

readonly branchSortOrder: BranchSortOrder

Expand Down Expand Up @@ -293,6 +294,7 @@ export class BranchesContainer extends React.Component<
onRenameBranch={this.props.onRenameBranch}
onSetAsDefaultBranch={this.props.onSetAsDefaultBranch}
onDeleteBranch={this.props.onDeleteBranch}
onFetchRemoteBranch={this.props.onFetchRemoteBranch}
/>
)
case BranchesTab.PullRequests: {
Expand Down
8 changes: 8 additions & 0 deletions app/src/ui/dispatcher/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,14 @@ export class Dispatcher {
return this.appStore._pull(repository)
}

/** Pull remote branch by name */
public fetchRemoteBranch(
repository: Repository,
branch: Branch
): Promise<void> {
return this.appStore._fetchRemoteBranch(repository, branch)
}

public async pullAllRepositories(): Promise<void> {
try {
await this.appStore._pullAllRepositories()
Expand Down
15 changes: 15 additions & 0 deletions app/src/ui/toolbar/branch-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class BranchDropdown extends React.Component<IBranchDropdownProps> {
branchSortOrder={this.props.branchSortOrder}
emoji={this.props.emoji}
onDeleteBranch={this.onDeleteBranch}
onFetchRemoteBranch={this.onFetchRemoteBranch}
onRenameBranch={this.onRenameBranch}
onSetAsDefaultBranch={this.onSetAsDefaultBranch}
underlineLinks={this.props.underlineLinks}
Expand Down Expand Up @@ -443,6 +444,20 @@ export class BranchDropdown extends React.Component<IBranchDropdownProps> {
})
}

private onFetchRemoteBranch = (branchName: string) => {
const branch = this.getBranchWithName(branchName)
const { dispatcher, repository } = this.props

if (!branch) {
return
}

// Only fetch remote branch
if (branch.type === BranchType.Remote) {
dispatcher.fetchRemoteBranch(repository, branch)
}
}

private onBadgeClick = () => {
// The badge can't be clicked while the CI status popover is shown, because
// in that case the Popover component will recognize the "click outside"
Expand Down
Loading