Merge branch 'development' into reuse-workflow

This commit is contained in:
Markus Olsson 2023-07-03 09:33:39 +02:00
commit f8369105ec
18 changed files with 441 additions and 59 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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
}
/**

View file

@ -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}

View 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()
}
}

View file

@ -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)
}

View file

@ -181,6 +181,7 @@ export class CommitDragElement extends React.Component<
canBeUndone={false}
canBeAmended={false}
canBeResetTo={false}
canBeCheckedOut={false}
isLocal={false}
showUnpushedIndicator={false}
/>

View file

@ -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__

View file

@ -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}

View file

@ -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)
}

View file

@ -256,6 +256,7 @@ export class ConfigureGitUser extends React.Component<
canBeUndone={false}
canBeAmended={false}
canBeResetTo={false}
canBeCheckedOut={false}
isLocal={false}
showUnpushedIndicator={false}
selectedCommits={[dummyCommit]}

View file

@ -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
)

View file

@ -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={

View file

@ -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
}
/>
)
}

View file

@ -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'