Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d503504
Include a fixture repository with no commits
smashwilson Feb 17, 2017
6b10aed
Test helper to copy rather than clone a fixture repository
smashwilson Feb 17, 2017
10b5a39
Use `symbolic-ref` instead of `rev-parse` to fetch the current branch
smashwilson Feb 17, 2017
41cfceb
`getHeadCommit()` notes when HEAD is an unborn ref
smashwilson Feb 17, 2017
2102d62
Conceal the "amend" checkbox on an unborn ref
smashwilson Feb 17, 2017
04f961f
lastCommit is a required prop for CommitView
smashwilson Feb 17, 2017
bae98de
:shirt:
smashwilson Feb 17, 2017
59ee37a
CommitViewController tests need to pass a lastCommit
smashwilson Feb 17, 2017
c80fa8e
Use realpaths for copied repositories
smashwilson Feb 17, 2017
9929eee
Merge branch 'master' of github.com:atom/github into aw-fear-of-commi…
smashwilson Feb 24, 2017
5d052bc
Initialize repositories in-place
smashwilson Feb 24, 2017
cd2f92d
Pass a TooltipManager in GitControllerTest
smashwilson Feb 24, 2017
b7d692d
Handle a detached HEAD in getCurrentBranch()
smashwilson Feb 24, 2017
bfefe93
Return a flag when HEAD is detached so we can change the display somehow
smashwilson Feb 24, 2017
d8db1ce
Adapt to the object returned by `getCurrentBranch()`
smashwilson Feb 24, 2017
4df926f
Style the BranchView differently when on a detached branch
smashwilson Feb 24, 2017
9ecc9e9
Use `for-each-ref` instead of parsing `branch` to enumerate branches
smashwilson Feb 27, 2017
30d49c7
Include a detached HEAD description disabled in the BranchMenuView
smashwilson Feb 27, 2017
206e004
Disable the push and pull buttons when detached
smashwilson Feb 27, 2017
fd88cd6
Custom error message for detached HEAD
smashwilson Feb 27, 2017
6098e1f
Unit tests for PushPullMenu detached HEAD cases
smashwilson Feb 27, 2017
1c7d131
Merge branch 'master' of github.com:atom/github into aw-fear-of-commi…
smashwilson Feb 28, 2017
9152afd
Extract and use common BranchPropType
smashwilson Feb 28, 2017
ac635ca
`query.branchName` isn't a thing
smashwilson Feb 28, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/controllers/git-tab-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default class GitTabController {
lastCommit: repository.getLastCommit(),
isMerging: repository.isMerging(),
isRebasing: repository.isRebasing(),
branchName: repository.getCurrentBranch(),
currentBranch: repository.getCurrentBranch(),
branches: repository.getBranches(),
remoteName: repository.getRemoteForBranch(this.branchName),
unstagedChanges: repository.getUnstagedChanges(),
Expand Down
20 changes: 12 additions & 8 deletions lib/controllers/github-tab-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ import RemotePrController from './remote-pr-controller';
import Repository from '../models/repository';
import GithubLoginModel from '../models/github-login-model';
import ObserveModel from '../decorators/observe-model';
import {RemotePropType} from '../prop-types';
import {RemotePropType, BranchPropType} from '../prop-types';

class RemoteSelector extends React.Component {
static propTypes = {
remotes: React.PropTypes.arrayOf(RemotePropType).isRequired,
currentBranch: React.PropTypes.string.isRequired,
currentBranchName: React.PropTypes.string.isRequired,
selectRemote: React.PropTypes.func.isRequired,
}

render() {
const {remotes, currentBranch, selectRemote} = this.props;
const {remotes, currentBranchName, selectRemote} = this.props;
return (
<div className="github-RemoteSelector">
<p>
This repository has multiple remotes hosted at GitHub.com.
Select a remote to see pull requests associated
with the <strong>{currentBranch}</strong> branch.
with the <strong>{currentBranchName}</strong> branch.
</p>
<ul>
{remotes.map(remote => (
Expand Down Expand Up @@ -55,13 +55,13 @@ export default class GithubTabController extends React.Component {
static propTypes = {
repository: React.PropTypes.object,
remotes: React.PropTypes.arrayOf(RemotePropType.isRequired),
currentBranch: React.PropTypes.string,
currentBranch: BranchPropType,
selectedRemote: React.PropTypes.string,
}

static defaultProps = {
remotes: null,
currentBranch: '',
currentBranch: {name: '', isDetached: false},
selectedRemote: null,
}

Expand All @@ -75,6 +75,10 @@ export default class GithubTabController extends React.Component {
return null;
}

if (!this.currentBranch || this.currentBranch.isDetached) {
return null;
}

let remote = this.props.remotes.find(r => r.name === this.props.selectedRemote);
let remotesAvailable = false;
if (!remote && this.props.remotes.length === 1) {
Expand All @@ -93,13 +97,13 @@ export default class GithubTabController extends React.Component {
endpoint="https://api.github.com"
loginModel={this.loginModel}
remote={remote}
currentBranch={this.props.currentBranch}
currentBranchName={this.props.currentBranch.name}
/>
}
{!remote && remotesAvailable &&
<RemoteSelector
remotes={this.props.remotes}
currentBranch={this.props.currentBranch}
currentBranchName={this.props.currentBranch.name}
selectRemote={this.handleRemoteSelect}
/>
}
Expand Down
4 changes: 2 additions & 2 deletions lib/controllers/pr-info-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default class PrInfoController extends React.Component {
endpoint: React.PropTypes.string.isRequired,
token: React.PropTypes.string.isRequired,
instance: React.PropTypes.string.isRequired,
currentBranch: React.PropTypes.string.isRequired,
currentBranchName: React.PropTypes.string.isRequired,
loginModel: React.PropTypes.object.isRequired,
onLogin: React.PropTypes.func.isRequired,
remote: RemotePropType.isRequired,
Expand All @@ -65,7 +65,7 @@ export default class PrInfoController extends React.Component {
const route = new PrInfoRoute({
repoOwner: this.props.remote.info.owner,
repoName: this.props.remote.info.name,
branchName: this.props.currentBranch,
branchName: this.props.currentBranchName,
});

const environment = getEnvironmentForInstance(instance, endpoint, token);
Expand Down
6 changes: 3 additions & 3 deletions lib/controllers/remote-pr-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class RemotePrController extends React.Component {
endpoint: React.PropTypes.string, // fully qualified URI to the API endpoint, e.g. 'https://api.github.com'
remote: RemotePropType.isRequired,
token: React.PropTypes.string,
currentBranch: React.PropTypes.string.isRequired,
currentBranchName: React.PropTypes.string.isRequired,
}

static defaultProps = {
Expand All @@ -32,11 +32,11 @@ export default class RemotePrController extends React.Component {
}

render() {
const {instance, endpoint, remote, currentBranch, token, loginModel} = this.props;
const {instance, endpoint, remote, currentBranchName, token, loginModel} = this.props;
return (
<div className="github-RemotePrController">
{token && <PrInfoController
{...{instance, endpoint, remote, currentBranch, token, loginModel}}
{...{instance, endpoint, remote, currentBranchName, token, loginModel}}
onLogin={this.handleLogin}
/>
}
Expand Down
21 changes: 12 additions & 9 deletions lib/controllers/status-bar-tile-controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';

import ObserveModel from '../decorators/observe-model';
import {BranchPropType} from '../prop-types';
import BranchView from '../views/branch-view';
import BranchMenuView from '../views/branch-menu-view';
import PushPullView from '../views/push-pull-view';
Expand All @@ -15,7 +16,7 @@ import {autobind} from 'core-decorators';
getModel: props => props.repository,
fetchData: repository => {
return yubikiri({
branchName: repository.getCurrentBranch(),
currentBranch: repository.getCurrentBranch(),
branches: repository.getBranches(),
changedFilesCount: repository.getStatusesForChangedFiles().then(statuses => {
const {stagedFiles, unstagedFiles, mergeConflictFiles} = statuses;
Expand All @@ -33,9 +34,9 @@ import {autobind} from 'core-decorators';

return changedFiles.size;
}),
remoteName: async query => repository.getRemoteForBranch(await query.branchName),
aheadCount: async query => repository.getAheadCount(await query.branchName),
behindCount: async query => repository.getBehindCount(await query.branchName),
remoteName: async query => repository.getRemoteForBranch((await query.currentBranch).name),
aheadCount: async query => repository.getAheadCount((await query.currentBranch).name),
behindCount: async query => repository.getBehindCount((await query.currentBranch).name),
pullDisabled: async query => (await query.changedFilesCount) > 0,
});
},
Expand All @@ -47,7 +48,7 @@ export default class StatusBarTileController extends React.Component {
commandRegistry: React.PropTypes.object.isRequired,
tooltips: React.PropTypes.object.isRequired,
repository: React.PropTypes.object,
branchName: React.PropTypes.string,
currentBranch: BranchPropType,
branches: React.PropTypes.arrayOf(React.PropTypes.string),
remoteName: React.PropTypes.string,
aheadCount: React.PropTypes.number,
Expand All @@ -73,7 +74,7 @@ export default class StatusBarTileController extends React.Component {

render() {
const repoProps = {
branchName: this.props.branchName,
currentBranch: this.props.currentBranch || {name: '', isDetached: false},
branches: this.props.branches,
remoteName: this.props.remoteName,
aheadCount: this.props.aheadCount,
Expand Down Expand Up @@ -179,7 +180,9 @@ export default class StatusBarTileController extends React.Component {

@autobind
async push(options) {
await this.setInProgressWhile(() => this.props.repository.push(this.props.branchName, options), {push: true});
await this.setInProgressWhile(() => {
return this.props.repository.push(this.props.currentBranch.name, options);
}, {push: true});
}

@autobind
Expand All @@ -188,11 +191,11 @@ export default class StatusBarTileController extends React.Component {
return;
}

await this.setInProgressWhile(() => this.props.repository.pull(this.props.branchName), {pull: true});
await this.setInProgressWhile(() => this.props.repository.pull(this.props.currentBranch.name), {pull: true});
}

@autobind
async fetch() {
await this.setInProgressWhile(() => this.props.repository.fetch(this.props.branchName), {fetch: true});
await this.setInProgressWhile(() => this.props.repository.fetch(this.props.currentBranch.name), {fetch: true});
}
}
37 changes: 29 additions & 8 deletions lib/git-shell-out-strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,21 @@ export default class GitShellOutStrategy {
async getCommit(ref) {
const output = await this.exec(['log', '--pretty=%H%x00%B%x00', '--no-abbrev-commit', '-1', ref]);
const [sha, message] = (output).split('\0');
return {sha, message: message.trim()};
return {sha, message: message.trim(), unbornRef: false};
}

getHeadCommit() {
return this.getCommit('HEAD');
async getHeadCommit() {
try {
const commit = await this.getCommit('HEAD');
commit.unbornRef = false;
return commit;
} catch (e) {
if (/unknown revision/.test(e.stdErr)) {
return {sha: '', message: '', unbornRef: true};
} else {
throw e;
}
}
}

readFileFromIndex(filePath) {
Expand Down Expand Up @@ -421,8 +431,20 @@ export default class GitShellOutStrategy {
* Branches
*/
async getCurrentBranch() {
const output = await this.exec(['rev-parse', '--abbrev-ref', 'HEAD']);
return output.trim();
const content = await readFile(path.join(this.workingDir, '.git', 'HEAD'));
if (content.startsWith('ref: ')) {
// The common case: you're on a branch
return {
name: (await this.exec(['symbolic-ref', '--short', 'HEAD'])).trim(),
isDetached: false,
};
} else {
// Detached HEAD
return {
name: (await this.exec(['describe', '--contains', '--all', 'HEAD'])).trim(),
isDetached: true,
};
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☝️ This bit is done consistently with the way that git-prompt.sh decorates your PS1. A bit messy but it should be fairly robust?

}

checkout(branchName, options = {}) {
Expand Down Expand Up @@ -454,9 +476,8 @@ export default class GitShellOutStrategy {
}

async getBranches() {
const output = await this.exec(['branch']);
return output.trim().split(LINE_ENDING_REGEX)
.map(branchName => branchName.trim().replace(/^\* /, ''));
const output = await this.exec(['for-each-ref', '--format=%(refname:short)', 'refs/heads/*']);
return output.trim().split(LINE_ENDING_REGEX);
}

async getConfig(option, {local} = {}) {
Expand Down
5 changes: 5 additions & 0 deletions lib/prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ export const RemotePropType = PropTypes.shape({
name: PropTypes.string,
}).isRequired,
});

export const BranchPropType = PropTypes.shape({
name: PropTypes.string.isRequired,
isDetached: PropTypes.bool.isRequired,
});
14 changes: 11 additions & 3 deletions lib/views/branch-menu-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ import {autobind} from 'core-decorators';

import Commands, {Command} from './commands';
import {GitError} from '../git-shell-out-strategy';
import {BranchPropType} from '../prop-types';

export default class BranchMenuView extends React.Component {
static propTypes = {
workspace: React.PropTypes.object.isRequired,
commandRegistry: React.PropTypes.object.isRequired,
notificationManager: React.PropTypes.object.isRequired,
branches: React.PropTypes.arrayOf(React.PropTypes.string),
branchName: React.PropTypes.string,
currentBranch: BranchPropType,
checkout: React.PropTypes.func,
}

static defaultProps = {
branches: [],
currentBranch: {
name: '',
isDetached: true,
},
checkout: () => Promise.resolve(),
}

Expand All @@ -29,7 +34,7 @@ export default class BranchMenuView extends React.Component {
}

render() {
const currentBranch = this.state.checkingOutBranch || this.props.branchName;
const currentBranchName = this.state.checkingOutBranch || this.props.currentBranch.name;
const branches = this.props.branches;
if (this.state.checkingOutBranch && this.props.branches.indexOf(this.state.checkingOutBranch) === -1) {
branches.push(this.state.checkingOutBranch);
Expand All @@ -53,7 +58,10 @@ export default class BranchMenuView extends React.Component {
<select
className="github-BranchMenuView-item github-BranchMenuView-select input-select"
onChange={this.didSelectItem}
value={currentBranch}>
value={currentBranchName}>
{this.props.currentBranch.isDetached &&
<option key="detached" value="detached" disabled>{this.props.currentBranch.name}</option>
}
{branches.map(branch => {
return <option key={branch} value={branch}>{branch}</option>;
})}
Expand Down
13 changes: 10 additions & 3 deletions lib/views/branch-view.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import React from 'react';
import cx from 'classnames';

import {BranchPropType} from '../prop-types';

export default class BranchView extends React.Component {
static propTypes = {
branchName: React.PropTypes.string,
currentBranch: BranchPropType,
}

render() {
const classNames = cx(
'github-branch', 'inline-block', {'github-branch-detached': this.props.currentBranch.isDetached},
);

return (
<div className="github-branch inline-block" ref={e => { this.element = e; }}>
<div className={classNames} ref={e => { this.element = e; }}>
<span className="icon icon-git-branch" />
<span className="branch-label">{this.props.branchName}</span>
<span className="branch-label">{this.props.currentBranch.name}</span>
</div>
);
}
Expand Down
31 changes: 19 additions & 12 deletions lib/views/commit-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export default class CommitView {
} else if (this.getRemainingCharacters() < this.props.maximumCharacterLimit / 4) {
remainingCharsClassName = 'is-warning';
}

const showAbortMergeButton = this.props.isMerging || null;
const showAmendBox = (!this.props.isMerging && !this.props.lastCommit.unbornRef) || null;

return (
<div className="github-CommitView" ref="CommitView">
<div className="github-CommitView-editor">
Expand All @@ -61,18 +65,21 @@ export default class CommitView {
/>
</div>
<footer className="github-CommitView-bar">
<button ref="abortMergeButton" className="btn github-CommitView-button is-secondary"
onclick={this.abortMerge}
style={{display: this.props.isMerging ? '' : 'none'}}>Abort Merge</button>
<label className="github-CommitView-label input-label" style={{display: this.props.isMerging ? 'none' : ''}}>
<input
ref="amend"
className="input-checkbox"
type="checkbox"
onclick={this.handleAmendBoxClick}
checked={this.props.isAmending}
/> Amend
</label>
{showAbortMergeButton &&
<button ref="abortMergeButton" className="btn github-CommitView-button is-secondary"
onclick={this.abortMerge}>Abort Merge</button>
}
{showAmendBox &&
<label className="github-CommitView-label input-label">
<input
ref="amend"
className="input-checkbox"
type="checkbox"
onclick={this.handleAmendBoxClick}
checked={this.props.isAmending}
/> Amend
</label>
}
<button ref="commitButton" className="btn github-CommitView-button"
onclick={this.commit}
disabled={!this.isCommitButtonEnabled()}>{this.commitButtonText()}</button>
Expand Down
Loading