Merge branch 'development' into high-contrast

This commit is contained in:
tidy-dev 2021-08-05 16:55:57 -04:00
commit 027efae770
106 changed files with 2663 additions and 2234 deletions

View file

@ -1,6 +1,6 @@
---
name: "⭐ Submit a feature request"
about: 'Feature requests are considered based on team''s capacity '
about: 'Feature requests are considered based on the team''s capacity. '
title: ''
labels: ''
assignees: ''
@ -9,12 +9,12 @@ assignees: ''
### Describe the feature or problem youd like to solve
A clear and concise description of what the feature or problem is. If this is a bug report, please use the bug report template instead.
Write a clear and concise description of what the feature or problem is. If this is a bug report, please use the bug report template instead.
### Proposed solution
How will it benefit Desktop and its users?
Please share how this will it benefit Desktop and its users?
### Additional context
Add any other context like screenshots or mockups are helpful, if applicable.
Please include any other context, like screenshots or mockups, if applicable.

View file

@ -1,6 +1,6 @@
---
name: "\U0001F41B Bug report"
about: Report a bug while using GitHub Desktop (full template required)
about: Report a bug while using GitHub Desktop. The full template is required.
title: ''
labels: ''
assignees: ''
@ -8,12 +8,11 @@ assignees: ''
---
### Describe the bug
A clear and concise description of what the bug is.
Write a clear and concise description of what the bug is.
### Version & OS
Open 'About GitHub Desktop' menu to see the Desktop version. Also include what operating system you are using.
Open the 'About GitHub Desktop' menu to see the GitHub Desktop version. Also include what operating system you are using.
### Steps to reproduce the behavior
@ -24,11 +23,11 @@ Open 'About GitHub Desktop' menu to see the Desktop version. Also include what o
### Expected behavior
A clear and concise description of what you expected to happen.
Write a clear and concise description of what you expected to happen.
### Actual behavior
A clear and concise description of what actually happened.
Write a clear and concise description of what actually happened.
### Screenshots

View file

@ -101,3 +101,7 @@ jobs:
S3_KEY: ${{ secrets.S3_KEY }}
S3_SECRET: ${{ secrets.S3_SECRET }}
S3_BUCKET: github-desktop
AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }}
AZURE_STORAGE_ACCESS_KEY: ${{ secrets.AZURE_STORAGE_ACCESS_KEY }}
AZURE_BLOB_CONTAINER: ${{ secrets.AZURE_BLOB_CONTAINER }}
AZURE_STORAGE_URL: ${{ secrets.AZURE_STORAGE_URL }}

View file

@ -8,7 +8,8 @@ import {
getWindowState,
windowStateChannelName,
} from '../lib/window-state'
import { Octicon, OcticonSymbol } from '../ui/octicons'
import { Octicon } from '../ui/octicons'
import * as OcticonSymbol from '../ui/octicons/octicons.generated'
import { Button } from '../ui/lib/button'
import { LinkButton } from '../ui/lib/link-button'
import { getVersion } from '../ui/lib/app-proxy'

View file

