Skip to content
This repository was archived by the owner on May 21, 2019. It is now read-only.

Commit 769fd4a

Browse files
authored
Git branch plugin (#883)
* Re-implement git branch with no additions * Create + use button abstraction * Add checkout button to git branch * Fix types * Add reloading * Fix lint
1 parent 7c9f42e commit 769fd4a

File tree

5 files changed

+181
-38
lines changed

5 files changed

+181
-38
lines changed

src/plugins/GitBranch.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import * as React from "react";
2+
import {PluginManager} from "../PluginManager";
3+
import {isEqual} from "lodash";
4+
import {
5+
branches,
6+
Branch,
7+
repoRoot,
8+
GitDirectoryPath,
9+
isGitDirectory,
10+
} from "../utils/Git";
11+
import {colors, background} from "../views/css/colors";
12+
import {executeCommand} from "../PTY";
13+
import Button from "./autocompletion_utils/Button";
14+
import {failurize} from "../views/css/functions";
15+
16+
const errorMessageStyles = {
17+
whiteSpace: "pre",
18+
padding: "10px",
19+
background: failurize(background),
20+
};
21+
22+
interface GitBranchProps {
23+
branches: Branch[];
24+
repoRoot: GitDirectoryPath;
25+
};
26+
27+
interface GitBranchState {
28+
failReason: string | undefined;
29+
branches: Branch[];
30+
};
31+
32+
class GitBranchComponent extends React.Component<GitBranchProps, GitBranchState> {
33+
constructor(props: GitBranchProps) {
34+
super(props);
35+
36+
this.state = {
37+
failReason: undefined,
38+
branches: props.branches,
39+
};
40+
}
41+
42+
async reload() {
43+
const newBranches: Branch[] = await branches({
44+
directory: this.props.repoRoot,
45+
remotes: false,
46+
tags: false,
47+
});
48+
this.setState({
49+
branches: newBranches,
50+
failReason: undefined,
51+
});
52+
}
53+
54+
render(): any {
55+
return <div>
56+
{this.state.failReason ? <div style={errorMessageStyles}>{this.state.failReason}</div> : undefined}
57+
<div style={{ padding: "10px" }}>
58+
{this.state.branches.map((branch, index) =>
59+
<div key={index.toString()} style={branch.isCurrent() ? {color: colors.green} : {}}>
60+
<span style={{whiteSpace: "pre"}}>{branch.isCurrent() ? "* " : " "}{branch.toString()}</span>
61+
<Button onClick={async () => {
62+
try {
63+
await executeCommand("git", ["checkout", branch.toString()], this.props.repoRoot);
64+
this.reload();
65+
} catch (e) {
66+
this.setState({ failReason: e.message } as GitBranchState);
67+
}
68+
}}>Checkout</Button>
69+
</div>
70+
)}
71+
</div>
72+
</div>;
73+
}
74+
}
75+
76+
PluginManager.registerCommandInterceptorPlugin({
77+
intercept: async({ presentWorkingDirectory }): Promise<React.ReactElement<any>> => {
78+
if (await isGitDirectory(presentWorkingDirectory)) {
79+
const gitBranches: Branch[] = await branches({
80+
directory: presentWorkingDirectory as GitDirectoryPath,
81+
remotes: false,
82+
tags: false,
83+
});
84+
return <GitBranchComponent
85+
branches={gitBranches}
86+
repoRoot={await repoRoot(presentWorkingDirectory as GitDirectoryPath)}
87+
/>;
88+
} else {
89+
return <div style={{ padding: "10px" }}>fatal: Not a git repository (or any of the parent directories): .git</div>;
90+
}
91+
},
92+
93+
isApplicable: ({ command }): boolean => {
94+
return isEqual(command, ["git", "branch"]);
95+
},
96+
});

src/plugins/GitStatus.tsx

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,26 @@
11
import * as React from "react";
22
import {PluginManager} from "../PluginManager";
33
import {isEqual} from "lodash";
4-
import {status, FileStatus, branches, Branch, repoRoot} from "../utils/Git";
4+
import {
5+
status,
6+
FileStatus,
7+
branches,
8+
Branch,
9+
repoRoot,
10+
isGitDirectory,
11+
GitDirectoryPath,
12+
} from "../utils/Git";
513
import {colors} from "../views/css/colors";
614
import {executeCommand} from "../PTY";
715
import Link from "../utils/Link";
816
import {join} from "path";
17+
import Button from "./autocompletion_utils/Button";
918

1019
const gitStatusStyle = (color: string) => ({
1120
lineHeight: "18px",
1221
color: color,
1322
});
1423

15-
const buttonStyles = {
16-
borderColor: colors.blue,
17-
borderStyle: "solid",
18-
borderRadius: "4px",
19-
borderWidth: "1px",
20-
padding: "2px",
21-
color: colors.blue,
22-
WebkitUserSelect: "none",
23-
fontSize: "10px",
24-
margin: "4px",
25-
cursor: "pointer",
26-
};
27-
2824
interface GitStatusProps {
2925
currentBranch: Branch | undefined;
3026
gitStatus: FileStatus[];
@@ -61,13 +57,9 @@ const GitStatusFile: React.StatelessComponent<GitStatusFileProps> = ({
6157
{separator}
6258
<Link absolutePath={absolutePath}>{path}</Link>
6359
</span>
64-
{buttons.map(({buttonText, action}, index) => <span
65-
style={buttonStyles}
66-
onClick={action}
67-
key={index.toString()}
68-
>
69-
{buttonText}
70-
</span>)}
60+
{buttons.map(({buttonText, action}, index) =>
61+
<Button key={index.toString()} onClick={action}>{buttonText}</Button>
62+
)}
7163
</div>;
7264
};
7365

@@ -105,7 +97,11 @@ class GitStatusComponent extends React.Component<GitStatusProps, GitStatusState>
10597

10698
async reload() {
10799
const gitStatus = await status(this.props.repoRoot as any);
108-
const gitBranches: Branch[] = await branches(this.props.repoRoot as any);
100+
const gitBranches: Branch[] = await branches({
101+
directory: this.props.repoRoot as GitDirectoryPath,
102+
remotes: false,
103+
tags: false,
104+
});
109105
const currentBranch = gitBranches.find(branch => branch.isCurrent());
110106
this.setState({
111107
currentBranch,
@@ -477,15 +473,24 @@ class GitStatusComponent extends React.Component<GitStatusProps, GitStatusState>
477473

478474
PluginManager.registerCommandInterceptorPlugin({
479475
intercept: async({ presentWorkingDirectory }): Promise<React.ReactElement<any>> => {
480-
const gitBranches: Branch[] = await branches(presentWorkingDirectory as any);
481-
const currentBranch = gitBranches.find(branch => branch.isCurrent());
482-
const gitStatus = await status(presentWorkingDirectory as any);
483-
const root = await repoRoot(presentWorkingDirectory);
484-
return <GitStatusComponent
485-
currentBranch={currentBranch}
486-
gitStatus={gitStatus}
487-
repoRoot={root}
488-
/>;
476+
477+
if (await isGitDirectory(presentWorkingDirectory)) {
478+
const gitBranches: Branch[] = await branches({
479+
directory: presentWorkingDirectory as GitDirectoryPath,
480+
remotes: false,
481+
tags: false,
482+
});
483+
const currentBranch = gitBranches.find(branch => branch.isCurrent());
484+
const gitStatus = await status(presentWorkingDirectory as any);
485+
const root = await repoRoot(presentWorkingDirectory as any);
486+
return <GitStatusComponent
487+
currentBranch={currentBranch}
488+
gitStatus={gitStatus}
489+
repoRoot={root}
490+
/>;
491+
} else {
492+
return <div style={{ padding: "10px" }}>fatal: Not a git repository (or any of the parent directories): .git</div>;
493+
}
489494
},
490495

491496
isApplicable: ({ command }): boolean => {

src/plugins/autocompletion_providers/Git.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,13 @@ const configVariables = unique(async(context: AutocompletionContext): Promise<Su
263263

264264
const branchesExceptCurrent = async(context: AutocompletionContext): Promise<Suggestion[]> => {
265265
if (Git.isGitDirectory(context.environment.pwd)) {
266-
const branches = (await Git.branches(context.environment.pwd)).filter(branch => !branch.isCurrent());
267-
return branches.map(branch => new Suggestion({value: branch.toString(), style: styles.branch}));
266+
const allBranches = (await Git.branches({
267+
directory: context.environment.pwd,
268+
remotes: true,
269+
tags: false,
270+
}));
271+
const nonCurrentBranches = allBranches.filter(branch => !branch.isCurrent());
272+
return nonCurrentBranches.map(branch => new Suggestion({value: branch.toString(), style: styles.branch}));
268273
} else {
269274
return [];
270275
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as React from "react";
2+
import {colors} from "../../views/css/colors";
3+
4+
const buttonStyles = {
5+
borderColor: colors.blue,
6+
borderStyle: "solid",
7+
borderRadius: "4px",
8+
borderWidth: "1px",
9+
padding: "2px",
10+
color: colors.blue,
11+
WebkitUserSelect: "none",
12+
fontSize: "10px",
13+
margin: "4px",
14+
cursor: "pointer",
15+
};
16+
17+
type ButtonProps = {
18+
onClick: () => void;
19+
children?: React.ReactElement<any>;
20+
};
21+
22+
const Button = ({ onClick, children}: ButtonProps) => <span
23+
style={buttonStyles}
24+
onClick={onClick}
25+
>{children}</span>;
26+
27+
export default Button;

src/utils/Git.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,26 @@ export class FileStatus {
103103
}
104104
}
105105

106-
type GitDirectoryPath = string & { __isGitDirectoryPath: boolean };
106+
export type GitDirectoryPath = string & { __isGitDirectoryPath: boolean };
107107

108108
export function isGitDirectory(directory: string): directory is GitDirectoryPath {
109109
return fs.existsSync(Path.join(directory, ".git") );
110110
}
111111

112-
export async function branches(directory: GitDirectoryPath): Promise<Branch[]> {
112+
type BranchesOptions = {
113+
directory: GitDirectoryPath;
114+
remotes: boolean;
115+
tags: boolean;
116+
};
117+
118+
export async function branches({
119+
directory,
120+
remotes,
121+
tags,
122+
}: BranchesOptions): Promise<Branch[]> {
113123
let lines = await linedOutputOf(
114124
"git",
115-
["for-each-ref", "refs/tags", "refs/heads", "refs/remotes", "--format='%(HEAD)%(refname:short)'"],
125+
["for-each-ref", tags ? "refs/tags" : "", "refs/heads", remotes ? "refs/remotes" : "", "--format='%(HEAD)%(refname:short)'"],
116126
directory
117127
);
118128
return lines.map(line => new Branch(line.slice(1), line[0] === "*"));
@@ -157,6 +167,6 @@ export async function status(directory: GitDirectoryPath): Promise<FileStatus[]>
157167
return lines.map(line => new FileStatus(line));
158168
}
159169

160-
export async function repoRoot(directory: string): Promise<string> {
161-
return (await linedOutputOf("git", ["rev-parse", "--show-toplevel"], directory))[0];
170+
export async function repoRoot(directory: GitDirectoryPath): Promise<GitDirectoryPath> {
171+
return (await linedOutputOf("git", ["rev-parse", "--show-toplevel"], directory))[0] as GitDirectoryPath;
162172
}

0 commit comments

Comments
 (0)