Skip to content

Commit 36e8c7c

Browse files
committed
Improve context menu of commits
1 parent 5d161a2 commit 36e8c7c

6 files changed

Lines changed: 103 additions & 37 deletions

File tree

src/renderer/components/app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export class App extends React.PureComponent<{}, AppState> {
178178
});
179179
}
180180

181-
openInputDialog(label: string, button: string, onSubmit: (value: string) => void, defaultValue?: string) {
181+
openInputDialog(label: string, button: string, onSubmit: (value: string) => void, defaultValue = '') {
182182
const element = <InputDialog label={label}
183183
button={button}
184184
defaultValue={defaultValue}

src/renderer/components/commit-item.tsx

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { remote, clipboard } from 'electron';
21
import * as React from 'react';
32
import * as Git from 'nodegit';
43
import { ReferenceBadge } from './reference-badge';
54
import { InputDialogHandler } from './input-dialog';
65
import { RepoState, Stash } from '../helpers/repo-state';
76
import { createStashContextMenu } from '../helpers/stash-context-menu';
7+
import { createCommitContextMenu } from '../helpers/commit-context-menu';
88

99
export interface CommitItemProps {
1010
repo: RepoState;
1111
commit: Git.Commit;
12-
head: string;
12+
head: string | null;
1313
references: string[];
1414
selected: boolean;
1515
color: string;
@@ -34,37 +34,10 @@ export class CommitItem extends React.PureComponent<CommitItemProps, {}> {
3434
event.preventDefault();
3535
let menu: Electron.Menu;
3636
if (!this.props.stash) {
37-
const template: Electron.MenuItemConstructorOptions[] = [
38-
{
39-
label: 'Create branch here',
40-
click: () => this.props.onCreateBranch(this.props.commit)
41-
},
42-
{
43-
label: 'Reset to this commit',
44-
submenu: [
45-
{
46-
label: 'Soft',
47-
click: () => this.props.repo.reset(this.props.commit, Git.Reset.TYPE.SOFT)
48-
},
49-
{
50-
label: 'Mixed',
51-
click: () => this.props.repo.reset(this.props.commit, Git.Reset.TYPE.MIXED)
52-
},
53-
{
54-
label: 'Hard',
55-
click: () => this.props.repo.reset(this.props.commit, Git.Reset.TYPE.HARD)
56-
}
57-
]
58-
},
59-
{
60-
type: 'separator'
61-
},
62-
{
63-
label: 'Copy commit sha to clipboard',
64-
click: () => clipboard.writeText(this.props.commit.sha())
65-
},
66-
];
67-
menu = remote.Menu.buildFromTemplate(template);
37+
menu = createCommitContextMenu(this.props.repo,
38+
this.props.commit,
39+
this.props.onCreateBranch,
40+
this.props.onOpenInputDialog);
6841
} else {
6942
menu = createStashContextMenu(this.props.repo, this.props.stash.index);
7043
}

src/renderer/components/commit-list.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export class CommitList extends React.PureComponent<CommitListProps, CommitListS
137137
<CommitItem
138138
repo={this.props.repo}
139139
commit={commit}
140-
head={this.props.repo.head!}
140+
head={this.props.repo.head}
141141
references={this.props.repo.shaToReferences.get(commitSha) || []}
142142
selected={this.props.selectedCommit !== null && this.props.selectedCommit.sha() === commit.sha()}
143143
onCommitSelect={this.props.onCommitSelect}

src/renderer/components/input-dialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { makeModal } from './make-modal';
44
export type InputDialogHandler = (label: string, button: string, onSubmit: (value: string) => void, defaultValue?: string) => void;
55

66
export class InputFormProps {
7-
defaultValue?: string;
7+
defaultValue = '';
88
label: string;
99
button: string;
1010
onSubmit: (value: string) => void;
@@ -19,7 +19,7 @@ class InputForm extends React.PureComponent<InputFormProps, InputFormState> {
1919
constructor(props: InputFormProps) {
2020
super(props);
2121
this.state = {
22-
value: this.props.defaultValue || '',
22+
value: this.props.defaultValue,
2323
}
2424
this.handleValueChange = this.handleValueChange.bind(this);
2525
this.handleSubmit = this.handleSubmit.bind(this);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as Git from 'nodegit';
2+
import { remote, clipboard } from 'electron';
3+
import { RepoState, removeReferencePrefix } from './repo-state';
4+
import { InputDialogHandler } from '../components/input-dialog';
5+
6+
export function createCommitContextMenu(repo: RepoState,
7+
commit: Git.Commit,
8+
onCreateBranch: (commit: Git.Commit) => void,
9+
onOpenInputDialog: InputDialogHandler) {
10+
function openCreateTagDialog() {
11+
onOpenInputDialog('Name', 'Create tag', (value) => repo.createTag(value, commit));
12+
}
13+
14+
const template: Electron.MenuItemConstructorOptions[] = [
15+
{
16+
label: 'Create branch here',
17+
click: () => onCreateBranch(commit)
18+
},
19+
{
20+
label: 'Cherrypick commit',
21+
click: () => repo.cherrypick(commit)
22+
},
23+
];
24+
if (repo.head) {
25+
template.push(
26+
{
27+
label: `Reset ${removeReferencePrefix(repo.head)} to this commit`,
28+
submenu: [
29+
{
30+
label: 'Soft',
31+
click: () => repo.reset(commit, Git.Reset.TYPE.SOFT)
32+
},
33+
{
34+
label: 'Mixed',
35+
click: () => repo.reset(commit, Git.Reset.TYPE.MIXED)
36+
},
37+
{
38+
label: 'Hard',
39+
click: () => repo.reset(commit, Git.Reset.TYPE.HARD)
40+
}
41+
]
42+
}
43+
);
44+
}
45+
template.push(
46+
{
47+
label: 'Revert commit',
48+
click: () => repo.revert(commit)
49+
},
50+
{
51+
type: 'separator'
52+
},
53+
{
54+
label: 'Copy commit sha to clipboard',
55+
click: () => clipboard.writeText(commit.sha())
56+
},
57+
{
58+
type: 'separator'
59+
},
60+
{
61+
label: 'Create tag here',
62+
click: openCreateTagDialog
63+
}
64+
);
65+
return remote.Menu.buildFromTemplate(template);
66+
}

src/renderer/helpers/repo-state.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,24 @@ export class RepoState {
421421
}
422422
}
423423

424+
async cherrypick(commit: Git.Commit) {
425+
try {
426+
await Git.Cherrypick.cherrypick(this.repo, commit, new Git.CheckoutOptions());
427+
await this.commit(commit.message());
428+
} catch (e) {
429+
this.onNotification(`Unable to cherrypick: ${e.message}`, NotificationType.Error);
430+
}
431+
}
432+
433+
async revert(commit: Git.Commit) {
434+
try {
435+
await Git.Revert.revert(this.repo, commit, null);
436+
await this.commit(`Revert "${commit.message()}"`);
437+
} catch (e) {
438+
this.onNotification(`Unable to cherrypick: ${e.message}`, NotificationType.Error);
439+
}
440+
}
441+
424442
// Reference operations
425443

426444
async getReferenceCommits() {
@@ -448,6 +466,15 @@ export class RepoState {
448466
}
449467
}
450468

469+
async createTag(name: string, commit: Git.Commit, message = '') {
470+
try {
471+
const object = await Git.Object.lookup(this.repo, commit.id(), Git.Object.TYPE.COMMIT);
472+
await Git.Tag.createLightweight(this.repo, name, object, 0);
473+
} catch (e) {
474+
this.onNotification(`Unable to create tag ${name}: ${e.message}`, NotificationType.Error);
475+
}
476+
}
477+
451478
async removeReference(name: string) {
452479
try {
453480
await Git.Reference.remove(this.repo, name);

0 commit comments

Comments
 (0)