mirror of
https://github.com/desktop/desktop
synced 2024-09-13 21:31:32 +00:00
Merge branch 'development' into reuse-workflow
This commit is contained in:
commit
f8369105ec
|
@ -210,6 +210,9 @@ export interface IAppState {
|
|||
/** Should the app prompt the user to confirm a discard stash */
|
||||
readonly askForConfirmationOnDiscardStash: boolean
|
||||
|
||||
/** Should the app prompt the user to confirm a commit checkout? */
|
||||
readonly askForConfirmationOnCheckoutCommit: boolean
|
||||
|
||||
/** Should the app prompt the user to confirm a force push? */
|
||||
readonly askForConfirmationOnForcePush: boolean
|
||||
|
||||
|
|
|
@ -73,6 +73,11 @@ export function enableResetToCommit(): boolean {
|
|||
return enableDevelopmentFeatures()
|
||||
}
|
||||
|
||||
/** Should we allow checking out a single commit? */
|
||||
export function enableCheckoutCommit(): boolean {
|
||||
return enableBetaFeatures()
|
||||
}
|
||||
|
||||
/** Should ci check runs show logs? */
|
||||
export function enableCICheckRunsLogs(): boolean {
|
||||
return false
|
||||
|
|
|
@ -15,20 +15,18 @@ import {
|
|||
} from './environment'
|
||||
import { WorkingDirectoryFileChange } from '../../models/status'
|
||||
import { ManualConflictResolution } from '../../models/manual-conflict-resolution'
|
||||
import { CommitOneLine, shortenSHA } from '../../models/commit'
|
||||
|
||||
export type ProgressCallback = (progress: ICheckoutProgress) => void
|
||||
|
||||
async function getCheckoutArgs(
|
||||
repository: Repository,
|
||||
branch: Branch,
|
||||
account: IGitAccount | null,
|
||||
progressCallback?: ProgressCallback
|
||||
) {
|
||||
const baseArgs =
|
||||
progressCallback != null
|
||||
? [...gitNetworkArguments(), 'checkout', '--progress']
|
||||
: [...gitNetworkArguments(), 'checkout']
|
||||
function getCheckoutArgs(progressCallback?: ProgressCallback) {
|
||||
return progressCallback != null
|
||||
? [...gitNetworkArguments(), 'checkout', '--progress']
|
||||
: [...gitNetworkArguments(), 'checkout']
|
||||
}
|
||||
|
||||
async function getBranchCheckoutArgs(branch: Branch) {
|
||||
const baseArgs: ReadonlyArray<string> = []
|
||||
if (enableRecurseSubmodulesFlag()) {
|
||||
return branch.type === BranchType.Remote
|
||||
? baseArgs.concat(
|
||||
|
@ -39,11 +37,62 @@ async function getCheckoutArgs(
|
|||
'--'
|
||||
)
|
||||
: baseArgs.concat(branch.name, '--recurse-submodules', '--')
|
||||
} else {
|
||||
return branch.type === BranchType.Remote
|
||||
? baseArgs.concat(branch.name, '-b', branch.nameWithoutRemote, '--')
|
||||
: baseArgs.concat(branch.name, '--')
|
||||
}
|
||||
|
||||
return branch.type === BranchType.Remote
|
||||
? baseArgs.concat(branch.name, '-b', branch.nameWithoutRemote, '--')
|
||||
: baseArgs.concat(branch.name, '--')
|
||||
}
|
||||
|
||||
async function getCheckoutOpts(
|
||||
repository: Repository,
|
||||
account: IGitAccount | null,
|
||||
title: string,
|
||||
target: string,
|
||||
progressCallback?: ProgressCallback,
|
||||
initialDescription?: string
|
||||
): Promise<IGitExecutionOptions> {
|
||||
const opts: IGitExecutionOptions = {
|
||||
env: await envForRemoteOperation(
|
||||
account,
|
||||
getFallbackUrlForProxyResolve(account, repository)
|
||||
),
|
||||
expectedErrors: AuthenticationErrors,
|
||||
}
|
||||
|
||||
if (!progressCallback) {
|
||||
return opts
|
||||
}
|
||||
|
||||
const kind = 'checkout'
|
||||
|
||||
// Initial progress
|
||||
progressCallback({
|
||||
kind,
|
||||
title,
|
||||
description: initialDescription ?? title,
|
||||
value: 0,
|
||||
target,
|
||||
})
|
||||
|
||||
return await executionOptionsWithProgress(
|
||||
{ ...opts, trackLFSProgress: true },
|
||||
new CheckoutProgressParser(),
|
||||
progress => {
|
||||
if (progress.kind === 'progress') {
|
||||
const description = progress.details.text
|
||||
const value = progress.percent
|
||||
|
||||
progressCallback({
|
||||
kind,
|
||||
title,
|
||||
description,
|
||||
value,
|
||||
target,
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,44 +115,59 @@ export async function checkoutBranch(
|
|||
branch: Branch,
|
||||
progressCallback?: ProgressCallback
|
||||
): Promise<true> {
|
||||
let opts: IGitExecutionOptions = {
|
||||
env: await envForRemoteOperation(
|
||||
account,
|
||||
getFallbackUrlForProxyResolve(account, repository)
|
||||
),
|
||||
expectedErrors: AuthenticationErrors,
|
||||
}
|
||||
|
||||
if (progressCallback) {
|
||||
const title = `Checking out branch ${branch.name}`
|
||||
const kind = 'checkout'
|
||||
const targetBranch = branch.name
|
||||
|
||||
opts = await executionOptionsWithProgress(
|
||||
{ ...opts, trackLFSProgress: true },
|
||||
new CheckoutProgressParser(),
|
||||
progress => {
|
||||
if (progress.kind === 'progress') {
|
||||
const description = progress.details.text
|
||||
const value = progress.percent
|
||||
|
||||
progressCallback({ kind, title, description, value, targetBranch })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Initial progress
|
||||
progressCallback({ kind, title, value: 0, targetBranch })
|
||||
}
|
||||
|
||||
const args = await getCheckoutArgs(
|
||||
const opts = await getCheckoutOpts(
|
||||
repository,
|
||||
branch,
|
||||
account,
|
||||
`Checking out branch ${branch.name}`,
|
||||
branch.name,
|
||||
progressCallback,
|
||||
`Switching to ${__DARWIN__ ? 'Branch' : 'branch'}`
|
||||
)
|
||||
|
||||
const baseArgs = getCheckoutArgs(progressCallback)
|
||||
const args = [...baseArgs, ...(await getBranchCheckoutArgs(branch))]
|
||||
|
||||
await git(args, repository.path, 'checkoutBranch', opts)
|
||||
|
||||
// we return `true` here so `GitStore.performFailableGitOperation`
|
||||
// will return _something_ differentiable from `undefined` if this succeeds
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check out the given commit.
|
||||
* Literally invokes `git checkout <commit SHA>`.
|
||||
*
|
||||
* @param repository - The repository in which the branch checkout should
|
||||
* take place
|
||||
*
|
||||
* @param commit - The commit that should be checked out
|
||||
*
|
||||
* @param progressCallback - An optional function which will be invoked
|
||||
* with information about the current progress
|
||||
* of the checkout operation. When provided this
|
||||
* enables the '--progress' command line flag for
|
||||
* 'git checkout'.
|
||||
*/
|
||||
export async function checkoutCommit(
|
||||
repository: Repository,
|
||||
account: IGitAccount | null,
|
||||
commit: CommitOneLine,
|
||||
progressCallback?: ProgressCallback
|
||||
): Promise<true> {
|
||||
const title = `Checking out ${__DARWIN__ ? 'Commit' : 'commit'}`
|
||||
const opts = await getCheckoutOpts(
|
||||
repository,
|
||||
account,
|
||||
title,
|
||||
shortenSHA(commit.sha),
|
||||
progressCallback
|
||||
)
|
||||
|
||||
await git(args, repository.path, 'checkoutBranch', opts)
|
||||
const baseArgs = getCheckoutArgs(progressCallback)
|
||||
const args = [...baseArgs, commit.sha]
|
||||
|
||||
await git(args, repository.path, 'checkoutCommit', opts)
|
||||
|
||||
// we return `true` here so `GitStore.performFailableGitOperation`
|
||||
// will return _something_ differentiable from `undefined` if this succeeds
|
||||
|
|
|
@ -17,7 +17,12 @@ import { Branch, BranchType, IAheadBehind } from '../../models/branch'
|
|||
import { BranchesTab } from '../../models/branches-tab'
|
||||
import { CloneRepositoryTab } from '../../models/clone-repository-tab'
|
||||
import { CloningRepository } from '../../models/cloning-repository'
|
||||
import { Commit, ICommitContext, CommitOneLine } from '../../models/commit'
|
||||
import {
|
||||
Commit,
|
||||
ICommitContext,
|
||||
CommitOneLine,
|
||||
shortenSHA,
|
||||
} from '../../models/commit'
|
||||
import {
|
||||
DiffSelection,
|
||||
DiffSelectionType,
|
||||
|
@ -175,6 +180,7 @@ import {
|
|||
updateRemoteHEAD,
|
||||
getBranchMergeBaseChangedFiles,
|
||||
getBranchMergeBaseDiff,
|
||||
checkoutCommit,
|
||||
} from '../git'
|
||||
import {
|
||||
installGlobalLFSFilters,
|
||||
|
@ -345,12 +351,14 @@ const confirmRepoRemovalDefault: boolean = true
|
|||
const confirmDiscardChangesDefault: boolean = true
|
||||
const confirmDiscardChangesPermanentlyDefault: boolean = true
|
||||
const confirmDiscardStashDefault: boolean = true
|
||||
const confirmCheckoutCommitDefault: boolean = true
|
||||
const askForConfirmationOnForcePushDefault = true
|
||||
const confirmUndoCommitDefault: boolean = true
|
||||
const askToMoveToApplicationsFolderKey: string = 'askToMoveToApplicationsFolder'
|
||||
const confirmRepoRemovalKey: string = 'confirmRepoRemoval'
|
||||
const confirmDiscardChangesKey: string = 'confirmDiscardChanges'
|
||||
const confirmDiscardStashKey: string = 'confirmDiscardStash'
|
||||
const confirmCheckoutCommitKey: string = 'confirmCheckoutCommit'
|
||||
const confirmDiscardChangesPermanentlyKey: string =
|
||||
'confirmDiscardChangesPermanentlyKey'
|
||||
const confirmForcePushKey: string = 'confirmForcePush'
|
||||
|
@ -462,6 +470,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
|
|||
private confirmDiscardChangesPermanently: boolean =
|
||||
confirmDiscardChangesPermanentlyDefault
|
||||
private confirmDiscardStash: boolean = confirmDiscardStashDefault
|
||||
private confirmCheckoutCommit: boolean = confirmCheckoutCommitDefault
|
||||
private askForConfirmationOnForcePush = askForConfirmationOnForcePushDefault
|
||||
private confirmUndoCommit: boolean = confirmUndoCommitDefault
|
||||
private imageDiffType: ImageDiffType = imageDiffTypeDefault
|
||||
|
@ -961,6 +970,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
|
|||
askForConfirmationOnDiscardChangesPermanently:
|
||||
this.confirmDiscardChangesPermanently,
|
||||
askForConfirmationOnDiscardStash: this.confirmDiscardStash,
|
||||
askForConfirmationOnCheckoutCommit: this.confirmCheckoutCommit,
|
||||
askForConfirmationOnForcePush: this.askForConfirmationOnForcePush,
|
||||
askForConfirmationOnUndoCommit: this.confirmUndoCommit,
|
||||
uncommittedChangesStrategy: this.uncommittedChangesStrategy,
|
||||
|
@ -1451,7 +1461,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
|
|||
aheadBehind,
|
||||
}
|
||||
|
||||
this.repositoryStateCache.updateCompareState(repository, s => ({
|
||||
this.repositoryStateCache.updateCompareState(repository, () => ({
|
||||
formState: newState,
|
||||
filterText: comparisonBranch.name,
|
||||
commitSHAs,
|
||||
|
@ -2041,6 +2051,11 @@ export class AppStore extends TypedBaseStore<IAppState> {
|
|||
confirmDiscardStashDefault
|
||||
)
|
||||
|
||||
this.confirmCheckoutCommit = getBoolean(
|
||||
confirmCheckoutCommitKey,
|
||||
confirmCheckoutCommitDefault
|
||||
)
|
||||
|
||||
this.askForConfirmationOnForcePush = getBoolean(
|
||||
confirmForcePushKey,
|
||||
askForConfirmationOnForcePushDefault
|
||||
|
@ -3724,7 +3739,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
|
|||
return this.checkoutImplementation(repository, branch, account, strategy)
|
||||
.then(() => this.onSuccessfulCheckout(repository, branch))
|
||||
.catch(e => this.emitError(new CheckoutError(e, repository, branch)))
|
||||
.then(() => this.refreshAfterCheckout(repository, branch))
|
||||
.then(() => this.refreshAfterCheckout(repository, branch.name))
|
||||
.finally(() => this.updateCheckoutProgress(repository, null))
|
||||
})
|
||||
}
|
||||
|
@ -3842,18 +3857,69 @@ export class AppStore extends TypedBaseStore<IAppState> {
|
|||
this.hasUserViewedStash = false
|
||||
}
|
||||
|
||||
private async refreshAfterCheckout(repository: Repository, branch: Branch) {
|
||||
/**
|
||||
* @param commitish A branch name or a commit hash
|
||||
*/
|
||||
private async refreshAfterCheckout(
|
||||
repository: Repository,
|
||||
commitish: string
|
||||
) {
|
||||
this.updateCheckoutProgress(repository, {
|
||||
kind: 'checkout',
|
||||
title: `Refreshing ${__DARWIN__ ? 'Repository' : 'repository'}`,
|
||||
description: 'Checking out',
|
||||
value: 1,
|
||||
targetBranch: branch.name,
|
||||
target: commitish,
|
||||
})
|
||||
|
||||
await this._refreshRepository(repository)
|
||||
return repository
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout the given commit, ignoring any local changes.
|
||||
*
|
||||
* Note: This shouldn't be called directly. See `Dispatcher`.
|
||||
*/
|
||||
public async _checkoutCommit(
|
||||
repository: Repository,
|
||||
commit: CommitOneLine
|
||||
): Promise<Repository> {
|
||||
const repositoryState = this.repositoryStateCache.get(repository)
|
||||
const { branchesState } = repositoryState
|
||||
const { tip } = branchesState
|
||||
|
||||
// No point in checking out the currently checked out commit.
|
||||
if (
|
||||
(tip.kind === TipState.Valid && tip.branch.tip.sha === commit.sha) ||
|
||||
(tip.kind === TipState.Detached && tip.currentSha === commit.sha)
|
||||
) {
|
||||
return repository
|
||||
}
|
||||
|
||||
return this.withAuthenticatingUser(repository, (repository, account) => {
|
||||
// We always want to end with refreshing the repository regardless of
|
||||
// whether the checkout succeeded or not in order to present the most
|
||||
// up-to-date information to the user.
|
||||
return this.checkoutCommitDefaultBehaviour(repository, commit, account)
|
||||
.catch(e => this.emitError(new Error(e)))
|
||||
.then(() =>
|
||||
this.refreshAfterCheckout(repository, shortenSHA(commit.sha))
|
||||
)
|
||||
.finally(() => this.updateCheckoutProgress(repository, null))
|
||||
})
|
||||
}
|
||||
|
||||
private async checkoutCommitDefaultBehaviour(
|
||||
repository: Repository,
|
||||
commit: CommitOneLine,
|
||||
account: IGitAccount | null
|
||||
) {
|
||||
await checkoutCommit(repository, account, commit, progress => {
|
||||
this.updateCheckoutProgress(repository, progress)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stash associated to the current checked out branch.
|
||||
*
|
||||
|
@ -5329,6 +5395,15 @@ export class AppStore extends TypedBaseStore<IAppState> {
|
|||
return Promise.resolve()
|
||||
}
|
||||
|
||||
public _setConfirmCheckoutCommitSetting(value: boolean): Promise<void> {
|
||||
this.confirmCheckoutCommit = value
|
||||
|
||||
setBoolean(confirmCheckoutCommitKey, value)
|
||||
this.emitUpdate()
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
public _setConfirmForcePushSetting(value: boolean): Promise<void> {
|
||||
this.askForConfirmationOnForcePush = value
|
||||
setBoolean(confirmForcePushKey, value)
|
||||
|
|
|
@ -60,6 +60,7 @@ export enum PopupType {
|
|||
StashAndSwitchBranch = 'StashAndSwitchBranch',
|
||||
ConfirmOverwriteStash = 'ConfirmOverwriteStash',
|
||||
ConfirmDiscardStash = 'ConfirmDiscardStash',
|
||||
ConfirmCheckoutCommit = 'ConfirmCheckoutCommit',
|
||||
CreateTutorialRepository = 'CreateTutorialRepository',
|
||||
ConfirmExitTutorial = 'ConfirmExitTutorial',
|
||||
PushRejectedDueToMissingWorkflowScope = 'PushRejectedDueToMissingWorkflowScope',
|
||||
|
@ -234,6 +235,11 @@ export type PopupDetail =
|
|||
repository: Repository
|
||||
stash: IStashEntry
|
||||
}
|
||||
| {
|
||||
type: PopupType.ConfirmCheckoutCommit
|
||||
repository: Repository
|
||||
commit: CommitOneLine
|
||||
}
|
||||
| {
|
||||
type: PopupType.CreateTutorialRepository
|
||||
account: Account
|
||||
|
|
|
@ -42,8 +42,13 @@ export interface IGenericProgress extends IProgress {
|
|||
export interface ICheckoutProgress extends IProgress {
|
||||
kind: 'checkout'
|
||||
|
||||
/** The branch that's currently being checked out */
|
||||
readonly targetBranch: string
|
||||
/** The branch or commit that's currently being checked out */
|
||||
readonly target: string
|
||||
|
||||
/**
|
||||
* Infotext for the user.
|
||||
*/
|
||||
readonly description: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -100,6 +100,7 @@ import { Banner, BannerType } from '../models/banner'
|
|||
import { StashAndSwitchBranch } from './stash-changes/stash-and-switch-branch-dialog'
|
||||
import { OverwriteStash } from './stash-changes/overwrite-stashed-changes-dialog'
|
||||
import { ConfirmDiscardStashDialog } from './stashing/confirm-discard-stash'
|
||||
import { ConfirmCheckoutCommitDialog } from './checkout/confirm-checkout-commit'
|
||||
import { CreateTutorialRepositoryDialog } from './no-repositories/create-tutorial-repository-dialog'
|
||||
import { ConfirmExitTutorial } from './tutorial'
|
||||
import { TutorialStep, isValidTutorialStep } from '../models/tutorial-step'
|
||||
|
@ -1606,6 +1607,9 @@ export class App extends React.Component<IAppProps, IAppState> {
|
|||
this.state.askForConfirmationOnDiscardChangesPermanently
|
||||
}
|
||||
confirmDiscardStash={this.state.askForConfirmationOnDiscardStash}
|
||||
confirmCheckoutCommit={
|
||||
this.state.askForConfirmationOnCheckoutCommit
|
||||
}
|
||||
confirmForcePush={this.state.askForConfirmationOnForcePush}
|
||||
confirmUndoCommit={this.state.askForConfirmationOnUndoCommit}
|
||||
uncommittedChangesStrategy={this.state.uncommittedChangesStrategy}
|
||||
|
@ -1985,6 +1989,22 @@ export class App extends React.Component<IAppProps, IAppState> {
|
|||
/>
|
||||
)
|
||||
}
|
||||
case PopupType.ConfirmCheckoutCommit: {
|
||||
const { repository, commit } = popup
|
||||
|
||||
return (
|
||||
<ConfirmCheckoutCommitDialog
|
||||
key="confirm-checkout-commit-dialog"
|
||||
dispatcher={this.props.dispatcher}
|
||||
askForConfirmationOnCheckoutCommit={
|
||||
this.state.askForConfirmationOnDiscardStash
|
||||
}
|
||||
repository={repository}
|
||||
commit={commit}
|
||||
onDismissed={onPopupDismissedFn}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case PopupType.CreateTutorialRepository: {
|
||||
return (
|
||||
<CreateTutorialRepositoryDialog
|
||||
|
@ -3215,6 +3235,9 @@ export class App extends React.Component<IAppProps, IAppState> {
|
|||
askForConfirmationOnDiscardStash={
|
||||
state.askForConfirmationOnDiscardStash
|
||||
}
|
||||
askForConfirmationOnCheckoutCommit={
|
||||
state.askForConfirmationOnCheckoutCommit
|
||||
}
|
||||
accounts={state.accounts}
|
||||
externalEditorLabel={externalEditorLabel}
|
||||
resolvedExternalEditor={state.resolvedExternalEditor}
|
||||
|
|
106
app/src/ui/checkout/confirm-checkout-commit.tsx
Normal file
106
app/src/ui/checkout/confirm-checkout-commit.tsx
Normal file
|
@ -0,0 +1,106 @@
|
|||
import * as React from 'react'
|
||||
import { Dialog, DialogContent, DialogFooter } from '../dialog'
|
||||
import { Repository } from '../../models/repository'
|
||||
import { Dispatcher } from '../dispatcher'
|
||||
import { Row } from '../lib/row'
|
||||
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'
|
||||
import { Checkbox, CheckboxValue } from '../lib/checkbox'
|
||||
import { CommitOneLine } from '../../models/commit'
|
||||
|
||||
interface IConfirmCheckoutCommitProps {
|
||||
readonly dispatcher: Dispatcher
|
||||
readonly repository: Repository
|
||||
readonly commit: CommitOneLine
|
||||
readonly askForConfirmationOnCheckoutCommit: boolean
|
||||
readonly onDismissed: () => void
|
||||
}
|
||||
|
||||
interface IConfirmCheckoutCommitState {
|
||||
readonly isCheckingOut: boolean
|
||||
readonly confirmCheckoutCommit: boolean
|
||||
}
|
||||
/**
|
||||
* Dialog to confirm checking out a commit
|
||||
*/
|
||||
export class ConfirmCheckoutCommitDialog extends React.Component<
|
||||
IConfirmCheckoutCommitProps,
|
||||
IConfirmCheckoutCommitState
|
||||
> {
|
||||
public constructor(props: IConfirmCheckoutCommitProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isCheckingOut: false,
|
||||
confirmCheckoutCommit: props.askForConfirmationOnCheckoutCommit,
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const title = __DARWIN__ ? 'Checkout Commit?' : 'Checkout commit?'
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
id="checkout-commit"
|
||||
type="warning"
|
||||
title={title}
|
||||
loading={this.state.isCheckingOut}
|
||||
disabled={this.state.isCheckingOut}
|
||||
onSubmit={this.onSubmit}
|
||||
onDismissed={this.props.onDismissed}
|
||||
ariaDescribedBy="checking-out-commit-confirmation"
|
||||
role="alertdialog"
|
||||
>
|
||||
<DialogContent>
|
||||
<Row id="checking-out-commit-confirmation">
|
||||
Checking out a commit will create a detached HEAD, and you will no
|
||||
longer be on any branch. Are you sure you want to checkout this
|
||||
commit?
|
||||
</Row>
|
||||
<Row>
|
||||
<Checkbox
|
||||
label="Do not show this message again"
|
||||
value={
|
||||
this.state.confirmCheckoutCommit
|
||||
? CheckboxValue.Off
|
||||
: CheckboxValue.On
|
||||
}
|
||||
onChange={this.onaskForConfirmationOnCheckoutCommitChanged}
|
||||
/>
|
||||
</Row>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<OkCancelButtonGroup destructive={true} okButtonText="Checkout" />
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
private onaskForConfirmationOnCheckoutCommitChanged = (
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) => {
|
||||
const value = !event.currentTarget.checked
|
||||
|
||||
this.setState({ confirmCheckoutCommit: value })
|
||||
}
|
||||
|
||||
private onSubmit = async () => {
|
||||
const { dispatcher, repository, commit, onDismissed } = this.props
|
||||
|
||||
this.setState({
|
||||
isCheckingOut: true,
|
||||
})
|
||||
|
||||
try {
|
||||
dispatcher.setConfirmCheckoutCommitSetting(
|
||||
this.state.confirmCheckoutCommit
|
||||
)
|
||||
await dispatcher.checkoutCommit(repository, commit)
|
||||
} finally {
|
||||
this.setState({
|
||||
isCheckingOut: false,
|
||||
})
|
||||
}
|
||||
|
||||
onDismissed()
|
||||
}
|
||||
}
|
|
@ -694,6 +694,14 @@ export class Dispatcher {
|
|||
return this.appStore._checkoutBranch(repository, branch, strategy)
|
||||
}
|
||||
|
||||
/** Check out the given commit. */
|
||||
public checkoutCommit(
|
||||
repository: Repository,
|
||||
commit: CommitOneLine
|
||||
): Promise<Repository> {
|
||||
return this.appStore._checkoutCommit(repository, commit)
|
||||
}
|
||||
|
||||
/** Push the current branch. */
|
||||
public push(repository: Repository): Promise<void> {
|
||||
return this.appStore._push(repository)
|
||||
|
@ -2374,6 +2382,10 @@ export class Dispatcher {
|
|||
return this.appStore._setConfirmDiscardStashSetting(value)
|
||||
}
|
||||
|
||||
public setConfirmCheckoutCommitSetting(value: boolean) {
|
||||
return this.appStore._setConfirmCheckoutCommitSetting(value)
|
||||
}
|
||||
|
||||
public setConfirmForcePushSetting(value: boolean) {
|
||||
return this.appStore._setConfirmForcePushSetting(value)
|
||||
}
|
||||
|
|
|
@ -181,6 +181,7 @@ export class CommitDragElement extends React.Component<
|
|||
canBeUndone={false}
|
||||
canBeAmended={false}
|
||||
canBeResetTo={false}
|
||||
canBeCheckedOut={false}
|
||||
isLocal={false}
|
||||
showUnpushedIndicator={false}
|
||||
/>
|
||||
|
|
|
@ -14,7 +14,10 @@ import { IMenuItem } from '../../lib/menu-item'
|
|||
import { Octicon } from '../octicons'
|
||||
import * as OcticonSymbol from '../octicons/octicons.generated'
|
||||
import { Draggable } from '../lib/draggable'
|
||||
import { enableResetToCommit } from '../../lib/feature-flag'
|
||||
import {
|
||||
enableCheckoutCommit,
|
||||
enableResetToCommit,
|
||||
} from '../../lib/feature-flag'
|
||||
import { dragAndDropManager } from '../../lib/drag-and-drop-manager'
|
||||
import {
|
||||
DragType,
|
||||
|
@ -32,11 +35,13 @@ interface ICommitProps {
|
|||
readonly canBeUndone: boolean
|
||||
readonly canBeAmended: boolean
|
||||
readonly canBeResetTo: boolean
|
||||
readonly canBeCheckedOut: boolean
|
||||
readonly onResetToCommit?: (commit: Commit) => void
|
||||
readonly onUndoCommit?: (commit: Commit) => void
|
||||
readonly onRevertCommit?: (commit: Commit) => void
|
||||
readonly onViewCommitOnGitHub?: (sha: string) => void
|
||||
readonly onCreateBranch?: (commit: CommitOneLine) => void
|
||||
readonly onCheckoutCommit?: (commit: CommitOneLine) => void
|
||||
readonly onCreateTag?: (targetCommitSha: string) => void
|
||||
readonly onDeleteTag?: (tagName: string) => void
|
||||
readonly onAmendCommit?: (commit: Commit, isLocalCommit: boolean) => void
|
||||
|
@ -305,6 +310,18 @@ export class CommitListItem extends React.PureComponent<
|
|||
})
|
||||
}
|
||||
|
||||
if (enableCheckoutCommit()) {
|
||||
items.push({
|
||||
label: __DARWIN__ ? 'Checkout Commit' : 'Checkout commit',
|
||||
action: () => {
|
||||
this.props.onCheckoutCommit?.(this.props.commit)
|
||||
},
|
||||
enabled:
|
||||
this.props.canBeCheckedOut &&
|
||||
this.props.onCheckoutCommit !== undefined,
|
||||
})
|
||||
}
|
||||
|
||||
items.push(
|
||||
{
|
||||
label: __DARWIN__
|
||||
|
|
|
@ -71,6 +71,12 @@ interface ICommitListProps {
|
|||
*/
|
||||
readonly onCreateBranch?: (commit: CommitOneLine) => void
|
||||
|
||||
/**
|
||||
* Callback to fire to checkout the selected commit in the current
|
||||
* repository
|
||||
*/
|
||||
readonly onCheckoutCommit?: (commit: CommitOneLine) => void
|
||||
|
||||
/** Callback to fire to open the dialog to create a new tag on the given commit */
|
||||
readonly onCreateTag?: (targetCommitSha: string) => void
|
||||
|
||||
|
@ -202,6 +208,7 @@ export class CommitList extends React.Component<ICommitListProps, {}> {
|
|||
canBeResetTo={
|
||||
this.props.canResetToCommits === true && isResettableCommit
|
||||
}
|
||||
canBeCheckedOut={row > 0} //Cannot checkout the current commit
|
||||
showUnpushedIndicator={showUnpushedIndicator}
|
||||
unpushedIndicatorTitle={this.getUnpushedIndicatorTitle(
|
||||
isLocal,
|
||||
|
@ -211,6 +218,7 @@ export class CommitList extends React.Component<ICommitListProps, {}> {
|
|||
commit={commit}
|
||||
emoji={this.props.emoji}
|
||||
onCreateBranch={this.props.onCreateBranch}
|
||||
onCheckoutCommit={this.props.onCheckoutCommit}
|
||||
onCreateTag={this.props.onCreateTag}
|
||||
onDeleteTag={this.props.onDeleteTag}
|
||||
onCherryPick={this.props.onCherryPick}
|
||||
|
|
|
@ -38,6 +38,7 @@ interface ICompareSidebarProps {
|
|||
readonly emoji: Map<string, string>
|
||||
readonly commitLookup: Map<string, Commit>
|
||||
readonly localCommitSHAs: ReadonlyArray<string>
|
||||
readonly askForConfirmationOnCheckoutCommit: boolean
|
||||
readonly dispatcher: Dispatcher
|
||||
readonly currentBranch: Branch | null
|
||||
readonly selectedCommitShas: ReadonlyArray<string>
|
||||
|
@ -255,6 +256,7 @@ export class CompareSidebar extends React.Component<
|
|||
onCommitsSelected={this.onCommitsSelected}
|
||||
onScroll={this.onScroll}
|
||||
onCreateBranch={this.onCreateBranch}
|
||||
onCheckoutCommit={this.onCheckoutCommit}
|
||||
onCreateTag={this.onCreateTag}
|
||||
onDeleteTag={this.onDeleteTag}
|
||||
onCherryPick={this.onCherryPick}
|
||||
|
@ -599,6 +601,20 @@ export class CompareSidebar extends React.Component<
|
|||
})
|
||||
}
|
||||
|
||||
private onCheckoutCommit = (commit: CommitOneLine) => {
|
||||
const { repository, dispatcher, askForConfirmationOnCheckoutCommit } =
|
||||
this.props
|
||||
if (!askForConfirmationOnCheckoutCommit) {
|
||||
dispatcher.checkoutCommit(repository, commit)
|
||||
} else {
|
||||
dispatcher.showPopup({
|
||||
type: PopupType.ConfirmCheckoutCommit,
|
||||
commit: commit,
|
||||
repository,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private onDeleteTag = (tagName: string) => {
|
||||
this.props.dispatcher.showDeleteTagDialog(this.props.repository, tagName)
|
||||
}
|
||||
|
|
|
@ -256,6 +256,7 @@ export class ConfigureGitUser extends React.Component<
|
|||
canBeUndone={false}
|
||||
canBeAmended={false}
|
||||
canBeResetTo={false}
|
||||
canBeCheckedOut={false}
|
||||
isLocal={false}
|
||||
showUnpushedIndicator={false}
|
||||
selectedCommits={[dummyCommit]}
|
||||
|
|
|
@ -56,6 +56,7 @@ interface IPreferencesProps {
|
|||
readonly confirmDiscardChanges: boolean
|
||||
readonly confirmDiscardChangesPermanently: boolean
|
||||
readonly confirmDiscardStash: boolean
|
||||
readonly confirmCheckoutCommit: boolean
|
||||
readonly confirmForcePush: boolean
|
||||
readonly confirmUndoCommit: boolean
|
||||
readonly uncommittedChangesStrategy: UncommittedChangesStrategy
|
||||
|
@ -81,6 +82,7 @@ interface IPreferencesState {
|
|||
readonly confirmDiscardChanges: boolean
|
||||
readonly confirmDiscardChangesPermanently: boolean
|
||||
readonly confirmDiscardStash: boolean
|
||||
readonly confirmCheckoutCommit: boolean
|
||||
readonly confirmForcePush: boolean
|
||||
readonly confirmUndoCommit: boolean
|
||||
readonly uncommittedChangesStrategy: UncommittedChangesStrategy
|
||||
|
@ -128,6 +130,7 @@ export class Preferences extends React.Component<
|
|||
confirmDiscardChanges: false,
|
||||
confirmDiscardChangesPermanently: false,
|
||||
confirmDiscardStash: false,
|
||||
confirmCheckoutCommit: false,
|
||||
confirmForcePush: false,
|
||||
confirmUndoCommit: false,
|
||||
uncommittedChangesStrategy: defaultUncommittedChangesStrategy,
|
||||
|
@ -188,6 +191,7 @@ export class Preferences extends React.Component<
|
|||
confirmDiscardChangesPermanently:
|
||||
this.props.confirmDiscardChangesPermanently,
|
||||
confirmDiscardStash: this.props.confirmDiscardStash,
|
||||
confirmCheckoutCommit: this.props.confirmCheckoutCommit,
|
||||
confirmForcePush: this.props.confirmForcePush,
|
||||
confirmUndoCommit: this.props.confirmUndoCommit,
|
||||
uncommittedChangesStrategy: this.props.uncommittedChangesStrategy,
|
||||
|
@ -209,7 +213,7 @@ export class Preferences extends React.Component<
|
|||
return (
|
||||
<Dialog
|
||||
id="preferences"
|
||||
title={__DARWIN__ ? 'Preferences' : 'Options'}
|
||||
title={__DARWIN__ ? 'Settings' : 'Options'}
|
||||
onDismissed={this.onCancel}
|
||||
onSubmit={this.onSave}
|
||||
>
|
||||
|
@ -364,6 +368,7 @@ export class Preferences extends React.Component<
|
|||
this.state.confirmDiscardChangesPermanently
|
||||
}
|
||||
confirmDiscardStash={this.state.confirmDiscardStash}
|
||||
confirmCheckoutCommit={this.state.confirmCheckoutCommit}
|
||||
confirmForcePush={this.state.confirmForcePush}
|
||||
confirmUndoCommit={this.state.confirmUndoCommit}
|
||||
onConfirmRepositoryRemovalChanged={
|
||||
|
@ -371,6 +376,7 @@ export class Preferences extends React.Component<
|
|||
}
|
||||
onConfirmDiscardChangesChanged={this.onConfirmDiscardChangesChanged}
|
||||
onConfirmDiscardStashChanged={this.onConfirmDiscardStashChanged}
|
||||
onConfirmCheckoutCommitChanged={this.onConfirmCheckoutCommitChanged}
|
||||
onConfirmForcePushChanged={this.onConfirmForcePushChanged}
|
||||
onConfirmDiscardChangesPermanentlyChanged={
|
||||
this.onConfirmDiscardChangesPermanentlyChanged
|
||||
|
@ -444,6 +450,10 @@ export class Preferences extends React.Component<
|
|||
this.setState({ confirmDiscardStash: value })
|
||||
}
|
||||
|
||||
private onConfirmCheckoutCommitChanged = (value: boolean) => {
|
||||
this.setState({ confirmCheckoutCommit: value })
|
||||
}
|
||||
|
||||
private onConfirmDiscardChangesPermanentlyChanged = (value: boolean) => {
|
||||
this.setState({ confirmDiscardChangesPermanently: value })
|
||||
}
|
||||
|
@ -583,6 +593,10 @@ export class Preferences extends React.Component<
|
|||
this.state.confirmDiscardStash
|
||||
)
|
||||
|
||||
await this.props.dispatcher.setConfirmCheckoutCommitSetting(
|
||||
this.state.confirmCheckoutCommit
|
||||
)
|
||||
|
||||
await this.props.dispatcher.setConfirmUndoCommitSetting(
|
||||
this.state.confirmUndoCommit
|
||||
)
|
||||
|
|
|
@ -9,12 +9,14 @@ interface IPromptsPreferencesProps {
|
|||
readonly confirmDiscardChanges: boolean
|
||||
readonly confirmDiscardChangesPermanently: boolean
|
||||
readonly confirmDiscardStash: boolean
|
||||
readonly confirmCheckoutCommit: boolean
|
||||
readonly confirmForcePush: boolean
|
||||
readonly confirmUndoCommit: boolean
|
||||
readonly uncommittedChangesStrategy: UncommittedChangesStrategy
|
||||
readonly onConfirmDiscardChangesChanged: (checked: boolean) => void
|
||||
readonly onConfirmDiscardChangesPermanentlyChanged: (checked: boolean) => void
|
||||
readonly onConfirmDiscardStashChanged: (checked: boolean) => void
|
||||
readonly onConfirmCheckoutCommitChanged: (checked: boolean) => void
|
||||
readonly onConfirmRepositoryRemovalChanged: (checked: boolean) => void
|
||||
readonly onConfirmForcePushChanged: (checked: boolean) => void
|
||||
readonly onConfirmUndoCommitChanged: (checked: boolean) => void
|
||||
|
@ -28,6 +30,7 @@ interface IPromptsPreferencesState {
|
|||
readonly confirmDiscardChanges: boolean
|
||||
readonly confirmDiscardChangesPermanently: boolean
|
||||
readonly confirmDiscardStash: boolean
|
||||
readonly confirmCheckoutCommit: boolean
|
||||
readonly confirmForcePush: boolean
|
||||
readonly confirmUndoCommit: boolean
|
||||
readonly uncommittedChangesStrategy: UncommittedChangesStrategy
|
||||
|
@ -46,6 +49,7 @@ export class Prompts extends React.Component<
|
|||
confirmDiscardChangesPermanently:
|
||||
this.props.confirmDiscardChangesPermanently,
|
||||
confirmDiscardStash: this.props.confirmDiscardStash,
|
||||
confirmCheckoutCommit: this.props.confirmCheckoutCommit,
|
||||
confirmForcePush: this.props.confirmForcePush,
|
||||
confirmUndoCommit: this.props.confirmUndoCommit,
|
||||
uncommittedChangesStrategy: this.props.uncommittedChangesStrategy,
|
||||
|
@ -79,6 +83,15 @@ export class Prompts extends React.Component<
|
|||
this.props.onConfirmDiscardStashChanged(value)
|
||||
}
|
||||
|
||||
private onConfirmCheckoutCommitChanged = (
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) => {
|
||||
const value = event.currentTarget.checked
|
||||
|
||||
this.setState({ confirmCheckoutCommit: value })
|
||||
this.props.onConfirmCheckoutCommitChanged(value)
|
||||
}
|
||||
|
||||
private onConfirmForcePushChanged = (
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) => {
|
||||
|
@ -154,6 +167,15 @@ export class Prompts extends React.Component<
|
|||
}
|
||||
onChange={this.onConfirmDiscardStashChanged}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Checking out a commit"
|
||||
value={
|
||||
this.state.confirmCheckoutCommit
|
||||
? CheckboxValue.On
|
||||
: CheckboxValue.Off
|
||||
}
|
||||
onChange={this.onConfirmCheckoutCommitChanged}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Force pushing"
|
||||
value={
|
||||
|
|
|
@ -51,6 +51,7 @@ interface IRepositoryViewProps {
|
|||
readonly showSideBySideDiff: boolean
|
||||
readonly askForConfirmationOnDiscardChanges: boolean
|
||||
readonly askForConfirmationOnDiscardStash: boolean
|
||||
readonly askForConfirmationOnCheckoutCommit: boolean
|
||||
readonly focusCommitMessage: boolean
|
||||
readonly commitSpellcheckEnabled: boolean
|
||||
readonly accounts: ReadonlyArray<Account>
|
||||
|
@ -299,6 +300,9 @@ export class RepositoryView extends React.Component<
|
|||
tagsToPush={tagsToPush}
|
||||
aheadBehindStore={aheadBehindStore}
|
||||
isMultiCommitOperationInProgress={mcos !== null}
|
||||
askForConfirmationOnCheckoutCommit={
|
||||
this.props.askForConfirmationOnCheckoutCommit
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -157,15 +157,15 @@ export class BranchDropdown extends React.Component<IBranchDropdownProps> {
|
|||
let progressValue: number | undefined = undefined
|
||||
|
||||
if (checkoutProgress) {
|
||||
title = checkoutProgress.targetBranch
|
||||
description = __DARWIN__ ? 'Switching to Branch' : 'Switching to branch'
|
||||
title = checkoutProgress.target
|
||||
description = checkoutProgress.description
|
||||
|
||||
if (checkoutProgress.value > 0) {
|
||||
const friendlyProgress = Math.round(checkoutProgress.value * 100)
|
||||
description = `${description} (${friendlyProgress}%)`
|
||||
}
|
||||
|
||||
tooltip = `Switching to ${checkoutProgress.targetBranch}`
|
||||
tooltip = `Checking out ${checkoutProgress.target}`
|
||||
progressValue = checkoutProgress.value
|
||||
icon = syncClockwise
|
||||
iconClassName = 'spin'
|
||||
|
|
Loading…
Reference in a new issue