@ -13,6 +13,7 @@ import { AuthenticationMode } from './2fa'
import { uuid } from './uuid'
import username from 'username'
import { GitProtocol } from './remote-parsing'
import { Emitter } from 'event-kit'
const envEndpoint = process.env['DESKTOP_GITHUB_DOTCOM_API_ENDPOINT']
const envHTMLURL = process.env['DESKTOP_GITHUB_DOTCOM_HTML_URL']
@ -576,6 +577,18 @@ function toGitHubIsoDateString(date: Date) {
* An object for making authenticated requests to the GitHub API
*/
export class API {
private static readonly TOKEN_INVALIDATED_EVENT = 'token-invalidated'
private static readonly emitter = new Emitter()
public static onTokenInvalidated(callback: (endpoint: string) => void) {
API.emitter.on(API.TOKEN_INVALIDATED_EVENT, callback)
}
private static emitTokenInvalidated(endpoint: string) {
API.emitter.emit(API.TOKEN_INVALIDATED_EVENT, endpoint)
}
/** Create a new API client from the given account. */
public static fromAccount(account: Account): API {
return new API(account.endpoint, account.token)
@ -633,10 +646,11 @@ export class API {
name: string,
protocol: GitProtocol | undefined
): Promise<IAPIRepositoryCloneInfo | null> {
const response = await this.requestReloadCache(
'GET',
`repos/${owner}/${name}`
)
const response = await this.request('GET', `repos/${owner}/${name}`, {
// Make sure we don't run into cache issues when fetching the repositories,
// specially after repositories have been renamed.
reloadCache: true,
})
if (response.status === HttpStatusCode.NotFound) {
return null
@ -715,9 +729,11 @@ export class API {
try {
const apiPath = org ? `orgs/${org.login}/repos` : 'user/repos'
const response = await this.request('POST', apiPath, {
name,
description,
private: private_,
body: {
name,
description,
private: private_,
},
})
return await parsedResponse<IAPIFullRepository>(response)
@ -918,7 +934,7 @@ export class API {
Accept: 'application/vnd.github.antiope-preview+json',
}
const response = await this.request('GET', path, undefined, headers)
const response = await this.request('GET', path, { customHeaders: headers })
try {
return await parsedResponse<IAPIRefCheckRuns>(response)
@ -950,7 +966,9 @@ export class API {
}
try {
const response = await this.request('GET', path, undefined, headers)
const response = await this.request('GET', path, {
customHeaders: headers,
})
return await parsedResponse<IAPIPushControl>(response)
} catch (err) {
log.info(
@ -1021,32 +1039,30 @@ export class API {
}
/** Make an authenticated request to the client's endpoint with its token. */
private request(
private async request(
method: HTTPMethod,
path: string,
body?: Object,
customHeaders?: Object
options: {
body?: Object
customHeaders?: Object
reloadCache?: boolean
} = {}
): Promise<Response> {
return request(this.endpoint, this.token, method, path, body, customHeaders)
}
/** Make an authenticated request to the client's endpoint with its token but
* skip checking cache while also updating cache. */
private requestReloadCache(
method: HTTPMethod,
path: string,
body?: Object,
customHeaders?: Object
): Promise<Response> {
return request(
const response = await request(
this.endpoint,
this.token,
method,
path,
body,
customHeaders,
true
options.body,
options.customHeaders,
options.reloadCache
)
if (response.status === 401) {
API.emitTokenInvalidated(this.endpoint)
}
return response
}
/**
@ -1089,7 +1105,9 @@ export class API {
try {
const path = `repos/${owner}/${name}/mentionables/users`
const response = await this.request('GET', path, undefined, headers)
const response = await this.request('GET', path, {
customHeaders: headers,
})
if (response.status === HttpStatusCode.NotFound) {
log.warn(`fetchMentionables: '${path}' returned a 404`)

View file

@ -50,6 +50,7 @@ import {
MultiCommitOperationStep,
} from '../models/multi-commit-operation'
import { DragAndDropIntroType } from '../ui/history/drag-and-drop-intro'
import { IChangesetData } from './git'
export enum SelectionType {
Repository,
@ -580,8 +581,8 @@ export interface ICommitSelection {
/** The commits currently selected in the app */
readonly shas: ReadonlyArray<string>
/** The list of files associated with the current commit */
readonly changedFiles: ReadonlyArray<CommittedFileChange>
/** The changeset data associated with the selected commit */
readonly changesetData: IChangesetData
/** The selected file inside the selected commit */
readonly file: CommittedFileChange | null

View file

@ -123,6 +123,11 @@ export function enableResetToCommit(): boolean {
return enableDevelopmentFeatures()
}
/** Should we show line changes (added/deleted) in commits? */
export function enableLineChangesInCommit(): boolean {
return enableBetaFeatures()
}
/** Should we allow high contrast theme option */
export function enableHighContrastTheme(): boolean {
return enableDevelopmentFeatures()

View file

@ -302,7 +302,8 @@ function getDescriptionForError(error: DugiteError): string | null {
- You may need to log out and log back in to refresh your token.
- You do not have permission to access this repository.
- The repository is archived on GitHub. Check the repository settings to confirm you are still permitted to push commits.
- If you use SSH authentication, check that your key is added to the ssh-agent and associated with your account.`
- If you use SSH authentication, check that your key is added to the ssh-agent and associated with your account.
- If you used username / password authentication, you might need to use a Personal Access Token instead of your account password. Check the documentation of your repository hosting service.`
}
switch (error) {

View file

@ -16,6 +16,7 @@ import {
import { getCaptures } from '../helpers/regex'
import { createLogParser } from './git-delimiter-parser'
import { revRange } from '.'
import { enableLineChangesInCommit } from '../feature-flag'
/**
* Map the raw status text from Git to an app-friendly value
@ -66,6 +67,7 @@ export async function getCommits(
repository: Repository,
revisionRange?: string,
limit?: number,
skip?: number,
additionalArgs: ReadonlyArray<string> = []
): Promise<ReadonlyArray<Commit>> {
const { formatArgs, parse } = createLogParser({
@ -95,6 +97,10 @@ export async function getCommits(
args.push(`--max-count=${limit}`)
}
if (skip !== undefined) {
args.push(`--skip=${skip}`)
}
args.push(
...formatArgs,
'--no-show-signature',
@ -133,15 +139,27 @@ export async function getCommits(
})
}
/** This interface contains information of a changeset. */
export interface IChangesetData {
/** Files changed in the changeset. */
readonly files: ReadonlyArray<CommittedFileChange>
/** Number of lines added in the changeset. */
readonly linesAdded: number
/** Number of lines deleted in the changeset. */
readonly linesDeleted: number
}
/** Get the files that were changed in the given commit. */
export async function getChangedFiles(
repository: Repository,
sha: string
): Promise<ReadonlyArray<CommittedFileChange>> {
): Promise<IChangesetData> {
// opt-in for rename detection (-M) and copies detection (-C)
// this is equivalent to the user configuring 'diff.renames' to 'copies'
// NOTE: order here matters - doing -M before -C means copies aren't detected
const args = [
const baseArgs = [
'log',
sha,
'-C',
@ -150,14 +168,63 @@ export async function getChangedFiles(
'-1',
'--no-show-signature',
'--first-parent',
'--name-status',
'--format=format:',
'-z',
'--',
]
const result = await git(args, repository.path, 'getChangedFiles')
return parseChangedFiles(result.stdout, sha)
// Run `git log` to obtain the file names and their state
const resultNameStatus = await git(
[...baseArgs, '--name-status', '--'],
repository.path,
'getChangedFilesNameStatus'
)
const files = parseChangedFiles(resultNameStatus.stdout, sha)
if (!enableLineChangesInCommit()) {
return { files, linesAdded: 0, linesDeleted: 0 }
}
// Run `git log` again, but this time to get the number of lines added/deleted
// per file
const resultNumStat = await git(
[...baseArgs, '--numstat', '--'],
repository.path,
'getChangedFilesNumStats'
)
const linesChanged = parseChangedFilesNumStat(resultNumStat.stdout)
return {
files,
...linesChanged,
}
}
function parseChangedFilesNumStat(
stdout: string
): { linesAdded: number; linesDeleted: number } {
const lines = stdout.split('\0')
let totalLinesAdded = 0
let totalLinesDeleted = 0
for (const line of lines) {
const parts = line.split('\t')
if (parts.length !== 3) {
continue
}
const [added, deleted] = parts
if (added === '-' || deleted === '-') {
continue
}
totalLinesAdded += parseInt(added, 10)
totalLinesDeleted += parseInt(deleted, 10)
}
return { linesAdded: totalLinesAdded, linesDeleted: totalLinesDeleted }
}
/**
@ -221,9 +288,13 @@ export async function doMergeCommitsExistAfterCommit(
const commitRevRange =
commitRef === null ? undefined : revRange(commitRef, 'HEAD')
const mergeCommits = await getCommits(repository, commitRevRange, undefined, [
'--merges',
])
const mergeCommits = await getCommits(
repository,
commitRevRange,
undefined,
undefined,
['--merges']
)
return mergeCommits.length > 0
}

View file

@ -527,6 +527,31 @@ export class AppStore extends TypedBaseStore<IAppState> {
this.repositoryIndicatorUpdater.start()
}
}, InitialRepositoryIndicatorTimeout)
API.onTokenInvalidated(this.onTokenInvalidated)
}
private onTokenInvalidated = (endpoint: string) => {
const account = getAccountForEndpoint(this.accounts, endpoint)
if (account === null) {
return
}
// If there is a currently open popup, don't do anything here. Since the
// app can only show one popup at a time, we don't want to close the current
// one in favor of the error we're about to show.
if (this.currentPopup !== null) {
return
}
// If the token was invalidated for an account, sign out from that account
this._removeAccount(account)
this._showPopup({
type: PopupType.InvalidatedToken,
account,
})
}
/** Figure out what step of the tutorial the user needs to do next */
@ -993,7 +1018,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
this.repositoryStateCache.updateCommitSelection(repository, () => ({
shas: [],
file: null,
changedFiles: [],
changesetData: { files: [], linesAdded: 0, linesDeleted: 0 },
diff: null,
}))
}
@ -1015,7 +1040,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
this.repositoryStateCache.updateCommitSelection(repository, () => ({
shas,
file: null,
changedFiles: [],
changesetData: { files: [], linesAdded: 0, linesDeleted: 0 },
diff: null,
}))
@ -1127,7 +1152,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
}
// load initial group of commits for current branch
const commits = await gitStore.loadCommitBatch('HEAD')
const commits = await gitStore.loadCommitBatch('HEAD', 0)
if (commits === null) {
return
@ -1278,9 +1303,8 @@ export class AppStore extends TypedBaseStore<IAppState> {
const { formState } = state.compareState
if (formState.kind === HistoryTabMode.History) {
const commits = state.compareState.commitSHAs
const lastCommitSha = commits[commits.length - 1]
const newCommits = await gitStore.loadCommitBatch(`${lastCommitSha}^`)
const newCommits = await gitStore.loadCommitBatch('HEAD', commits.length)
if (newCommits == null) {
return
}
@ -1305,10 +1329,10 @@ export class AppStore extends TypedBaseStore<IAppState> {
}
const gitStore = this.gitStoreCache.get(repository)
const changedFiles = await gitStore.performFailableOperation(() =>
const changesetData = await gitStore.performFailableOperation(() =>
getChangedFiles(repository, currentSHAs[0])
)
if (!changedFiles) {
if (!changesetData) {
return
}
@ -1328,13 +1352,13 @@ export class AppStore extends TypedBaseStore<IAppState> {
const noFileSelected = commitSelection.file === null
const firstFileOrDefault =
noFileSelected && changedFiles.length
? changedFiles[0]
noFileSelected && changesetData.files.length
? changesetData.files[0]
: commitSelection.file
this.repositoryStateCache.updateCommitSelection(repository, () => ({
file: firstFileOrDefault,
changedFiles,
changesetData,
diff: null,
}))

View file

@ -201,44 +201,13 @@ export class GitStore extends BaseStore {
this.emitUpdate()
}
/** Load the next batch of history, starting from the last loaded commit. */
public async loadNextHistoryBatch() {
if (this.requestsInFight.has(LoadingHistoryRequestKey)) {
return
}
if (!this.history.length) {
return
}
const lastSHA = this.history[this.history.length - 1]
const requestKey = `history/${lastSHA}`
if (this.requestsInFight.has(requestKey)) {
return
}
this.requestsInFight.add(requestKey)
const commits = await this.performFailableOperation(() =>
getCommits(this.repository, `${lastSHA}^`, CommitBatchSize)
)
if (!commits) {
return
}
this._history = this._history.concat(commits.map(c => c.sha))
this.storeCommits(commits)
this.requestsInFight.delete(requestKey)
this.emitUpdate()
}
/** Load a batch of commits from the repository, using a given commitish object as the starting point */
public async loadCommitBatch(commitish: string) {
public async loadCommitBatch(commitish: string, skip: number) {
if (this.requestsInFight.has(LoadingHistoryRequestKey)) {
return null
}
const requestKey = `history/compare/${commitish}`
const requestKey = `history/compare/${commitish}/skip/${skip}`
if (this.requestsInFight.has(requestKey)) {
return null
}
@ -246,7 +215,7 @@ export class GitStore extends BaseStore {
this.requestsInFight.add(requestKey)
const commits = await this.performFailableOperation(() =>
getCommits(this.repository, commitish, CommitBatchSize)
getCommits(this.repository, commitish, CommitBatchSize, skip)
)
this.requestsInFight.delete(requestKey)
@ -647,7 +616,7 @@ export class GitStore extends BaseStore {
)
} else {
localCommits = await this.performFailableOperation(() =>
getCommits(this.repository, 'HEAD', CommitBatchSize, [
getCommits(this.repository, 'HEAD', CommitBatchSize, undefined, [
'--not',
'--remotes',
])

View file

@ -3,7 +3,6 @@ import { Commit } from '../../models/commit'
import { PullRequest } from '../../models/pull-request'
import { Repository } from '../../models/repository'
import {
CommittedFileChange,
WorkingDirectoryFileChange,
WorkingDirectoryStatus,
} from '../../models/status'
@ -195,7 +194,7 @@ function getInitialRepositoryState(): IRepositoryState {
commitSelection: {
shas: [],
file: null,
changedFiles: new Array<CommittedFileChange>(),
changesetData: { files: [], linesAdded: 0, linesDeleted: 0 },
diff: null,
},
changesState: {

View file

@ -74,6 +74,7 @@ export enum PopupType {
MultiCommitOperation,
WarnLocalChangesBeforeUndo,
WarningBeforeReset,
InvalidatedToken,
}
export type Popup =
@ -298,3 +299,7 @@ export type Popup =
repository: Repository
commit: Commit
}
| {
type: PopupType.InvalidatedToken
account: Account
}

View file

@ -9,7 +9,8 @@ import { Button } from '../lib/button'
import { TextBox } from '../lib/text-box'
import { Row } from '../lib/row'
import { Dialog, DialogContent, DialogFooter } from '../dialog'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { LinkButton } from '../lib/link-button'
import { PopupType } from '../../models/popup'
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'

View file

@ -24,7 +24,8 @@ import { ILicense, getLicenses, writeLicense } from './licenses'
import { writeGitAttributes } from './git-attributes'
import { getDefaultDir, setDefaultDir } from '../lib/default-dir'
import { Dialog, DialogContent, DialogFooter, DialogError } from '../dialog'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { LinkButton } from '../lib/link-button'
import { PopupType } from '../../models/popup'
import { Ref } from '../lib/ref'

View file

@ -1,7 +1,8 @@
import * as React from 'react'
import classNames from 'classnames'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { MenuItem } from '../../models/app-menu'
import { AccessText } from '../lib/access-text'
import { getPlatformSpecificNameOrSymbolForModifier } from '../../lib/menu-item'

View file

@ -55,7 +55,8 @@ import {
BranchDropdown,
RevertProgress,
} from './toolbar'
import { OcticonSymbol, iconForRepository } from './octicons'
import { iconForRepository, OcticonSymbolType } from './octicons'
import * as OcticonSymbol from './octicons/octicons.generated'
import { showCertificateTrustDialog, sendReady } from './main-process-proxy'
import { DiscardChanges } from './discard-changes'
import { Welcome } from './welcome'
@ -147,6 +148,7 @@ import { dragAndDropManager } from '../lib/drag-and-drop-manager'
import { MultiCommitOperation } from './multi-commit-operation/multi-commit-operation'
import { WarnLocalChangesBeforeUndo } from './undo/warn-local-changes-before-undo'
import { WarningBeforeReset } from './reset/warning-before-reset'
import { InvalidatedToken } from './invalidated-token/invalidated-token'
const MinuteInMilliseconds = 1000 * 60
const HourInMilliseconds = MinuteInMilliseconds * 60
@ -2116,6 +2118,16 @@ export class App extends React.Component<IAppProps, IAppState> {
/>
)
}
case PopupType.InvalidatedToken: {
return (
<InvalidatedToken
key="invalidated-token"
dispatcher={this.props.dispatcher}
account={popup.account}
onDismissed={onPopupDismissedFn}
/>
)
}
default:
return assertNever(popup, `Unknown popup type: ${popup}`)
}
@ -2441,7 +2453,7 @@ export class App extends React.Component<IAppProps, IAppState> {
const repository = selection ? selection.repository : null
let icon: OcticonSymbol
let icon: OcticonSymbolType
let title: string
if (repository) {
const alias = repository instanceof Repository ? repository.alias : null

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
interface IBannerProps {
readonly id?: string

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Banner } from './banner'
export function BranchAlreadyUpToDate({

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Banner } from './banner'
import { LinkButton } from '../lib/link-button'

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Banner } from './banner'
import { LinkButton } from '../lib/link-button'

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Banner } from './banner'
import { Dispatcher } from '../dispatcher'
import { Popup } from '../../models/popup'

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Banner } from './banner'
import { Dispatcher } from '../dispatcher'
import { LinkButton } from '../lib/link-button'

View file

@ -1,6 +1,7 @@
import * as React from 'react'
import { LinkButton } from '../lib/link-button'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Banner } from './banner'
interface ISuccessBannerProps {

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import { Dispatcher } from '../dispatcher/index'
import { LinkButton } from '../lib/link-button'
import { updateStore } from '../lib/update-store'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { PopupType } from '../../models/popup'
import { shell } from '../../lib/app-shell'

View file

@ -4,11 +4,11 @@ import moment from 'moment'
import { IMatches } from '../../lib/fuzzy-find'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { HighlightText } from '../lib/highlight-text'
import { showContextualMenu } from '../main-process-proxy'
import { IMenuItem } from '../../lib/menu-item'
import { String } from 'aws-sdk/clients/apigateway'
import { dragAndDropManager } from '../../lib/drag-and-drop-manager'
import { DragType, DropTargetType } from '../../models/drag-drop'
@ -33,7 +33,7 @@ interface IBranchListItemProps {
readonly onDeleteBranch?: (branchName: string) => void
/** When a drag element has landed on a branch that is not current */
readonly onDropOntoBranch?: (branchName: String) => void
readonly onDropOntoBranch?: (branchName: string) => void
/** When a drag element has landed on the current branch */
readonly onDropOntoCurrentBranch?: () => void

View file

@ -16,7 +16,8 @@ import { assertNever } from '../../lib/fatal-error'
import { TabBar } from '../tab-bar'
import { Row } from '../lib/row'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Button } from '../lib/button'
import { BranchList } from './branch-list'

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon, OcticonSymbolType } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import classNames from 'classnames'
import { GitHubRepository } from '../../models/github-repository'
import { IDisposable } from 'event-kit'
@ -109,7 +110,7 @@ export class CIStatus extends React.PureComponent<
}
}
function getSymbolForCheck(check: ICombinedRefCheck): OcticonSymbol {
function getSymbolForCheck(check: ICombinedRefCheck): OcticonSymbolType {
switch (check.conclusion) {
case 'timed_out':
return OcticonSymbol.x

View file

@ -1,7 +1,8 @@
import * as React from 'react'
import moment from 'moment'
import classNames from 'classnames'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { CIStatus } from './ci-status'
import { HighlightText } from '../lib/highlight-text'
import { IMatches } from '../../lib/fuzzy-find'

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import { PathLabel } from '../lib/path-label'
import { AppFileStatus } from '../../models/status'
import { IDiff, DiffType } from '../../models/diff'
import { Octicon, OcticonSymbol, iconForStatus } from '../octicons'
import { Octicon, iconForStatus } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { mapStatus } from '../../lib/status'
import { DiffOptions } from '../diff/diff-options'
import { RepositorySectionTab } from '../../lib/app-state'

View file

@ -34,17 +34,19 @@ import { basename } from 'path'
import { Commit, ICommitContext } from '../../models/commit'
import { RebaseConflictState, ConflictState } from '../../lib/app-state'
import { ContinueRebase } from './continue-rebase'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { IStashEntry } from '../../models/stash-entry'
import classNames from 'classnames'
import { hasWritePermission } from '../../models/github-repository'
import { hasConflictedFiles } from '../../lib/status'
const RowHeight = 29
const StashIcon = new OcticonSymbol(
16,
16,
'M10.5 1.286h-9a.214.214 0 0 0-.214.214v9a.214.214 0 0 0 .214.214h9a.214.214 0 0 0 ' +
const StashIcon: OcticonSymbol.OcticonSymbolType = {
w: 16,
h: 16,
d:
'M10.5 1.286h-9a.214.214 0 0 0-.214.214v9a.214.214 0 0 0 .214.214h9a.214.214 0 0 0 ' +
'.214-.214v-9a.214.214 0 0 0-.214-.214zM1.5 0h9A1.5 1.5 0 0 1 12 1.5v9a1.5 1.5 0 0 1-1.5 ' +
'1.5h-9A1.5 1.5 0 0 1 0 10.5v-9A1.5 1.5 0 0 1 1.5 0zm5.712 7.212a1.714 1.714 0 1 ' +
'1-2.424-2.424 1.714 1.714 0 0 1 2.424 2.424zM2.015 12.71c.102.729.728 1.29 1.485 ' +
@ -52,8 +54,8 @@ const StashIcon = new OcticonSymbol(
'.004.043v9a.214.214 0 0 1-.214.214h-9a.216.216 0 0 1-.043-.004H2.015zm2 2c.102.729.728 ' +
'1.29 1.485 1.29h9a1.5 1.5 0 0 0 1.5-1.5v-9a1.5 1.5 0 0 0-1.29-1.485v1.442a.216.216 0 0 1 ' +
'.004.043v9a.214.214 0 0 1-.214.214h-9a.216.216 0 0 1-.043-.004H4.015z',
'evenodd'
)
fr: 'evenodd',
}
const GitIgnoreFileName = '.gitignore'

View file

@ -5,7 +5,8 @@ import { Row } from '../lib/row'
import { Popover, PopoverCaretPosition } from '../lib/popover'
import { IAvatarUser } from '../../models/avatar'
import { Avatar } from '../lib/avatar'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { LinkButton } from '../lib/link-button'
interface ICommitMessageAvatarState {

View file

@ -17,7 +17,7 @@ import { Button } from '../lib/button'
import { Loading } from '../lib/loading'
import { AuthorInput } from '../lib/author-input'
import { FocusContainer } from '../lib/focus-container'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import { IAuthor } from '../../models/author'
import { IMenuItem } from '../../lib/menu-item'
import { Commit, ICommitContext } from '../../models/commit'
@ -36,16 +36,17 @@ import { PopupType } from '../../models/popup'
import { RepositorySettingsTab } from '../repository-settings/repository-settings'
import { isAccountEmail } from '../../lib/is-account-email'
const addAuthorIcon = new OcticonSymbol(
18,
13,
'M14 6V4.25a.75.75 0 0 1 1.5 0V6h1.75a.75.75 0 1 1 0 1.5H15.5v1.75a.75.75 0 0 ' +
const addAuthorIcon = {
w: 18,
h: 13,
d:
'M14 6V4.25a.75.75 0 0 1 1.5 0V6h1.75a.75.75 0 1 1 0 1.5H15.5v1.75a.75.75 0 0 ' +
'1-1.5 0V7.5h-1.75a.75.75 0 1 1 0-1.5H14zM8.5 4a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 ' +
'0zm.063 3.064a3.995 3.995 0 0 0 1.2-4.429A3.996 3.996 0 0 0 8.298.725a4.01 4.01 0 0 ' +
'0-6.064 1.91 3.987 3.987 0 0 0 1.2 4.43A5.988 5.988 0 0 0 0 12.2a.748.748 0 0 0 ' +
'.716.766.751.751 0 0 0 .784-.697 4.49 4.49 0 0 1 1.39-3.04 4.51 4.51 0 0 1 6.218 ' +
'0 4.49 4.49 0 0 1 1.39 3.04.748.748 0 0 0 .786.73.75.75 0 0 0 .714-.8 5.989 5.989 0 0 0-3.435-5.136z'
)
'0 4.49 4.49 0 0 1 1.39 3.04.748.748 0 0 0 .786.73.75.75 0 0 0 .714-.8 5.989 5.989 0 0 0-3.435-5.136z',
}
interface ICommitMessageProps {
readonly onCreateCommit: (context: ICommitContext) => Promise<boolean>

View file

@ -1,6 +1,7 @@
import * as React from 'react'
import { assertNever } from '../../lib/fatal-error'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
export enum CommitWarningIcon {
Warning,

View file

@ -18,7 +18,6 @@ import { WorkingDirectoryStatus } from '../../models/status'
import { getResolvedFiles } from '../../lib/status'
import { ConfirmCherryPickAbortDialog } from './confirm-cherry-pick-abort-dialog'
import { CreateBranch } from '../create-branch'
import { String } from 'aws-sdk/clients/acm'
import { ConflictsDialog } from '../multi-commit-operation/conflicts-dialog'
interface ICherryPickFlowProps {
@ -161,7 +160,7 @@ export class CherryPickFlow extends React.Component<ICherryPickFlowProps> {
})
}
private onCreateNewBranch = (targetBranchName: String) => {
private onCreateNewBranch = (targetBranchName: string) => {
const { dispatcher, repository } = this.props
dispatcher.setCherryPickCreateBranchFlowStep(repository, targetBranchName)
}

View file

@ -1,7 +1,8 @@
import * as React from 'react'
import { RichText } from '../lib/rich-text'
import { Dialog, DialogContent } from '../dialog'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { ICherryPickProgress } from '../../models/progress'
interface ICherryPickProgressDialogProps {

View file

@ -1,7 +1,8 @@
import { IAPIRepository } from '../../lib/api'
import { IFilterListGroup, IFilterListItem } from '../lib/filter-list'
import { caseInsensitiveCompare } from '../../lib/compare'
import { OcticonSymbol } from '../octicons'
import { OcticonSymbolType } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
/** The identifier for the "Your Repositories" grouping. */
export const YourRepositoriesIdentifier = 'your-repositories'
@ -17,13 +18,13 @@ export interface ICloneableRepositoryListItem extends IFilterListItem {
readonly name: string
/** The icon for the repo. */
readonly icon: OcticonSymbol
readonly icon: OcticonSymbolType
/** The clone URL. */
readonly url: string
}
function getIcon(gitHubRepo: IAPIRepository): OcticonSymbol {
function getIcon(gitHubRepo: IAPIRepository): OcticonSymbolType {
if (gitHubRepo.private) {
return OcticonSymbol.lock
}

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import { CloningRepository } from '../models/cloning-repository'
import { ICloneProgress } from '../models/progress'
import { Octicon, OcticonSymbol } from './octicons'
import { Octicon } from './octicons'
import * as OcticonSymbol from './octicons/octicons.generated'
import { UiView } from './ui-view'
interface ICloningRepositoryProps {

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
/**
* A component used for displaying short error messages inline

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol, syncClockwise } from '../octicons'
import { Octicon, syncClockwise } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
interface IDialogHeaderProps {
/**

View file

@ -1,6 +1,7 @@
import * as React from 'react'
import { Checkbox, CheckboxValue } from '../lib/checkbox'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { RadioButton } from '../lib/radio-button'
import { getBoolean, setBoolean } from '../../lib/local-storage'
import { Popover, PopoverCaretPosition } from '../lib/popover'

View file

@ -9,7 +9,8 @@ import {
} from './diff-helpers'
import { ILineTokens } from '../../lib/highlighter/types'
import classNames from 'classnames'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { narrowNoNewlineSymbol } from './text-diff'
import { shallowEquals, structuralEquals } from '../../lib/equality'
import { DiffHunkExpansionType } from '../../models/diff'

View file

@ -15,8 +15,6 @@ import {
CommittedFileChange,
} from '../../models/status'
import { OcticonSymbol } from '../octicons'
import { IEditorConfigurationExtra } from './editor-configuration-extra'
import { DiffSyntaxMode, IDiffSyntaxModeSpec } from './diff-syntax-mode'
import { CodeMirrorHost } from './code-mirror-host'
@ -56,6 +54,7 @@ import {
expandWholeTextDiff,
} from './text-diff-expansion'
import { createOcticonElement } from '../octicons/octicon'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { HideWhitespaceWarning } from './hide-whitespace-warning'
/** The longest line for which we'd try to calculate a line diff. */
@ -63,11 +62,12 @@ const MaxIntraLineDiffStringLength = 4096
// This is a custom version of the no-newline octicon that's exactly as
// tall as it needs to be (8px) which helps with aligning it on the line.
export const narrowNoNewlineSymbol = new OcticonSymbol(
16,
8,
'm 16,1 0,3 c 0,0.55 -0.45,1 -1,1 l -3,0 0,2 -3,-3 3,-3 0,2 2,0 0,-2 2,0 z M 8,4 C 8,6.2 6.2,8 4,8 1.8,8 0,6.2 0,4 0,1.8 1.8,0 4,0 6.2,0 8,1.8 8,4 Z M 1.5,5.66 5.66,1.5 C 5.18,1.19 4.61,1 4,1 2.34,1 1,2.34 1,4 1,4.61 1.19,5.17 1.5,5.66 Z M 7,4 C 7,3.39 6.81,2.83 6.5,2.34 L 2.34,6.5 C 2.82,6.81 3.39,7 4,7 5.66,7 7,5.66 7,4 Z'
)
export const narrowNoNewlineSymbol = {
w: 16,
h: 8,
d:
'm 16,1 0,3 c 0,0.55 -0.45,1 -1,1 l -3,0 0,2 -3,-3 3,-3 0,2 2,0 0,-2 2,0 z M 8,4 C 8,6.2 6.2,8 4,8 1.8,8 0,6.2 0,4 0,1.8 1.8,0 4,0 6.2,0 8,1.8 8,4 Z M 1.5,5.66 5.66,1.5 C 5.18,1.19 4.61,1 4,1 2.34,1 1,2.34 1,4 1,4.61 1.19,5.17 1.5,5.66 Z M 7,4 C 7,3.39 6.81,2.83 6.5,2.34 L 2.34,6.5 C 2.82,6.81 3.39,7 4,7 5.66,7 7,5.66 7,4 Z',
}
type ChangedFile = WorkingDirectoryFileChange | CommittedFileChange

View file

@ -1524,9 +1524,15 @@ export class Dispatcher {
/**
* Launch a sign in dialog for authenticating a user with
* a GitHub Enterprise instance.
* Optionally, you can provide an endpoint URL.
*/
public async showEnterpriseSignInDialog(): Promise<void> {
public async showEnterpriseSignInDialog(endpoint?: string): Promise<void> {
await this.appStore._beginEnterpriseSignIn()
if (endpoint !== undefined) {
await this.appStore._setSignInEndpoint(endpoint)
}
await this.appStore._showPopup({ type: PopupType.SignIn })
}

View file

@ -7,7 +7,8 @@ import { Commit } from '../../models/commit'
import { DragType, DropTarget, DropTargetType } from '../../models/drag-drop'
import { GitHubRepository } from '../../models/github-repository'
import { CommitListItem } from '../history/commit-list-item'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
interface ICommitDragElementProps {
readonly commit: Commit

View file

@ -1,7 +1,8 @@
import classNames from 'classnames'
import React from 'react'
import { Button } from './lib/button'
import { Octicon, OcticonSymbol } from './octicons'
import { Octicon } from './octicons'
import * as OcticonSymbol from './octicons/octicons.generated'
export interface IDropdownSelectButtonOption {
/** The select option header label. */

View file

@ -6,6 +6,7 @@ import { Dialog, DialogContent, DialogFooter } from '../dialog'
import { RetryAction } from '../../models/retry-actions'
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'
import { Ref } from '../lib/ref'
import { LinkButton } from '../lib/link-button'
interface IGenericGitAuthenticationProps {
/** The hostname with which the user tried to authenticate. */
@ -74,6 +75,18 @@ export class GenericGitAuthentication extends React.Component<
onValueChanged={this.onPasswordChange}
/>
</Row>
<Row>
<div>
Depending on your repository's hosting service, you might need to
use a Personal Access Token (PAT) as your password. Learn more
about creating a PAT in our{' '}
<LinkButton uri="https://github.com/desktop/desktop/tree/development/docs/integrations">
integration docs
</LinkButton>
.
</div>
</Row>
</DialogContent>
<DialogFooter>

View file

@ -10,7 +10,8 @@ import { showContextualMenu } from '../main-process-proxy'
import { CommitAttribution } from '../lib/commit-attribution'
import { AvatarStack } from '../lib/avatar-stack'
import { IMenuItem } from '../../lib/menu-item'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Draggable } from '../lib/draggable'
import {
enableAmendingCommits,

View file

@ -1,8 +1,8 @@
import * as React from 'react'
import classNames from 'classnames'
import { FileChange } from '../../models/status'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { RichText } from '../lib/rich-text'
import { Repository } from '../../models/repository'
import { Commit } from '../../models/commit'
@ -13,11 +13,12 @@ import { Tokenizer, TokenResult } from '../../lib/text-token-parser'
import { wrapRichTextCommitMessage } from '../../lib/wrap-rich-text-commit-message'
import { DiffOptions } from '../diff/diff-options'
import { RepositorySectionTab } from '../../lib/app-state'
import { IChangesetData } from '../../lib/git'
interface ICommitSummaryProps {
readonly repository: Repository
readonly commit: Commit
readonly files: ReadonlyArray<FileChange>
readonly changesetData: IChangesetData
readonly emoji: Map<string, string>
/**
@ -289,7 +290,7 @@ export class CommitSummary extends React.Component<
}
public render() {
const fileCount = this.props.files.length
const fileCount = this.props.changesetData.files.length
const filesPlural = fileCount === 1 ? 'file' : 'files'
const filesDescription = `${fileCount} changed ${filesPlural}`
const shortSHA = this.props.commit.shortSha
@ -352,6 +353,7 @@ export class CommitSummary extends React.Component<
{filesDescription}
</li>
{this.renderLinesChanged()}
{this.renderTags()}
<li
@ -379,6 +381,36 @@ export class CommitSummary extends React.Component<
)
}
private renderLinesChanged() {
const linesAdded = this.props.changesetData.linesAdded
const linesDeleted = this.props.changesetData.linesDeleted
if (linesAdded + linesDeleted === 0) {
return null
}
const linesAddedPlural = linesAdded === 1 ? 'line' : 'lines'
const linesDeletedPlural = linesDeleted === 1 ? 'line' : 'lines'
const linesAddedTitle = `${linesAdded} ${linesAddedPlural} added`
const linesDeletedTitle = `${linesDeleted} ${linesDeletedPlural} deleted`
return (
<>
<li
className="commit-summary-meta-item without-truncation lines-added"
title={linesAddedTitle}
>
+{linesAdded}
</li>
<li
className="commit-summary-meta-item without-truncation lines-deleted"
title={linesDeletedTitle}
>
-{linesDeleted}
</li>
</>
)
}
private renderTags() {
const tags = this.props.commit.tags || []

View file

@ -1,6 +1,7 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { HighlightText } from '../lib/highlight-text'
import { Branch, IAheadBehind } from '../../models/branch'
import { IMatches } from '../../lib/fuzzy-find'

View file

@ -19,7 +19,7 @@ import { IBranchListItem } from '../branches/group-branches'
import { TabBar } from '../tab-bar'
import { CompareBranchListItem } from './compare-branch-list-item'
import { FancyTextBox } from '../lib/fancy-text-box'
import { OcticonSymbol } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { SelectionSource } from '../lib/filter-list'
import { IMatches } from '../../lib/fuzzy-find'
import { Ref } from '../lib/ref'
@ -498,7 +498,7 @@ export class CompareSidebar extends React.Component<
if (commits.length - end <= CloseToBottomThreshold) {
if (this.loadingMoreCommitsPromise != null) {
// as this callback fires for any scroll event we need to guard
// against re-entrant calls to loadNextHistoryBatch
// against re-entrant calls to loadCommitBatch
return
}

View file

@ -28,13 +28,14 @@ import { showContextualMenu } from '../main-process-proxy'
import { CommitSummary } from './commit-summary'
import { FileList } from './file-list'
import { SeamlessDiffSwitcher } from '../diff/seamless-diff-switcher'
import { IChangesetData } from '../../lib/git'
interface ISelectedCommitProps {
readonly repository: Repository
readonly dispatcher: Dispatcher
readonly emoji: Map<string, string>
readonly selectedCommit: Commit | null
readonly changedFiles: ReadonlyArray<CommittedFileChange>
readonly changesetData: IChangesetData
readonly selectedFile: CommittedFileChange | null
readonly currentDiff: IDiff | null
readonly commitSummaryWidth: number
@ -132,7 +133,7 @@ export class SelectedCommit extends React.Component<
if (file == null) {
// don't show both 'empty' messages
const message =
this.props.changedFiles.length === 0 ? '' : 'No file selected'
this.props.changesetData.files.length === 0 ? '' : 'No file selected'
return (
<div className="panel blankslate" id="diff">
@ -160,7 +161,7 @@ export class SelectedCommit extends React.Component<
return (
<CommitSummary
commit={commit}
files={this.props.changedFiles}
changesetData={this.props.changesetData}
emoji={this.props.emoji}
repository={this.props.repository}
onExpandChanged={this.onExpandChanged}
@ -210,7 +211,7 @@ export class SelectedCommit extends React.Component<
}
private renderFileList() {
const files = this.props.changedFiles
const files = this.props.changesetData.files
if (files.length === 0) {
return <div className="fill-window">No files in commit</div>
}

View file

@ -0,0 +1,62 @@
import * as React from 'react'
import { Dialog, DialogContent, DialogFooter } from '../dialog'
import { Dispatcher } from '../dispatcher'
import { Row } from '../lib/row'
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'
import { Account } from '../../models/account'
import { getDotComAPIEndpoint } from '../../lib/api'
interface IInvalidatedTokenResetProps {
readonly dispatcher: Dispatcher
readonly account: Account
readonly onDismissed: () => void
}
/**
* Dialog that alerts user that their GitHub (Enterprise) account token is not
* valid and they need to sign in again.
*/
export class InvalidatedToken extends React.Component<
IInvalidatedTokenResetProps
> {
public render() {
const accountTypeSuffix = this.isEnterpriseAccount ? ' Enterprise' : ''
return (
<Dialog
id="invalidated-token"
type="warning"
title="Warning"
onSubmit={this.onSubmit}
onDismissed={this.props.onDismissed}
>
<DialogContent>
<Row>
Your account token has been invalidated and you have been signed out
from your GitHub{accountTypeSuffix} account. Do you want to sign in
again?
</Row>
</DialogContent>
<DialogFooter>
<OkCancelButtonGroup okButtonText="Yes" cancelButtonText="No" />
</DialogFooter>
</Dialog>
)
}
private get isEnterpriseAccount() {
return this.props.account.endpoint !== getDotComAPIEndpoint()
}
private onSubmit = () => {
const { dispatcher, onDismissed } = this.props
onDismissed()
if (this.isEnterpriseAccount) {
dispatcher.showEnterpriseSignInDialog(this.props.account.endpoint)
} else {
dispatcher.showDotComSignInDialog()
}
}
}

View file

@ -3,7 +3,8 @@ import classNames from 'classnames'
import { ComputedAction } from '../../models/computed-action'
import { assertNever } from '../../lib/fatal-error'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon, OcticonSymbolType } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
interface IActionStatusIconProps {
/** The status to display to the user */
@ -51,7 +52,7 @@ export class ActionStatusIcon extends React.Component<IActionStatusIconProps> {
}
}
function getSymbolForState(status: ComputedAction): OcticonSymbol {
function getSymbolForState(status: ComputedAction): OcticonSymbolType {
switch (status) {
case ComputedAction.Loading:
return OcticonSymbol.dotFill

View file

@ -1,6 +1,7 @@
import * as React from 'react'
import { LinkButton } from '../lib/link-button'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Loading } from './loading'
import { Form } from './form'
import { Button } from './button'

View file

@ -10,7 +10,8 @@ import classNames from 'classnames'
import { UserAutocompletionProvider, IUserHit } from '../autocompletion'
import { compare } from '../../lib/compare'
import { arrayEquals } from '../../lib/equality'
import { OcticonSymbol, syncClockwise } from '../octicons'
import { syncClockwise } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { IAuthor } from '../../models/author'
import { showContextualMenu } from '../main-process-proxy'
import { IMenuItem } from '../../lib/menu-item'

View file

@ -2,7 +2,7 @@ import * as React from 'react'
import { IAvatarUser } from '../../models/avatar'
import { shallowEquals } from '../../lib/equality'
import { generateGravatarUrl } from '../../lib/gravatar'
import { OcticonSymbol, Octicon } from '../octicons'
import { Octicon } from '../octicons'
import { getDotComAPIEndpoint } from '../../lib/api'
interface IAvatarProps {
@ -35,11 +35,12 @@ const avatarEndpoint = 'https://avatars.githubusercontent.com'
* The octicon has been tweaked to add some padding and so that it scales nicely in
* a square aspect ratio.
*/
const DefaultAvatarSymbol = new OcticonSymbol(
16,
16,
'M13 13.145a.844.844 0 0 1-.832.855H3.834A.846.846 0 0 1 3 13.142v-.856c0-2.257 3.333-3.429 3.333-3.429s.191-.35 0-.857c-.7-.531-.786-1.363-.833-3.429C5.644 2.503 7.056 2 8 2s2.356.502 2.5 2.571C10.453 6.637 10.367 7.47 9.667 8c-.191.506 0 .857 0 .857S13 10.03 13 12.286v.859z'
)
const DefaultAvatarSymbol = {
w: 16,
h: 16,
d:
'M13 13.145a.844.844 0 0 1-.832.855H3.834A.846.846 0 0 1 3 13.142v-.856c0-2.257 3.333-3.429 3.333-3.429s.191-.35 0-.857c-.7-.531-.786-1.363-.833-3.429C5.644 2.503 7.056 2 8 2s2.356.502 2.5 2.571C10.453 6.637 10.367 7.47 9.667 8c-.191.506 0 .857 0 .857S13 10.03 13 12.286v.859z',
}
/**
* A regular expression meant to match both the legacy format GitHub.com

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import { Branch, BranchType } from '../../models/branch'
import { Row } from './row'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Ref } from './ref'
import { IStashEntry } from '../../models/stash-entry'

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../../octicons'
import { Octicon } from '../../octicons'
import * as OcticonSymbol from '../../octicons/octicons.generated'
import { LinkButton } from '../link-button'
export function renderUnmergedFilesSummary(conflictedFilesCount: number) {

View file

@ -11,7 +11,8 @@ import { join } from 'path'
import { Repository } from '../../../models/repository'
import { Dispatcher } from '../../dispatcher'
import { showContextualMenu } from '../../main-process-proxy'
import { Octicon, OcticonSymbol } from '../../octicons'
import { Octicon } from '../../octicons'
import * as OcticonSymbol from '../../octicons/octicons.generated'
import { PathText } from '../path-text'
import { ManualConflictResolution } from '../../../models/manual-conflict-resolution'
import {

View file

@ -1,11 +1,11 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon, OcticonSymbolType } from '../octicons'
import { TextBox, ITextBoxProps } from './text-box'
import classNames from 'classnames'
interface IFancyTextBoxProps extends ITextBoxProps {
/** Icon to render */
readonly symbol: OcticonSymbol
readonly symbol: OcticonSymbolType
/** Callback used to get a reference to internal TextBox */
readonly onRef: (textbox: TextBox) => void

View file

@ -1,7 +1,8 @@
import * as React from 'react'
import { AppFileStatus, AppFileStatusKind } from '../../models/status'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { PathText } from './path-text'
interface IPathLabelProps {

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import { sanitizedRefName } from '../../lib/sanitize-ref-name'
import { TextBox } from './text-box'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Ref } from './ref'
interface IRefNameProps {

View file

@ -35,6 +35,12 @@ interface ISegmentedItemProps<T> {
* a pointer device.
*/
readonly onClick: (value: T) => void
/**
* A function that's called when a user double-clicks on the item
* using a pointer device.
*/
readonly onDoubleClick: (value: T) => void
}
export class SegmentedItem<T> extends React.Component<
@ -45,6 +51,10 @@ export class SegmentedItem<T> extends React.Component<
this.props.onClick(this.props.value)
}
private onDoubleClick = () => {
this.props.onDoubleClick(this.props.value)
}
public render() {
const description = this.props.description ? (
<p>{this.props.description}</p>
@ -57,6 +67,7 @@ export class SegmentedItem<T> extends React.Component<
<li
className={className}
onClick={this.onClick}
onDoubleClick={this.onDoubleClick}
role="radio"
id={this.props.id}
aria-checked={isSelected ? 'true' : 'false'}

View file

@ -116,12 +116,27 @@ export class VerticalSegmentedControl<T extends Key> extends React.Component<
}
}
private submitForm() {
const form = this.formRef
if (form) {
// NB: In order to play nicely with React's custom event dispatching,
// we dispatch an event instead of calling `submit` directly on the
// form.
form.dispatchEvent(new Event('submit'))
}
}
private onItemClick = (key: T) => {
if (key !== this.props.selectedKey) {
this.props.onSelectionChanged(key)
}
}
private onItemDoubleClick = (key: T) => {
this.onItemClick(key)
this.submitForm()
}
private getListItemId(index: number) {
return `${this.state.listId}_Item_${index}`
}
@ -136,6 +151,7 @@ export class VerticalSegmentedControl<T extends Key> extends React.Component<
isSelected={item.key === this.props.selectedKey}
value={item.key}
onClick={this.onItemClick}
onDoubleClick={this.onItemDoubleClick}
/>
)
}
@ -154,13 +170,7 @@ export class VerticalSegmentedControl<T extends Key> extends React.Component<
}
event.preventDefault()
} else if (event.key === 'Enter') {
const form = this.formRef
if (form) {
// NB: In order to play nicely with React's custom event dispatching,
// we dispatch an event instead of calling `submit` directly on the
// form.
form.dispatchEvent(new Event('submit'))
}
this.submitForm()
}
}

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import { formatRebaseValue } from '../../lib/rebase'
import { RichText } from '../lib/rich-text'
import { Dialog, DialogContent } from '../dialog'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { IMultiCommitOperationProgress } from '../../models/progress'
interface IProgressDialogProps {

View file

@ -1,7 +1,8 @@
import * as React from 'react'
import { UiView } from '../ui-view'
import { Button } from '../lib/button'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon, OcticonSymbolType } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import {
WelcomeLeftTopImageUri,
WelcomeLeftBottomImageUri,
@ -340,7 +341,7 @@ export class NoRepositoriesView extends React.Component<
private onShowClone = () => this.props.onClone()
private renderButtonGroupButton(
symbol: OcticonSymbol,
symbol: OcticonSymbolType,
title: string,
onClick: () => void,
type?: 'submit'

View file

@ -1,5 +1,5 @@
export { OcticonSymbolType } from './octicons.generated'
export { Octicon } from './octicon'
export { OcticonSymbol } from './octicons.generated'
export { iconForRepository } from './repository'
export { iconForStatus } from './status'
export { syncClockwise } from './sync-clockwise'

View file

@ -1,5 +1,5 @@
import * as React from 'react'
import { OcticonSymbol } from './octicons.generated'
import { OcticonSymbolType } from './octicons.generated'
import classNames from 'classnames'
import { createUniqueId, releaseUniqueId } from '../lib/id-pool'
import ReactDOM from 'react-dom'
@ -10,7 +10,7 @@ interface IOcticonProps {
* type. Supports custom paths as well as those provided
* through the static properties of the OcticonSymbol class.
*/
readonly symbol: OcticonSymbol
readonly symbol: OcticonSymbolType
/**
* An optional classname that will be appended to the default
@ -86,7 +86,7 @@ export class Octicon extends React.Component<IOcticonProps, {}> {
* @param id Optional identifier to set to the wrapper element.
*/
export function createOcticonElement(
symbol: OcticonSymbol,
symbol: OcticonSymbolType,
className?: string,
id?: string
) {

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
import { OcticonSymbol } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Repository } from '../../models/repository'
import { CloningRepository } from '../../models/cloning-repository'

View file

@ -3,7 +3,8 @@ import {
AppFileStatus,
isConflictWithMarkers,
} from '../../models/status'
import { OcticonSymbol } from './octicons.generated'
import * as OcticonSymbol from './octicons.generated'
import { OcticonSymbolType } from '../octicons'
import { assertNever } from '../../lib/fatal-error'
/**
@ -12,7 +13,7 @@ import { assertNever } from '../../lib/fatal-error'
*
* Used in file lists.
*/
export function iconForStatus(status: AppFileStatus): OcticonSymbol {
export function iconForStatus(status: AppFileStatus): OcticonSymbolType {
switch (status.kind) {
case AppFileStatusKind.New:
case AppFileStatusKind.Untracked:

View file

@ -1,16 +1,17 @@
import { OcticonSymbol } from './octicons.generated'
import { OcticonSymbolType } from './octicons.generated'
/**
* An horizontally flipped (i.e `scaleX(-1)`) but otherwise unmodified version of
* the `sync` octicon.
*/
export const syncClockwise = new OcticonSymbol(
16,
16,
'M8 2.5c1.645 0 3.123.722 4.131 1.869l-1.204 1.204a.25.25 0 0 0 .177.427h3.646a.25.25 ' +
export const syncClockwise: OcticonSymbolType = {
w: 16,
h: 16,
d:
'M8 2.5c1.645 0 3.123.722 4.131 1.869l-1.204 1.204a.25.25 0 0 0 .177.427h3.646a.25.25 ' +
'0 0 0 .25-.25V2.104a.25.25 0 0 0-.427-.177l-1.38 1.38A7.001 7.001 0 0 0 1.05 7.16a.75.75 ' +
'0 1 0 1.49.178A5.501 5.501 0 0 1 8 2.5zm6.294 5.505a.75.75 0 0 0-.833.656 5.501 5.501 ' +
'0 0 1-9.592 2.97l1.204-1.204A.25.25 0 0 0 4.896 10H1.25a.25.25 0 0 0-.25.25v3.646c0 ' +
'.223.27.335.427.177l1.38-1.38A7.001 7.001 0 0 0 14.95 8.84a.75.75 0 0 0-.657-.834z',
'evenodd'
)
fr: 'evenodd',
}

View file

@ -27,7 +27,8 @@ import {
UncommittedChangesStrategy,
defaultUncommittedChangesStrategy,
} from '../../models/uncommitted-changes-strategy'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import {
isConfigFileLockError,
parseConfigLockFilePathFromError,

View file

@ -8,7 +8,8 @@ import { Row } from '../lib/row'
import { merge } from '../../lib/merge'
import { caseInsensitiveCompare } from '../../lib/compare'
import { sanitizedRepositoryName } from '../add-repository/sanitized-repository-name'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { RepositoryPublicationSettings } from '../../models/publish-settings'
interface IPublishRepositoryProps {

View file

@ -5,7 +5,8 @@ import { formatRebaseValue } from '../../lib/rebase'
import { RichText } from '../lib/rich-text'
import { Dialog, DialogContent } from '../dialog'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { IMultiCommitOperationProgress } from '../../models/progress'
interface IRebaseProgressDialogProps {

View file

@ -14,7 +14,8 @@ import { IMatches } from '../../lib/fuzzy-find'
import { ILocalRepositoryState, Repository } from '../../models/repository'
import { Dispatcher } from '../dispatcher'
import { Button } from '../lib/button'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { showContextualMenu } from '../main-process-proxy'
import { IMenuItem } from '../../lib/menu-item'
import { PopupType } from '../../models/popup'

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import { clipboard } from 'electron'
import { Repository } from '../../models/repository'
import { Octicon, iconForRepository, OcticonSymbol } from '../octicons'
import { Octicon, iconForRepository } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { showContextualMenu } from '../main-process-proxy'
import { Repositoryish } from './group-repositories'
import { IMenuItem } from '../../lib/menu-item'

View file

@ -377,7 +377,7 @@ export class RepositoryView extends React.Component<
const selectedCommit =
sha != null ? this.props.state.commitLookup.get(sha) || null : null
const { changedFiles, file, diff } = commitSelection
const { changesetData, file, diff } = commitSelection
const showDragOverlay = dragAndDropManager.isDragOfTypeInProgress(
DragType.Commit
@ -388,7 +388,7 @@ export class RepositoryView extends React.Component<
repository={this.props.repository}
dispatcher={this.props.dispatcher}
selectedCommit={selectedCommit}
changedFiles={changedFiles}
changesetData={changesetData}
selectedFile={file}
currentDiff={diff}
emoji={this.props.emoji}

View file

@ -9,7 +9,8 @@ import {
} from '../../lib/stores'
import { assertNever } from '../../lib/fatal-error'
import { LinkButton } from '../lib/link-button'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Row } from '../lib/row'
import { TextBox } from '../lib/text-box'
import { Dialog, DialogError, DialogContent, DialogFooter } from '../dialog'

View file

@ -6,7 +6,8 @@ import { VerticalSegmentedControl } from '../lib/vertical-segmented-control'
import { Row } from '../lib/row'
import { Branch } from '../../models/branch'
import { UncommittedChangesStrategy } from '../../models/uncommitted-changes-strategy'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { PopupType } from '../../models/popup'
import { startTimer } from '../lib/timing'
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'

View file

@ -3,7 +3,8 @@ import { IStashEntry } from '../../models/stash-entry'
import { Dispatcher } from '../dispatcher'
import { Repository } from '../../models/repository'
import { PopupType } from '../../models/popup'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { OkCancelButtonGroup } from '../dialog/ok-cancel-button-group'
interface IStashDiffHeaderProps {

View file

@ -1,6 +1,7 @@
import * as React from 'react'
import { Dispatcher } from '../dispatcher'
import { OcticonSymbol, syncClockwise } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { syncClockwise } from '../octicons'
import { Repository } from '../../models/repository'
import { TipState } from '../../models/tip'
import { ToolbarDropdown, DropdownState } from './dropdown'
@ -97,7 +98,7 @@ export class BranchDropdown extends React.Component<IBranchDropdownProps> {
const tipKind = tip.kind
let icon = OcticonSymbol.gitBranch
let icon: OcticonSymbol.OcticonSymbolType = OcticonSymbol.gitBranch
let iconClassName: string | undefined = undefined
let title: string
let description = __DARWIN__ ? 'Current Branch' : 'Current branch'

View file

@ -1,5 +1,5 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon, OcticonSymbolType } from '../octicons'
import classNames from 'classnames'
import { assertNever } from '../../lib/fatal-error'
import { Button } from '../lib/button'
@ -25,7 +25,7 @@ export interface IToolbarButtonProps {
readonly tooltip?: string
/** An optional symbol to be displayed next to the button text */
readonly icon?: OcticonSymbol
readonly icon?: OcticonSymbolType
/** The class name for the icon element. */
readonly iconClassName?: string

View file

@ -1,5 +1,6 @@
import * as React from 'react'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon, OcticonSymbolType } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { assertNever } from '../../lib/fatal-error'
import { ToolbarButton, ToolbarButtonStyle } from './button'
import { rectEquals } from '../lib/rect'
@ -20,7 +21,7 @@ export interface IToolbarDropdownProps {
readonly tooltip?: string
/** An optional symbol to be displayed next to the button text */
readonly icon?: OcticonSymbol
readonly icon?: OcticonSymbolType
/**
* The state for of the drop down button.
@ -173,7 +174,7 @@ export class ToolbarDropdown extends React.Component<
return this.props.dropdownState === 'open'
}
private dropdownIcon(state: DropdownState): OcticonSymbol {
private dropdownIcon(state: DropdownState): OcticonSymbolType {
// @TODO: Remake triangle octicon in a 12px version,
// right now it's scaled badly on normal dpi monitors.
if (state === 'open') {

View file

@ -7,7 +7,8 @@ import { TipState } from '../../models/tip'
import { FetchType } from '../../models/fetch'
import { Dispatcher } from '../dispatcher'
import { Octicon, OcticonSymbol, syncClockwise } from '../octicons'
import { Octicon, syncClockwise } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { RelativeTime } from '../relative-time'
import { ToolbarButton, ToolbarButtonStyle } from './button'
@ -261,15 +262,16 @@ function pushButton(
* This represents the "double arrow" icon used to show a force-push, and is a
* less complicated icon than the generated Octicon from the `octicons` package.
*/
const forcePushIcon = new OcticonSymbol(
10,
16,
'M0 6a.75.75 0 0 0 .974.714L4.469 3.22a.75.75 0 0 1 1.06 0l3.478 3.478a.75.75 ' +
const forcePushIcon: OcticonSymbol.OcticonSymbolType = {
w: 10,
h: 16,
d:
'M0 6a.75.75 0 0 0 .974.714L4.469 3.22a.75.75 0 0 1 1.06 0l3.478 3.478a.75.75 ' +
'0 0 0 .772-1.228L5.53 1.22a.75.75 0 0 0-1.06 0L.22 5.47A.75.75 0 0 0 0 6zm0 ' +
'3a.75.75 0 0 0 1.28.53l2.97-2.97V14a.75.75 0 1 0 1.5 0V6.56l2.97 2.97a.75.75 ' +
'0 0 0 1.06-1.06L5.53 4.22a.75.75 0 0 0-1.06 0L.22 8.47A.75.75 0 0 0 0 9z',
'evenodd'
)
fr: 'evenodd',
}
function forcePushButton(
remoteName: string,

View file

@ -4,7 +4,8 @@ import { encodePathAsUrl } from '../../lib/path'
import { Dispatcher } from '../dispatcher'
import { Repository } from '../../models/repository'
import { PopupType } from '../../models/popup'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { SuggestedAction } from '../suggested-actions'
import { SuggestedActionGroup } from '../suggested-actions'

View file

@ -4,7 +4,8 @@ import { LinkButton } from '../lib/link-button'
import { Button } from '../lib/button'
import { Repository } from '../../models/repository'
import { Dispatcher } from '../dispatcher'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import {
ValidTutorialStep,
TutorialStep,

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import { WelcomeStep } from './welcome'
import { LinkButton } from '../lib/link-button'
import { Dispatcher } from '../dispatcher'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { Button } from '../lib/button'
import { Loading } from '../lib/loading'
import { BrowserRedirectMessage } from '../lib/authentication-form'

View file

@ -3,7 +3,8 @@ import memoizeOne from 'memoize-one'
import { remote } from 'electron'
import { WindowState } from '../../lib/window-state'
import { WindowControls } from './window-controls'
import { Octicon, OcticonSymbol } from '../octicons'
import { Octicon } from '../octicons/octicon'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { isMacOSBigSurOrLater } from '../../lib/get-os'
/** Get the height (in pixels) of the title bar depending on the platform */

View file

@ -180,6 +180,13 @@ $overlay-background-color: rgba(0, 0, 0, 0.4);
--co-author-tag-background-color: #{$blue-000};
--co-author-tag-border-color: #{$blue-200};
/**
* Author input (co-authors)
*/
--commit-warning-badge-background-color: #{$gray-000};
--commit-warning-badge-border-color: #{$gray-300};
--commit-warning-badge-icon-color: #{$gray-900};
/**
* The height of the title bar area on Win32 platforms
* If changed, update titleBarHeight in 'app/src/ui/dialog/dialog.tsx'

View file

@ -3,16 +3,17 @@
position: relative;
.warning-badge {
background-color: #f6f8fa;
background-color: var(--commit-warning-badge-background-color);
border: var(--commit-warning-badge-border-color) 1px solid;
width: 18px;
height: 18px;
position: absolute;
margin-top: -6px;
margin-left: -7px;
border-radius: 9px;
border: #d1d5da 1px solid;
> svg {
color: var(--commit-warning-badge-icon-color);
height: 10px;
// With width=100%, the icon will be centered horizontally
width: 100%;

View file

@ -31,6 +31,7 @@ dialog#multi-commit-progress {
height: 22px;
width: 22px;
display: flex;
flex: 0 0 auto;
justify-content: center;
align-items: center;
}

View file

@ -71,6 +71,14 @@
&-title,
&-meta {
padding: var(--spacing);
.lines-added {
color: var(--color-new);
}
.lines-deleted {
color: var(--color-deleted);
}
}
&-title {

View file

@ -39,7 +39,7 @@ describe('GitStore', () => {
const repo = new Repository(path, -1, null, false)
const gitStore = new GitStore(repo, shell, statsStore)
const commits = await gitStore.loadCommitBatch('HEAD')
const commits = await gitStore.loadCommitBatch('HEAD', 0)
expect(commits).not.toBeNull()
expect(commits).toHaveLength(100)

View file

@ -187,9 +187,9 @@ describe('git/commit', () => {
expect(newTip.shortSha).toEqual(sha)
// verify that the contents of this new commit are just the new file
const changedFiles = await getChangedFiles(repository, newTip.sha)
expect(changedFiles.length).toEqual(1)
expect(changedFiles[0].path).toEqual(newFileName)
const changesetData = await getChangedFiles(repository, newTip.sha)
expect(changesetData.files.length).toEqual(1)
expect(changesetData.files[0].path).toEqual(newFileName)
// verify that changes remain for this new file
const status = await getStatusOrThrow(repository)
@ -239,9 +239,9 @@ describe('git/commit', () => {
expect(newTip.summary).toEqual('title')
// verify that the contents of this new commit are just the modified file
const changedFiles = await getChangedFiles(repository, newTip.sha)
expect(changedFiles.length).toEqual(1)
expect(changedFiles[0].path).toEqual(modifiedFile)
const changesetData = await getChangedFiles(repository, newTip.sha)
expect(changesetData.files.length).toEqual(1)
expect(changesetData.files[0].path).toEqual(modifiedFile)
// verify that changes remain for this modified file
const status = await getStatusOrThrow(repository)
@ -294,9 +294,9 @@ describe('git/commit', () => {
expect(newTip.shortSha).toEqual(sha)
// verify that the contents of this new commit are just the modified file
const changedFiles = await getChangedFiles(repository, newTip.sha)
expect(changedFiles.length).toEqual(1)
expect(changedFiles[0].path).toEqual(fileName)
const changesetData = await getChangedFiles(repository, newTip.sha)
expect(changesetData.files.length).toEqual(1)
expect(changesetData.files[0].path).toEqual(fileName)
})
it('can commit multiple hunks from modified file', async () => {
@ -340,9 +340,9 @@ describe('git/commit', () => {
expect(newTip.shortSha).toEqual(sha)
// verify that the contents of this new commit are just the modified file
const changedFiles = await getChangedFiles(repository, newTip.sha)
expect(changedFiles.length).toEqual(1)
expect(changedFiles[0].path).toEqual(modifiedFile)
const changesetData = await getChangedFiles(repository, newTip.sha)
expect(changesetData.files.length).toEqual(1)
expect(changesetData.files[0].path).toEqual(modifiedFile)
// verify that changes remain for this modified file
const status = await getStatusOrThrow(repository)
@ -382,9 +382,9 @@ describe('git/commit', () => {
expect(newTip.sha.substring(0, 7)).toEqual(sha)
// verify that the contents of this new commit are just the new file
const changedFiles = await getChangedFiles(repository, newTip.sha)
expect(changedFiles.length).toEqual(1)
expect(changedFiles[0].path).toEqual(deletedFile)
const changesetData = await getChangedFiles(repository, newTip.sha)
expect(changesetData.files.length).toEqual(1)
expect(changesetData.files[0].path).toEqual(deletedFile)
// verify that changes remain for this new file
const status = await getStatusOrThrow(repository)
@ -838,10 +838,12 @@ describe('git/commit', () => {
expect(beforeCommit.currentTip).not.toBe(afterCommit.currentTip)
// Verify the file was delete in repo
const changedFiles = await getChangedFiles(repo, afterCommit.currentTip!)
expect(changedFiles.length).toBe(2)
expect(changedFiles[0].status.kind).toBe(AppFileStatusKind.Modified)
expect(changedFiles[1].status.kind).toBe(AppFileStatusKind.Deleted)
const changesetData = await getChangedFiles(repo, afterCommit.currentTip!)
expect(changesetData.files.length).toBe(2)
expect(changesetData.files[0].status.kind).toBe(
AppFileStatusKind.Modified
)
expect(changesetData.files[1].status.kind).toBe(AppFileStatusKind.Deleted)
})
})
})

View file

@ -61,13 +61,13 @@ describe('git/log', () => {
describe('getChangedFiles', () => {
it('loads the files changed in the commit', async () => {
const files = await getChangedFiles(
const changesetData = await getChangedFiles(
repository,
'7cd6640e5b6ca8dbfd0b33d0281ebe702127079c'
)
expect(files).toHaveLength(1)
expect(files[0].path).toBe('README.md')
expect(files[0].status.kind).toBe(AppFileStatusKind.New)
expect(changesetData.files).toHaveLength(1)
expect(changesetData.files[0].path).toBe('README.md')
expect(changesetData.files[0].status.kind).toBe(AppFileStatusKind.New)
})
it('detects renames', async () => {
@ -77,19 +77,19 @@ describe('git/log', () => {
repository = new Repository(testRepoPath, -1, null, false)
const first = await getChangedFiles(repository, '55bdecb')
expect(first).toHaveLength(1)
expect(first.files).toHaveLength(1)
expect(first[0].path).toBe('NEWER.md')
expect(first[0].status).toEqual({
expect(first.files[0].path).toBe('NEWER.md')
expect(first.files[0].status).toEqual({
kind: AppFileStatusKind.Renamed,
oldPath: 'NEW.md',
})
const second = await getChangedFiles(repository, 'c898ca8')
expect(second).toHaveLength(1)
expect(second.files).toHaveLength(1)
expect(second[0].path).toBe('NEW.md')
expect(second[0].status).toEqual({
expect(second.files[0].path).toBe('NEW.md')
expect(second.files[0].status).toEqual({
kind: AppFileStatusKind.Renamed,
oldPath: 'OLD.md',
})
@ -104,27 +104,29 @@ describe('git/log', () => {
// ensure the test repository is configured to detect copies
await setupLocalConfig(repository, [['diff.renames', 'copies']])
const files = await getChangedFiles(repository, 'a500bf415')
expect(files).toHaveLength(2)
const changesetData = await getChangedFiles(repository, 'a500bf415')
expect(changesetData.files).toHaveLength(2)
expect(files[0].path).toBe('duplicate-with-edits.md')
expect(files[0].status).toEqual({
expect(changesetData.files[0].path).toBe('duplicate-with-edits.md')
expect(changesetData.files[0].status).toEqual({
kind: AppFileStatusKind.Copied,
oldPath: 'initial.md',
})
expect(files[1].path).toBe('duplicate.md')
expect(files[1].status).toEqual({
expect(changesetData.files[1].path).toBe('duplicate.md')
expect(changesetData.files[1].status).toEqual({
kind: AppFileStatusKind.Copied,
oldPath: 'initial.md',
})
})
it('handles commit when HEAD exists on disk', async () => {
const files = await getChangedFiles(repository, 'HEAD')
expect(files).toHaveLength(1)
expect(files[0].path).toBe('README.md')
expect(files[0].status.kind).toBe(AppFileStatusKind.Modified)
const changesetData = await getChangedFiles(repository, 'HEAD')
expect(changesetData.files).toHaveLength(1)
expect(changesetData.files[0].path).toBe('README.md')
expect(changesetData.files[0].status.kind).toBe(
AppFileStatusKind.Modified
)
})
})
})

View file

@ -290,10 +290,12 @@ describe('git/rebase', () => {
status = await getStatusOrThrow(repository)
filesInRebasedCommit = await getChangedFiles(
const changesetData = await getChangedFiles(
repository,
status.currentTip!
)
filesInRebasedCommit = changesetData.files
})
it('returns success', () => {

View file

@ -47,8 +47,13 @@ describe('git/cherry-pick', () => {
expect(log.length).toBe(2)
// verify squashed commit contains changes from squashed commits
const squashedFiles = await getChangedFiles(repository, squashed.sha)
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
const squashedChangesetData = await getChangedFiles(
repository,
squashed.sha
)
const squashedFilePaths = squashedChangesetData.files
.map(f => f.path)
.join(' ')
expect(squashedFilePaths).toContain('first.md')
expect(squashedFilePaths).toContain('second.md')
})
@ -91,8 +96,13 @@ describe('git/cherry-pick', () => {
expect(log.length).toBe(2)
// verify squashed commit contains changes from squashed commits
const squashedFiles = await getChangedFiles(repository, squashed.sha)
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
const squashedChangesetData = await getChangedFiles(
repository,
squashed.sha
)
const squashedFilePaths = squashedChangesetData.files
.map(f => f.path)
.join(' ')
expect(squashedFilePaths).toContain('first.md')
expect(squashedFilePaths).toContain('second.md')
expect(squashedFilePaths).toContain('third.md')
@ -123,8 +133,13 @@ describe('git/cherry-pick', () => {
expect(log.length).toBe(1)
// verify squashed commit contains changes from squashed commits
const squashedFiles = await getChangedFiles(repository, squashed.sha)
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
const squashedChangesetData = await getChangedFiles(
repository,
squashed.sha
)
const squashedFilePaths = squashedChangesetData.files
.map(f => f.path)
.join(' ')
expect(squashedFilePaths).toContain('initialize')
expect(squashedFilePaths).toContain('first.md')
expect(squashedFilePaths).toContain('second.md')
@ -169,8 +184,13 @@ describe('git/cherry-pick', () => {
expect(log.length).toBe(4)
// verify squashed commit contains changes from squashed commits
const squashedFiles = await getChangedFiles(repository, squashed.sha)
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
const squashedChangesetData = await getChangedFiles(
repository,
squashed.sha
)
const squashedFilePaths = squashedChangesetData.files
.map(f => f.path)
.join(' ')
expect(squashedFilePaths).toContain('first.md')
expect(squashedFilePaths).toContain('third.md')
expect(squashedFilePaths).toContain('fifth.md')
@ -257,8 +277,13 @@ describe('git/cherry-pick', () => {
expect(squashed.body).toBe('Test Body\n')
// verify squashed commit contains changes from squashed commits
const squashedFiles = await getChangedFiles(repository, squashed.sha)
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
const squashedChangesetData = await getChangedFiles(
repository,
squashed.sha
)
const squashedFilePaths = squashedChangesetData.files
.map(f => f.path)
.join(' ')
expect(squashedFilePaths).toContain('first.md')
expect(squashedFilePaths).toContain('second.md')
})

View file

@ -1,4 +1,5 @@
import { OcticonSymbol, iconForRepository } from '../../src/ui/octicons'
import { iconForRepository } from '../../src/ui/octicons'
import * as OcticonSymbol from '../../src/ui/octicons/octicons.generated'
import { CloningRepository } from '../../src/models/cloning-repository'
import { Repository } from '../../src/models/repository'
import { gitHubRepoFixture } from '../helpers/github-repo-builder'

Some files were not shown because too many files have changed in this diff Show more