Fix conflicts and address feedback

This commit is contained in:
iAmWillShepherd 2018-02-28 10:59:09 -06:00
parent a3bc4967cb
commit cde2ae8bf0
30 changed files with 510 additions and 235 deletions

View file

@ -151,7 +151,7 @@ pull requests.
| `enhancement` | [search](https://github.com/desktop/desktop/labels/enhancement) | Feature requests. |
| `bug` | [search](https://github.com/desktop/desktop/labels/bug) | Confirmed bugs or reports that are very likely to be bugs. |
| `more-information-needed` | [search](https://github.com/desktop/desktop/labels/more-information-needed) | More information needs to be collected about these problems or feature requests (e.g. steps to reproduce). |
| `needs-reproduction` | [search](https://github.com/desktop/desktop/labels/needs-reproduction) | Likely bugs, but haven't been reliably reproduced. |
| `reviewer-needs-to-reproduce` | [search](https://github.com/desktop/desktop/labels/reviewer-needs-to-reproduce) | Likely bugs, but haven't been reliably reproduced by a reviewer. |
| `stale` | [search](https://github.com/desktop/desktop/labels/stale) | Issues that are inactive and marked to be closed. |
| `macOS` | [search](https://github.com/desktop/desktop/labels/macOS) | Issues specific to macOS users. |
| `Windows` | [search](https://github.com/desktop/desktop/labels/Windows) | Issues specific to Windows users. |

13
.github/config.yml vendored
View file

@ -2,12 +2,17 @@
# *Required* Comment to reply with
requestInfoReplyComment: >
We require the template to be filled out on all new issues. We do this so that we can be certain we have
all the information we need to address your submission efficiently. This allows the maintainers to spend
more time fixing bugs, implementing enhancements, and reviewing and merging pull requests.
Thanks for reaching out!
We require the [template](https://github.com/desktop/desktop/blob/master/.github/CONTRIBUTING.md#how-do-i-submit-a-good-bug-report) to be filled out with all new issues. We do this so that
we can be certain we have all the information we need to address your
submission efficiently. This allows the maintainers to spend more time fixing
bugs, implementing enhancements, and reviewing and merging pull requests.
Thanks for understanding and meeting us halfway 😀
requestInfoLabelToAdd: more-information-needed
requestInfoOn:
pullRequest: false
issue: true
issue: true

View file

@ -48,13 +48,16 @@ First, please search the [open issues](https://github.com/desktop/desktop/issues
and [closed issues](https://github.com/desktop/desktop/issues?q=is%3Aclosed)
to see if your issue hasn't already been reported (it may also be fixed).
There is also a list of [known issues](https://github.com/desktop/desktop/blob/master/docs/known-issues.md)
that are being tracked against Desktop, and some of these issues have workarounds.
If you can't find an issue that matches what you're seeing, open a [new issue](https://github.com/desktop/desktop/issues/new)
and fill out the template to provide us with enough information to investigate
further.
## How can I contribute to GitHub Desktop?
The [CONTRIBUTING.md](./CONTRIBUTING.md) document will help you get setup and
The [CONTRIBUTING.md](./.github/CONTRIBUTING.md) document will help you get setup and
familiar with the source. The [documentation](docs/) folder also contains more
resources relevant to the project.

View file

@ -3,7 +3,7 @@
"productName": "GitHub Desktop",
"bundleID": "com.github.GitHubClient",
"companyName": "GitHub, Inc.",
"version": "1.1.0-beta3",
"version": "1.1.1-beta1",
"main": "./main.js",
"repository": {
"type": "git",

View file

@ -1,4 +1,5 @@
import { spawn } from 'child_process'
import * as Path from 'path'
export function isGitOnPath(): Promise<boolean> {
// Modern versions of macOS ship with a Git shim that guides you through
@ -12,16 +13,19 @@ export function isGitOnPath(): Promise<boolean> {
// adapted from http://stackoverflow.com/a/34953561/1363815
return new Promise<boolean>((resolve, reject) => {
if (__WIN32__) {
const process = spawn('C:\\Windows\\System32\\where.exe', ['git'])
const windowsRoot = process.env.SystemRoot || 'C:\\Windows'
const wherePath = Path.join(windowsRoot, 'System32', 'where.exe')
process.on('error', error => {
const cp = spawn(wherePath, ['git'])
cp.on('error', error => {
log.warn('Unable to spawn where.exe', error)
resolve(false)
})
// `where` will return 0 when the executable
// is found under PATH, or 1 if it cannot be found
process.on('close', function(code) {
cp.on('close', function(code) {
resolve(code === 0)
})
return

View file

@ -1,7 +1,9 @@
import { spawn, ChildProcess } from 'child_process'
import * as Path from 'path'
import { assertNever } from '../fatal-error'
import { enumerateValues, HKEY, RegistryValueType } from 'registry-js'
import { pathExists } from '../file-system'
import { assertNever } from '../fatal-error'
import { IFoundShell } from './found-shell'
export enum Shell {
@ -43,75 +45,127 @@ export async function getAvailableShells(): Promise<
},
]
const powerShellPath = await findPowerShell()
if (powerShellPath != null) {
shells.push({
shell: Shell.PowerShell,
path: powerShellPath,
})
}
const hyperPath = await findHyper()
if (hyperPath != null) {
shells.push({
shell: Shell.Hyper,
path: hyperPath,
})
}
const gitBashPath = await findGitBash()
if (gitBashPath != null) {
shells.push({
shell: Shell.GitBash,
path: gitBashPath,
})
}
return shells
}
async function findPowerShell(): Promise<string | null> {
const powerShell = enumerateValues(
HKEY.HKEY_LOCAL_MACHINE,
'Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\PowerShell.exe'
)
if (powerShell.length > 0) {
const first = powerShell[0]
// NOTE:
// on Windows 7 these are both REG_SZ, which technically isn't supposed
// to contain unexpanded references to environment variables. But given
// it's also %SystemRoot% and we do the expanding here I think this is
// a fine workaround to do to support the maximum number of setups.
if (powerShell.length === 0) {
return null
}
if (
first.type === RegistryValueType.REG_EXPAND_SZ ||
first.type === RegistryValueType.REG_SZ
) {
const path = first.data.replace(
/^%SystemRoot%/i,
process.env.SystemRoot || 'C:\\Windows'
const first = powerShell[0]
// NOTE:
// on Windows 7 these are both REG_SZ, which technically isn't supposed
// to contain unexpanded references to environment variables. But given
// it's also %SystemRoot% and we do the expanding here I think this is
// a fine workaround to do to support the maximum number of setups.
if (
first.type === RegistryValueType.REG_EXPAND_SZ ||
first.type === RegistryValueType.REG_SZ
) {
const path = first.data.replace(
/^%SystemRoot%/i,
process.env.SystemRoot || 'C:\\Windows'
)
if (await pathExists(path)) {
return path
} else {
log.debug(
`[PowerShell] registry entry found but does not exist at '${path}'`
)
shells.push({
shell: Shell.PowerShell,
path,
})
}
}
return null
}
async function findHyper(): Promise<string | null> {
const hyper = enumerateValues(
HKEY.HKEY_CURRENT_USER,
'Software\\Classes\\Directory\\Background\\shell\\Hyper\\command'
)
if (hyper.length > 0) {
const first = hyper[0]
if (first.type === RegistryValueType.REG_SZ) {
// Registry key is structured as "{installationPath}\app-x.x.x\Hyper.exe" "%V"
// This regex is designed to get the path to the version-specific Hyper.
// commandPieces = ['"{installationPath}\app-x.x.x\Hyper.exe"', '"', '{installationPath}\app-x.x.x\Hyper.exe', ...]
const commandPieces = first.data.match(/(["'])(.*?)\1/)
const path = commandPieces
? commandPieces[2]
: process.env.LocalAppData.concat('\\hyper\\Hyper.exe') // fall back to the launcher in install root
shells.push({
shell: Shell.Hyper,
path: path,
})
if (hyper.length === 0) {
return null
}
const first = hyper[0]
if (first.type === RegistryValueType.REG_SZ) {
// Registry key is structured as "{installationPath}\app-x.x.x\Hyper.exe" "%V"
// This regex is designed to get the path to the version-specific Hyper.
// commandPieces = ['"{installationPath}\app-x.x.x\Hyper.exe"', '"', '{installationPath}\app-x.x.x\Hyper.exe', ...]
const commandPieces = first.data.match(/(["'])(.*?)\1/)
const path = commandPieces
? commandPieces[2]
: process.env.LocalAppData.concat('\\hyper\\Hyper.exe') // fall back to the launcher in install root
if (await pathExists(path)) {
return path
} else {
log.debug(`[Hyper] registry entry found but does not exist at '${path}'`)
}
}
const gitBash = enumerateValues(
return null
}
async function findGitBash(): Promise<string | null> {
const registryPath = enumerateValues(
HKEY.HKEY_LOCAL_MACHINE,
'SOFTWARE\\GitForWindows'
)
if (gitBash.length > 0) {
const installPathEntry = gitBash.find(e => e.name === 'InstallPath')
if (
installPathEntry &&
installPathEntry.type === RegistryValueType.REG_SZ
) {
shells.push({
shell: Shell.GitBash,
path: Path.join(installPathEntry.data, 'git-bash.exe'),
})
if (registryPath.length === 0) {
return null
}
const installPathEntry = registryPath.find(e => e.name === 'InstallPath')
if (installPathEntry && installPathEntry.type === RegistryValueType.REG_SZ) {
const path = Path.join(installPathEntry.data, 'git-bash.exe')
if (await pathExists(path)) {
return path
} else {
log.debug(
`[Git Bash] registry entry found but does not exist at '${path}'`
)
}
}
return shells
return null
}
export function launch(

View file

@ -535,9 +535,7 @@ export class App extends React.Component<IAppProps, IAppState> {
}
public componentDidMount() {
document.ondragover = document.ondrop = e => {
e.preventDefault()
}
document.ondragover = document.ondrop = e => e.preventDefault()
document.body.ondrop = e => {
if (this.state.currentPopup != null) {
@ -870,9 +868,7 @@ export class App extends React.Component<IAppProps, IAppState> {
)
}
private onPopupDismissed = () => {
this.props.dispatcher.closePopup()
}
private onPopupDismissed = () => this.props.dispatcher.closePopup()
private onSignInDialogDismissed = () => {
this.props.dispatcher.resetSignInState()
@ -889,9 +885,8 @@ export class App extends React.Component<IAppProps, IAppState> {
)
}
private onUpdateAvailableDismissed = () => {
private onUpdateAvailableDismissed = () =>
this.props.dispatcher.setUpdateBannerVisibility(false)
}
private currentPopupContent(): JSX.Element | null {
// Hide any dialogs while we're displaying an error
@ -1253,9 +1248,7 @@ export class App extends React.Component<IAppProps, IAppState> {
this.props.dispatcher.performRetry(retryAction)
}
private onCheckForUpdates = () => {
this.checkForUpdates(false)
}
private onCheckForUpdates = () => this.checkForUpdates(false)
private showAcknowledgements = () => {
this.props.dispatcher.showPopup({ type: PopupType.Acknowledgements })
@ -1286,9 +1279,7 @@ export class App extends React.Component<IAppProps, IAppState> {
return <FullScreenInfo windowState={this.state.windowState} />
}
private clearError = (error: Error) => {
this.props.dispatcher.clearError(error)
}
private clearError = (error: Error) => this.props.dispatcher.clearError(error)
private onConfirmDiscardChangesChanged = (value: boolean) => {
this.props.dispatcher.setConfirmDiscardChangesSetting(value)

View file

@ -24,6 +24,9 @@ interface IAutocompletingTextInputProps<ElementType> {
/** The current value of the input field. */
readonly value?: string
/** Disabled state for input field. */
readonly disabled?: boolean
/**
* Called when the user changes the value in the input field.
*/
@ -197,7 +200,7 @@ export abstract class AutocompletingTextInput<
const item = currentAutoCompletionState.items[row]
if (item) {
this.insertCompletion(item)
this.insertCompletion(item, 'mouseclick')
}
}
@ -231,16 +234,7 @@ export abstract class AutocompletingTextInput<
const item = items[row]
this.insertCompletion(item)
// This is pretty gross. Clicking on the list moves focus off the text area.
// Immediately moving focus back doesn't work. Gotta wait a runloop I guess?
window.setTimeout(() => {
const element = this.element
if (element) {
element.focus()
}
}, 0)
this.insertCompletion(item, 'mouseclick')
}
/**
@ -258,6 +252,7 @@ export abstract class AutocompletingTextInput<
onChange: this.onChange,
onKeyDown: this.onKeyDown,
onBlur: this.onBlur,
disabled: this.props.disabled,
}
return React.createElement<React.HTMLAttributes<ElementType>, ElementType>(
@ -302,7 +297,17 @@ export abstract class AutocompletingTextInput<
)
}
private insertCompletion(item: Object) {
private setCursorPosition(newCaretPosition: number) {
if (this.element == null) {
log.warn('Unable to set cursor position when element is null')
return
}
this.element.selectionStart = newCaretPosition
this.element.selectionEnd = newCaretPosition
}
private insertCompletion(item: Object, source: 'mouseclick' | 'keyboard') {
const element = this.element!
const autocompletionState = this.state.autocompletionState!
const originalText = element.value
@ -310,17 +315,32 @@ export abstract class AutocompletingTextInput<
const autoCompleteText = autocompletionState.provider.getCompletionText(
item
)
const textWithAutoCompleteText =
originalText.substr(0, range.start - 1) + autoCompleteText + ' '
const newText =
originalText.substr(0, range.start - 1) +
autoCompleteText +
originalText.substr(range.start + range.length) +
' '
textWithAutoCompleteText + originalText.substr(range.start + range.length)
element.value = newText
if (this.props.onValueChanged) {
this.props.onValueChanged(newText)
}
const newCaretPosition = textWithAutoCompleteText.length
if (source === 'mouseclick') {
// we only need to re-focus on the text input when the autocomplete overlay
// steals focus due to the user clicking on a selection in the autocomplete list
window.setTimeout(() => {
element.focus()
this.setCursorPosition(newCaretPosition)
}, 0)
} else {
this.setCursorPosition(newCaretPosition)
}
this.close()
}
@ -385,7 +405,7 @@ export abstract class AutocompletingTextInput<
if (item) {
event.preventDefault()
this.insertCompletion(item)
this.insertCompletion(item, 'keyboard')
}
} else if (event.key === 'Escape') {
this.close()

View file

@ -328,6 +328,7 @@ export class CommitMessage extends React.Component<
onAuthorsUpdated={this.onCoAuthorsUpdated}
authors={this.props.coAuthors}
autoCompleteProvider={autocompletionProvider}
disabled={this.props.isCommitting}
/>
)
}
@ -352,7 +353,9 @@ export class CommitMessage extends React.Component<
{
label: this.toggleCoAuthorsText,
action: this.onToggleCoAuthors,
enabled: this.props.repository.gitHubRepository !== null,
enabled:
this.props.repository.gitHubRepository !== null &&
!this.props.isCommitting,
},
]
@ -360,7 +363,7 @@ export class CommitMessage extends React.Component<
}
private onCoAuthorToggleButtonClick = (
e: React.MouseEvent<HTMLDivElement>
e: React.MouseEvent<HTMLButtonElement>
) => {
e.preventDefault()
this.onToggleCoAuthors()
@ -372,15 +375,15 @@ export class CommitMessage extends React.Component<
}
return (
<div
role="button"
<button
className="co-authors-toggle"
onClick={this.onCoAuthorToggleButtonClick}
tabIndex={-1}
aria-label={this.toggleCoAuthorsText}
disabled={this.props.isCommitting}
>
<Octicon symbol={addAuthorIcon} />
</div>
</button>
)
}
@ -436,7 +439,11 @@ export class CommitMessage extends React.Component<
return null
}
return <div className="action-bar">{this.renderCoAuthorToggleButton()}</div>
const className = classNames('action-bar', {
disabled: this.props.isCommitting,
})
return <div className={className}>{this.renderCoAuthorToggleButton()}</div>
}
public render() {
@ -471,6 +478,7 @@ export class CommitMessage extends React.Component<
value={this.state.summary}
onValueChanged={this.onSummaryChanged}
autocompletionProviders={this.props.autocompletionProviders}
disabled={this.props.isCommitting}
/>
</div>
@ -486,6 +494,7 @@ export class CommitMessage extends React.Component<
autocompletionProviders={this.props.autocompletionProviders}
ref={this.onDescriptionFieldRef}
onElementRef={this.onDescriptionTextAreaRef}
disabled={this.props.isCommitting}
/>
{this.renderActionBar()}
</FocusContainer>

View file

@ -253,7 +253,7 @@ export class CreateBranch extends React.Component<
label="Name"
value={this.state.proposedName}
autoFocus={true}
onChange={this.onBranchNameChange}
onValueChanged={this.onBranchNameChange}
/>
</Row>
@ -277,9 +277,8 @@ export class CreateBranch extends React.Component<
)
}
private onBranchNameChange = (event: React.FormEvent<HTMLInputElement>) => {
const str = event.currentTarget.value
this.updateBranchName(str)
private onBranchNameChange = (name: string) => {
this.updateBranchName(name)
}
private updateBranchName(name: string) {

View file

@ -83,14 +83,14 @@ export class AuthenticationForm extends React.Component<
label="Username or email address"
disabled={disabled}
autoFocus={true}
onChange={this.onUsernameChange}
onValueChanged={this.onUsernameChange}
/>
<TextBox
label="Password"
type="password"
disabled={disabled}
onChange={this.onPasswordChange}
onValueChanged={this.onPasswordChange}
/>
{this.renderError()}
@ -173,12 +173,12 @@ export class AuthenticationForm extends React.Component<
return <Errors>{error.message}</Errors>
}
private onUsernameChange = (event: React.FormEvent<HTMLInputElement>) => {
this.setState({ username: event.currentTarget.value })
private onUsernameChange = (username: string) => {
this.setState({ username })
}
private onPasswordChange = (event: React.FormEvent<HTMLInputElement>) => {
this.setState({ password: event.currentTarget.value })
private onPasswordChange = (password: string) => {
this.setState({ password })
}
private signInWithBrowser = (event?: React.MouseEvent<HTMLButtonElement>) => {

View file

@ -36,6 +36,12 @@ interface IAuthorInputProps {
* input field.
*/
readonly onAuthorsUpdated: (authors: ReadonlyArray<IAuthor>) => void
/**
* Whether or not the input should be read-only and styled as being
* disabled. When disabled the component will not accept focus.
*/
readonly disabled: boolean
}
/**
@ -488,6 +494,12 @@ export class AuthorInput extends React.Component<IAuthorInputProps, {}> {
}
public componentWillReceiveProps(nextProps: IAuthorInputProps) {
const cm = this.editor
if (!cm) {
return
}
// If the authors prop have changed from our internal representation
// we'll throw up our hands and reset the input to whatever we're
// given.
@ -495,12 +507,13 @@ export class AuthorInput extends React.Component<IAuthorInputProps, {}> {
nextProps.authors !== this.props.authors &&
!arrayEquals(this.authors, nextProps.authors)
) {
const cm = this.editor
if (cm) {
cm.operation(() => {
this.reset(cm, nextProps.authors)
})
}
cm.operation(() => {
this.reset(cm, nextProps.authors)
})
}
if (nextProps.disabled !== this.props.disabled) {
cm.setOption('readOnly', nextProps.disabled ? 'nocursor' : false)
}
}
@ -702,6 +715,7 @@ export class AuthorInput extends React.Component<IAuthorInputProps, {}> {
'Ctrl-Enter': false,
'Cmd-Enter': false,
},
readOnly: this.props.disabled ? 'nocursor' : false,
hintOptions: {
completeOnSingleClick: true,
completeSingle: false,
@ -811,7 +825,13 @@ export class AuthorInput extends React.Component<IAuthorInputProps, {}> {
}
public render() {
const className = classNames('author-input-component', this.props.className)
const className = classNames(
'author-input-component',
this.props.className,
{
disabled: this.props.disabled,
}
)
return <div className={className} ref={this.onContainerRef} />
}
}

View file

@ -100,14 +100,14 @@ export class ConfigureGitUser extends React.Component<
label="Name"
placeholder="Your Name"
value={this.state.name}
onChange={this.onNameChange}
onValueChanged={this.onNameChange}
/>
<TextBox
label="Email"
placeholder="your-email@example.com"
value={this.state.email}
onChange={this.onEmailChange}
onValueChanged={this.onEmailChange}
/>
<Row>
@ -131,16 +131,13 @@ export class ConfigureGitUser extends React.Component<
)
}
private onNameChange = (event: React.FormEvent<HTMLInputElement>) => {
private onNameChange = (name: string) => {
this.setState({
name: event.currentTarget.value,
email: this.state.email,
avatarURL: this.state.avatarURL,
name,
})
}
private onEmailChange = (event: React.FormEvent<HTMLInputElement>) => {
const email = event.currentTarget.value
private onEmailChange = (email: string) => {
const avatarURL = this.avatarURLForEmail(email)
this.setState({

View file

@ -165,12 +165,12 @@ export class FilterList<T extends IFilterListItem> extends React.Component<
<Row className="filter-field-row">
<TextBox
ref={this.filterTextBoxRef}
ref={this.onTextBoxRef}
type="search"
autoFocus={true}
placeholder="Filter"
className="filter-list-filter-field"
onChange={this.onFilterChanged}
onValueChanged={this.onFilterChanged}
onKeyDown={this.onKeyDown}
value={this.props.filterText}
disabled={this.props.disabled}
@ -184,7 +184,7 @@ export class FilterList<T extends IFilterListItem> extends React.Component<
)
}
private filterTextBoxRef = (component: TextBox | null) => {
private onTextBoxRef = (component: TextBox | null) => {
this.filterTextBox = component
}
@ -242,8 +242,7 @@ export class FilterList<T extends IFilterListItem> extends React.Component<
this.list = instance
}
private onFilterChanged = (event: React.FormEvent<HTMLInputElement>) => {
const text = event.currentTarget.value
private onFilterChanged = (text: string) => {
if (this.props.onFilterTextChanged) {
this.props.onFilterTextChanged(text)
}

View file

@ -25,9 +25,6 @@ interface ITextBoxProps {
/** Whether the input field is disabled. */
readonly disabled?: boolean
/** Called when the user changes the value in the input field. */
readonly onChange?: (event: React.FormEvent<HTMLInputElement>) => void
/**
* Called when the user changes the value in the input field.
*
@ -138,14 +135,8 @@ export class TextBox extends React.Component<ITextBoxProps, ITextBoxState> {
private onChange = (event: React.FormEvent<HTMLInputElement>) => {
const value = event.currentTarget.value
if (this.props.onChange) {
this.props.onChange(event)
}
const defaultPrevented = event.defaultPrevented
this.setState({ value }, () => {
if (this.props.onValueChanged && !defaultPrevented) {
if (this.props.onValueChanged) {
this.props.onValueChanged(value)
}
})

View file

@ -78,12 +78,12 @@ export class PublishRepository extends React.Component<
this.props.onSettingsChanged(newSettings)
}
private onNameChange = (event: React.FormEvent<HTMLInputElement>) => {
this.updateSettings({ name: event.currentTarget.value })
private onNameChange = (name: string) => {
this.updateSettings({ name })
}
private onDescriptionChange = (event: React.FormEvent<HTMLInputElement>) => {
this.updateSettings({ description: event.currentTarget.value })
private onDescriptionChange = (description: string) => {
this.updateSettings({ description })
}
private onPrivateChange = (event: React.FormEvent<HTMLInputElement>) => {
@ -146,7 +146,7 @@ export class PublishRepository extends React.Component<
label="Name"
value={this.props.settings.name}
autoFocus={true}
onChange={this.onNameChange}
onValueChanged={this.onNameChange}
/>
</Row>
@ -154,7 +154,7 @@ export class PublishRepository extends React.Component<
<TextBox
label="Description"
value={this.props.settings.description}
onChange={this.onDescriptionChange}
onValueChanged={this.onDescriptionChange}
/>
</Row>

View file

@ -47,7 +47,7 @@ export class RenameBranch extends React.Component<
label="Name"
autoFocus={true}
value={this.state.newName}
onChange={this.onNameChange}
onValueChanged={this.onNameChange}
onKeyDown={this.onKeyDown}
/>
</Row>
@ -75,8 +75,8 @@ export class RenameBranch extends React.Component<
}
}
private onNameChange = (event: React.FormEvent<HTMLInputElement>) => {
this.setState({ newName: event.currentTarget.value })
private onNameChange = (name: string) => {
this.setState({ newName: name })
}
private cancel = () => {

View file

@ -14,18 +14,23 @@
padding: 0 var(--spacing-half);
&:focus {
outline: none;
border-color: var(--focus-color);
box-shadow: 0 0 0 1px var(--text-field-focus-shadow-color);
@include textboxish-focus-styles;
}
}
// This is kept separate from textboxish because we need to be able
// to apply it conditionally when running on Windows, the disabled
// styles on macOS are fine.
@mixin textboxish-focus-styles {
outline: none;
border-color: var(--focus-color);
box-shadow: 0 0 0 1px var(--text-field-focus-shadow-color);
}
@mixin textboxish-disabled-styles {
background: var(--box-alt-background-color);
color: var(--text-secondary-color);
}
@mixin textboxish-disabled {
&:disabled {
background: var(--box-alt-background-color);
color: var(--text-secondary-color);
@include textboxish-disabled-styles;
}
}

View file

@ -1,9 +1,13 @@
@import '../mixins';
.author-input-component {
&.disabled .CodeMirror {
@include textboxish-disabled-styles;
}
.CodeMirror {
border: 1px solid var(--box-border-color);
border-radius: var(--border-radius);
font-size: var(--font-size);
font-family: var(--font-family-sans-serif);
@include textboxish;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top: 0;
@ -53,9 +57,7 @@
}
.CodeMirror-focused {
outline: none;
border-color: var(--focus-color);
box-shadow: 0 0 0 1px var(--text-field-focus-shadow-color);
@include textboxish-focus-styles;
border-top-width: 1px;
border-top-style: solid;
margin-top: -1px;

View file

@ -1,28 +1,16 @@
@import '../mixins';
.text-area-component {
display: flex;
flex-direction: column;
flex: 1;
textarea {
border: 1px solid var(--box-border-color);
border-radius: var(--border-radius);
color: currentColor;
font-size: var(--font-size);
font-family: var(--font-family-sans-serif);
padding: var(--spacing-half);
@include textboxish;
@include textboxish-disabled;
height: auto;
resize: none;
min-height: 100px;
display: flex;
flex-direction: row;
flex: 1;
&:focus {
outline: none;
border-color: var(--focus-color);
box-shadow: 0 0 0 1px var(--text-field-focus-shadow-color);
}
}
}

View file

@ -26,10 +26,6 @@
input {
@include textboxish;
// Don't override disabled styles on darwin for text boxes.
@include win32 {
@include textboxish-disabled;
}
@include textboxish-disabled;
}
}

View file

@ -119,7 +119,6 @@
.action-bar {
padding: var(--spacing-half);
display: inline-block;
// We're not faking being a textbox any more
cursor: default;
@ -129,9 +128,23 @@
&:empty {
display: none;
}
&.disabled {
background: var(--box-alt-background-color);
}
}
.co-authors-toggle {
// button reset styles
border: none;
-webkit-appearance: none;
background: transparent;
padding: 0;
margin: 0;
text-align: left;
align-items: center;
display: block;
height: 16px;
width: 16px;
cursor: pointer;
@ -148,6 +161,10 @@
pointer-events: none;
}
&:disabled {
pointer-events: none;
}
&:hover {
&:after {
opacity: 1;

View file

@ -1,5 +1,37 @@
{
"releases": {
"1.1.1-beta1": [
]
,"1.1.0": [
"[New] Check out pull requests from collaborators or forks from within Desktop",
"[New] View the commit status of the branch when it has an open pull request",
"[Added] Add RubyMine support for macOS - #3883. Thanks @gssbzn!",
"[Added] Add TextMate support for macOS - #3910. Thanks @caiofbpa!",
"[Added] Syntax highlighting for Elixir files - #3774. Thanks @joaovitoras!",
"[Fixed] Update layout of branch blankslate image - #4011",
"[Fixed] Expanded avatar stack in commit summary gets cut off - #3884",
"[Fixed] Clear repository filter when switching tabs - #3787. Thanks @reyronald!",
"[Fixed] Avoid crash when unable to launch shell - #3954",
"[Fixed] Ensure renames are detected when viewing commit diffs - #3673",
"[Fixed] Fetch default remote if it differs from the current - #4056",
"[Fixed] Handle Git errors when .gitmodules are malformed - #3912",
"[Fixed] Handle error when \"where\" is not on PATH - #3882 #3825",
"[Fixed] Ignore action assumes CRLF when core.autocrlf is unset - #3514",
"[Fixed] Prevent duplicate entries in co-author autocomplete list - #3887",
"[Fixed] Renames not detected when viewing commit diffs - #3673",
"[Fixed] Support legacy usernames as co-authors - #3897",
"[Improved] Update branch button text from \"New\" to \"New Branch\" - #4032",
"[Improved] Add fuzzy search in the repository, branch, PR, and clone FilterLists - #911. Thanks @j-f1!",
"[Improved] Tidy up commit summary and description layout in commit list - #3922. Thanks @willnode!",
"[Improved] Use smaller default size when rendering Gravatar avatars - #3911",
"[Improved] Show fetch progress when initializing remote for fork - #3953",
"[Improved] Remove references to Hubot from the user setup page - #4015. Thanks @j-f1!",
"[Improved] Error handling around ENOENT - #3954",
"[Improved] Clear repository filter text when switching tabs - #3787. Thanks @reyronald!",
"[Improved] Allow window to accept single click on focus - #3843",
"[Improved] Disable drag-and-drop interaction when a popup is in the foreground - #3996"
],
"1.1.0-beta3": [
"[Fixed] Fetch default remote if it differs from the current - #4056"
],

View file

@ -8,7 +8,7 @@ documentation.
If you are interested in contributing to the project, you should read these
resources to get familiar with how things work:
- **[How Can I Contribute?](../CONTRIBUTING.md#how-can-i-contribute)** -
- **[How Can I Contribute?](../.github/CONTRIBUTING.md#how-can-i-contribute)** -
details about how you can participate
- **[Development Environment Setup](contributing/setup.md)** - everything
you need to know to get Desktop up and running

View file

@ -182,7 +182,7 @@ require('devtron').install()
You're almost there! Here's a couple of things we recommend you read next:
- [Help Wanted](../../CONTRIBUTING.md#help-wanted) - we've marked some tasks in
- [Help Wanted](../../.github/CONTRIBUTING.md#help-wanted) - we've marked some tasks in
the backlog that are ideal for external contributors
- [Code Reviews](../process/reviews.md) - some notes on how the team does
code reviews

104
docs/known-issues.md Normal file
View file

@ -0,0 +1,104 @@
# Known Issues
This document outlines acknowledged issues with GitHub Desktop, including workarounds if known.
## What should I do if...
### I have encountered an issue listed here?
Some known issues have a workaround that users have reported addresses the issue. Please try the workaround for yourself to confirm it addresses the issue.
### I have additional questions about an issue listed here?
Each known issue links off to an existing GitHub issue. If you have additional questions or feedback, please comment on the issue.
### My issue is not listed here?
Please check the [open](https://github.com/desktop/desktop/labels/bug) and [closed](https://github.com/desktop/desktop/issues?q=is%3Aclosed+label%3Abug) bugs in the issue tracker for the details of your bug. If you can't find it, or if you're not sure, open a [new issue](https://github.com/desktop/desktop/issues/new).
## macOS
### 'The username or passphrase you entered is not correct' error after signing into account - [#3263](https://github.com/desktop/desktop/issues/3263)
This seems to be caused by the Keychain being in an invalid state, affecting applications that try to use the keychain to store or retrieve credentials. Seems to be specific to macOS High Sierra (10.13).
**Workaround:**
- Open `Keychain Access.app`
- Right-click on the `login` keychain and try locking it
- Right-click on the `login` keychain and try unlocking it
- Sign into your GitHub account again
## Windows
### Window is hidden after detaching secondary monitor - [#2107](https://github.com/desktop/desktop/issues/2107)
This is related to Desktop tracking the window position between launches, but not changes to your display configuration such as removing the secondary monitor where Desktop was positioned.
**Workaround:**
- Remove `%APPDATA%\GitHub Desktop\window-state.json`
- Restart Desktop
### Certificate revocation check fails - [#3326](https://github.com/desktop/desktop/issues/3326)
If you are using Desktop on a corporate network, you may encounter an error like this:
```
fatal: unable to access 'https://github.com/owner/name.git/': schannel: next InitializeSecurityContext failed: Unknown error (0x80092012) - The revocation function was unable to check revocation for the certificate.
```
GitHub Desktop by default uses the Windows Secure Channel (SChannel) APIs to validate the certificate received from a server. Some networks will block the attempts by Windows to check the revocation status of a certificate, which then causes the whole operation to error.
**Workaround:**
To use the classic OpenSSL behavior in Git, you'll need a PEM file containing certificates that are considered trusted. The [public list](https://curl.haxx.se/docs/caextract.html) provided by the curl project can be used if you are not connecting to a GitHub Enterprise instance which has it's own distinct certificates.
Once you've downloaded that PEM file somewhere, open a shell with Git and run these commands:
```shellsession
$ git config --global http.sslBackend "openssl"
$ git config --global http.sslCAInfo [path to .pem file]
```
### Using a repository configured with Folder Redirection - [#2972](https://github.com/desktop/desktop/issues/2972)
[Folder Redirection](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc753996(v%3dws.11)) is an feature of Windows for administrators to ensure files and folders are managed on a network server, instead.
**Not supported** as Git is not able to resolve the working directory correctly:
```shellsession
2017-09-21T23:16:05.933Z - error: [ui] `git -c credential.helper= lfs clone --recursive --progress --progress -- https://github.com/owner/name.git \\harvest\Redirected\andrewd\My Documents\GitHub\name` exited with an unexpected code: 2.
Cloning into '\\harvest\Redirected\andrewd\My Documents\GitHub\name'...
remote: Counting objects: 4, done.
remote: Compressing objects: 33% (1/3)
remote: Compressing objects: 66% (2/3)
remote: Compressing objects: 100% (3/3)
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 4 (delta 1), pack-reused 0
fatal: unable to get current working directory: No such file or directory
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry the checkout with 'git checkout -f HEAD'
Error(s) during clone:
git clone failed: exit status 128
```
### Enable Mandatory ASLR triggers cygheap errors - #3096
Windows 10 Fall Creators Edition (version 1709 or later) added enhancements to the Enhanced Mitigation Experience Toolkit, one being to enable Mandatory ASLR. This setting affects the embedded Git shipped in Desktop, and produces errors that look like this:
```
1 [main] sh (2072) C:\Users\bdorrans\AppData\Local\GitHubDesktop\app-1.0.4\resources\app\git\usr\bin\sh.exe: *** fatal error - cygheap base mismatch detected - 0x2E07408/0x2EC7408.
This problem is probably due to using incompatible versions of the cygwin DLL.
Search for cygwin1.dll using the Windows Start->Find/Search facility
and delete all but the most recent version. The most recent version *should*
reside in x:\cygwin\bin, where 'x' is the drive on which you have
installed the cygwin distribution. Rebooting is also suggested if you
are unable to find another cygwin DLL.
```
Enabling Mandatory ASLR affects the MSYS2 core library, which is relied upon by Git for Windows to emulate process forking.
**Not supported:** this is an upstream limitation of MSYS2, and it is recommend that you either disable Mandatory ASLR or whitelist all executables under `<Git>\usr\bin` which depend on MSYS2.

View file

@ -81,16 +81,16 @@ issues from time to time that isn't and won't be covered here.
1. Person files a new issue
1. Maintainer checks to ensure they adequately filled out the template. If not,
close with the [request to fill out the template](canned-messages/needs-template.md).
close with a request to fill out the template.
1. Label the issue as a `bug` if the issue is a regression or behaviour that
needs to be fixed.
1. If the issue has already been fixed, add a comment linking to the original
issue and close the issue.
1. If anything is unclear but the template is adequately filled out, post what
questions you have and label with `more-information-needed`
questions you have and label with `more-information-needed`.
1. Maintainer attempts to reproduce the problem
1. If the problem is not reproducible, label with `needs-reproduction` and
ask the author of the issue for [clarification on the repro steps](canned-messages/repro-steps.md)
ask the author of the issue for clarification on the repro steps.
1. Label the issue as an `enhancement` if the issue mentions new behaviour
or functionality that the app should have.
@ -101,7 +101,8 @@ issues from time to time that isn't and won't be covered here.
Periodically we should be doing a sweep of issues that are open and labeled
`more-information-needed`. If the original poster has not responded within
two weeks after the last question by an official maintainer, close the issue
with [the no response message](canned-messages/no-response.md).
with a message saying that you're closing the issue due to inactivity but are
happy to re-open if they can provide more details.
## Needs Reproduction

View file

@ -1,8 +1,8 @@
# Editor Integration
# "Open External Editor" integration
GitHub Desktop supports the user choosing an external program to open their
local repositories, and this is available from the main menu and right-clicking
on a repository in the sidebar.
local repositories, and this is available from the top-level **Repository** menu
or when right-clicking on a repository in the sidebar.
### My favourite editor XYZ isn't supported!
@ -25,8 +25,9 @@ The source for the editor integration on Windows is found in
These editors are currently supported:
- [Atom](https://atom.io/)
- [Visual Studio Code](https://code.visualstudio.com/)
- [Visual Studio Code](https://code.visualstudio.com/) - both stable and Insiders channel
- [Sublime Text](https://www.sublimetext.com/)
- [ColdFusion Builder](https://www.adobe.com/products/coldfusion-builder.html)
These are defined in an enum at the top of the file:
@ -34,7 +35,9 @@ These are defined in an enum at the top of the file:
export enum ExternalEditor {
Atom = 'Atom',
VisualStudioCode = 'Visual Studio Code',
VisualStudioCodeInsiders = 'Visual Studio Code (Insiders)',
SublimeText = 'Sublime Text',
CFBuilder = 'ColdFusion Builder',
}
```
@ -202,7 +205,7 @@ The source for the editor integration on macOS is found in
These editors are currently supported:
- [Atom](https://atom.io/)
- [Visual Studio Code](https://code.visualstudio.com/)
- [Visual Studio Code](https://code.visualstudio.com/) - both stable and Insiders channel
- [Sublime Text](https://www.sublimetext.com/)
- [BBEdit](http://www.barebones.com/products/bbedit/)
- [PhpStorm](https://www.jetbrains.com/phpstorm/)
@ -212,10 +215,16 @@ These editors are currently supported:
These are defined in an enum at the top of the file:
```ts
export enum ExternalEditor {
Atom = 'Atom',
VisualStudioCode = 'Visual Studio Code',
VisualStudioCodeInsiders = 'Visual Studio Code (Insiders)',
SublimeText = 'Sublime Text',
BBEdit = 'BBEdit',
PhpStorm = 'PhpStorm',
RubyMine = 'RubyMine',
TextMate = 'TextMate',
}
```
@ -297,7 +306,7 @@ The source for the editor integration on Linux is found in
These editors are currently supported:
- [Atom](https://atom.io/)
- [Visual Studio Code](https://code.visualstudio.com/)
- [Visual Studio Code](https://code.visualstudio.com/) - both stable and Insiders channel
- [Sublime Text](https://www.sublimetext.com/)
These are defined in an enum at the top of the file:
@ -306,6 +315,7 @@ These are defined in an enum at the top of the file:
export enum ExternalEditor {
Atom = 'Atom',
VisualStudioCode = 'Visual Studio Code',
VisualStudioCodeInsiders = 'Visual Studio Code (Insiders)',
SublimeText = 'Sublime Text',
}
```

View file

@ -1,4 +1,4 @@
# Shell Integration
# "Open Shell" integration
GitHub Desktop supports launching an available shell found on the user's
machine, to work with Git repositories outside of Desktop.
@ -46,22 +46,50 @@ use **Git Bash** as a reference for the rest of the process.
### Step 1: Find the shell executable
The `getAvailableShells()` method is used to find each shell, as some may not
be present on the user's machine. You will need to add some code in here.
be present on the user's machine.
For Git Bash we perform a couple of checks:
This is the example for how we resolve if Git Bash is installed.
```ts
const gitBash = await readRegistryKeySafe(
'HKEY_LOCAL_MACHINE\\SOFTWARE\\GitForWindows'
)
if (gitBash.length > 0) {
const installPathEntry = gitBash.find(e => e.name === 'InstallPath')
if (installPathEntry) {
const gitBashPath = await findGitBash()
if (gitBashPath != null) {
shells.push({
shell: Shell.GitBash,
path: Path.join(installPathEntry.value, 'git-bash.exe'),
path: gitBashPath,
})
}
```
You will need to add some code in here, following this pattern, to resolve a new shell.
The details of this check will vary based on the how the shell is installed,
but Git Bash is a good example of this:
```ts
async function findGitBash(): Promise<string | null> {
const registryPath = enumerateValues(
HKEY.HKEY_LOCAL_MACHINE,
'SOFTWARE\\GitForWindows'
)
if (registryPath.length === 0) {
return null
}
const installPathEntry = registryPath.find(e => e.name === 'InstallPath')
if (installPathEntry && installPathEntry.type === RegistryValueType.REG_SZ) {
const path = Path.join(installPathEntry.data, 'git-bash.exe')
if (await pathExists(path)) {
return path
} else {
log.debug(
`[Git Bash] registry entry found but does not exist at '${path}'`
)
}
}
return null
}
```
@ -75,14 +103,16 @@ This approximately reads as:
The `launch()` function defines the arguments to pass to the shell, and each
shell may require it's own set of command arguments. You will need to make
changes here.
changes here to handle a new shell.
```ts
} else if (shell === Shell.GitBash) {
await spawn(foundShell.path, [`--cd="${path}"`], {
shell: true,
cwd: path,
})
case Shell.GitBash:
const gitBashPath = `"${foundShell.path}"`
log.info(`launching ${shell} at path: ${gitBashPath}`)
return spawn(gitBashPath, [`--cd="${path}"`], {
shell: true,
cwd: path,
})
```
## macOS
@ -152,10 +182,13 @@ at the path requested by the user. You may not need to make changes here,
unless your shell behaviour differs significantly from this.
```ts
export async function launch(shell: Shell, path: string): Promise<void> {
const bundleID = getBundleID(shell)
export function launch(
foundShell: IFoundShell<Shell>,
path: string
): ChildProcess {
const bundleID = getBundleID(foundShell.shell)
const commandArgs = ['-b', bundleID, path]
await spawn('open', commandArgs)
return spawn('open', commandArgs)
}
```
@ -223,33 +256,28 @@ export async function getAvailableShells(): Promise<
### Step 2: Launch the shell
The `launch()` method will use the received `shell.shell` executable path and
the path requested by the user. You may need to make changes here, if your
shell has a different command line interface.
The `launch()` method will use the received `foundShell` executable path and
the path requested by the user. You will need to make changes here, to ensure
the correct arguments are passed to the command line interface:
```ts
export async function launch(
shell: IFoundShell<Shell>,
export function launch(
foundShell: IFoundShell<Shell>,
path: string
): Promise<void> {
if (shell.shell === Shell.Urxvt) {
const commandArgs = ['-cd', path]
await spawn(shell.path, commandArgs)
): ChildProcess {
const shell = foundShell.shell
switch (shell) {
case Shell.Urxvt:
return spawn(foundShell.path, ['-cd', path])
case Shell.Konsole:
return spawn(foundShell.path, ['--workdir', path])
case Shell.Xterm:
return spawn(foundShell.path, ['-e', '/bin/bash'], { cwd: path })
case Shell.Tilix:
case Shell.Gnome:
return spawn(foundShell.path, ['--working-directory', path])
default:
return assertNever(shell, `Unknown shell: ${shell}`)
}
if (shell.shell === Shell.Konsole) {
const commandArgs = ['--workdir', path]
await spawn(shell.path, commandArgs)
}
if (shell.shell === Shell.Xterm) {
const commandArgs = ['-e', '/bin/bash']
const commandOptions = { cwd: path }
await spawn(shell.path, commandArgs, commandOptions)
}
const commandArgs = ['--working-directory', path]
await spawn(shell.path, commandArgs)
}
```

View file

@ -16,7 +16,7 @@ Note, however that this list is likely to grow stale so I'd recommend checking [
### I want to add my favorite language
Cool! As long as it's a language that [CodeMirror supports out of the box](https://codemirror.net/mode/index.html) we should be able to make it work. Open an issue and we'll take it from there.
Cool! As long as it's a language that [CodeMirror supports out of the box](https://codemirror.net/mode/index.html) or it has a module that extends CodeMirror ([`codemirror-mode-elixir`](https://github.com/optick/codemirror-mode-elixir) is an example of this) we should be able to make it work. Open an issue and we'll take it from there.
If you want to create a PR and add highlighter support for your favourite programming language don't forget to:
1. Submit a PR with a sample file for the language to [desktop/highlighter-tests](https://github.com/desktop/highlighter-tests).