Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show both staged and unstaged changes for a file #629

Merged
merged 10 commits into from
Jun 14, 2020
10 changes: 4 additions & 6 deletions src/components/FileItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ export class FileItem extends React.Component<IFileItemProps> {
}

render() {
const status =
this.getFileChangedLabel(this.props.file.y as any) ||
this.getFileChangedLabel(this.props.file.x as any);
const { file } = this.props;
const status_code = file.status === 'staged' ? file.x : file.y;
const status = this.getFileChangedLabel(status_code as any);

return (
<li
Expand Down Expand Up @@ -110,9 +110,7 @@ export class FileItem extends React.Component<IFileItemProps> {
/>
{this.props.actions}
<span className={this.getFileChangedLabelClass(this.props.file.y)}>
{this.props.file.y === '?'
? 'U'
: this.props.file.y.trim() || this.props.file.x}
{this.props.file.y === '?' ? 'U' : status_code}
</span>
</li>
);
Expand Down
91 changes: 77 additions & 14 deletions src/components/FileList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
this._contextMenuStaged = new Menu({ commands });
this._contextMenuUnstaged = new Menu({ commands });
this._contextMenuUntracked = new Menu({ commands });
this._contextMenuSimpleUntracked = new Menu({ commands });
this._contextMenuSimpleTracked = new Menu({ commands });

this.state = {
selectedFile: null
Expand Down Expand Up @@ -134,7 +136,7 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
label: 'Discard',
caption: 'Discard recent changes of selected file',
execute: () => {
this.discardChanges(this.state.selectedFile.to);
this.discardChanges(this.state.selectedFile);
}
});
}
Expand All @@ -159,6 +161,18 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
[CommandIDs.gitFileOpen, CommandIDs.gitFileTrack].forEach(command => {
this._contextMenuUntracked.addItem({ command });
});

[
CommandIDs.gitFileOpen,
CommandIDs.gitFileDiscard,
CommandIDs.gitFileDiffWorking
].forEach(command => {
this._contextMenuSimpleTracked.addItem({ command });
});

[CommandIDs.gitFileOpen].forEach(command => {
this._contextMenuSimpleUntracked.addItem({ command });
});
}

/** Handle right-click on a staged file */
Expand All @@ -179,6 +193,18 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
this._contextMenuUntracked.open(event.clientX, event.clientY);
};

/** Handle right-click on an untracked file in Simple mode*/
contextMenuSimpleUntracked = (event: React.MouseEvent) => {
event.preventDefault();
this._contextMenuSimpleUntracked.open(event.clientX, event.clientY);
};

/** Handle right-click on an tracked file in Simple mode*/
contextMenuSimpleTracked = (event: React.MouseEvent) => {
event.preventDefault();
this._contextMenuSimpleTracked.open(event.clientX, event.clientY);
};

/** Reset all staged files */
resetAllStagedFiles = async () => {
await this.props.model.reset();
Expand Down Expand Up @@ -234,23 +260,31 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
};

/** Discard changes in a specific unstaged or staged file */
discardChanges = async (file: string) => {
discardChanges = async (file: Git.IStatusFile) => {
const result = await showDialog({
title: 'Discard changes',
body: (
<span>
Are you sure you want to permanently discard changes to <b>{file}</b>?
This action cannot be undone.
Are you sure you want to permanently discard changes to{' '}
<b>{file.to}</b>? This action cannot be undone.
</span>
),
buttons: [Dialog.cancelButton(), Dialog.warnButton({ label: 'Discard' })]
});
if (result.button.accept) {
try {
await this.props.model.reset(file);
await this.props.model.checkout({ filename: file });
if (file.status === 'staged' || file.status === 'partially-staged') {
await this.props.model.reset(file.to);
}
if (
file.status === 'unstaged' ||
(file.status === 'partially-staged' && file.x !== 'A')
) {
// resetting an added file moves it to untracked category => checkout will fail
await this.props.model.checkout({ filename: file.to });
}
} catch (reason) {
showErrorMessage(`Discard changes for ${file} failed.`, reason, [
showErrorMessage(`Discard changes for ${file.to} failed.`, reason, [
Dialog.warnButton({ label: 'DISMISS' })
]);
}
Expand Down Expand Up @@ -297,6 +331,16 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
case 'untracked':
untrackedFiles.push(file);
break;
case 'partially-staged':
stagedFiles.push({
...file,
status: 'staged'
});
unstagedFiles.push({
...file,
status: 'unstaged'
});
break;

default:
break;
Expand Down Expand Up @@ -325,7 +369,8 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
this.state.selectedFile.x === candidate.x &&
this.state.selectedFile.y === candidate.y &&
this.state.selectedFile.from === candidate.from &&
this.state.selectedFile.to === candidate.to
this.state.selectedFile.to === candidate.to &&
this.state.selectedFile.status === candidate.status
);
}

Expand Down Expand Up @@ -443,7 +488,7 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
iconName={'git-discard'}
title={'Discard changes'}
onClick={() => {
this.discardChanges(file.to);
this.discardChanges(file);
}}
/>
<ActionButton
Expand Down Expand Up @@ -569,9 +614,13 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
? (): void => undefined
: openFile;

let diffButton: JSX.Element;
if (file.status === 'unstaged') {
diffButton = this._createDiffButton(file, 'WORKING');
let contextMenu = this.contextMenuSimpleUntracked;

if (
file.status === 'unstaged' ||
file.status === 'partially-staged'
) {
const diffButton = this._createDiffButton(file, 'WORKING');
actions = (
<React.Fragment>
<ActionButton
Expand All @@ -586,7 +635,7 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
iconName={'git-discard'}
title={'Discard changes'}
onClick={() => {
this.discardChanges(file.to);
this.discardChanges(file);
}}
/>
</React.Fragment>
Expand All @@ -596,8 +645,9 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
? () => this._openDiffView(file, 'WORKING')
: () => undefined
: openFile;
contextMenu = this.contextMenuSimpleTracked;
} else if (file.status === 'staged') {
diffButton = this._createDiffButton(file, 'INDEX');
const diffButton = this._createDiffButton(file, 'INDEX');
actions = (
<React.Fragment>
<ActionButton
Expand All @@ -607,13 +657,22 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
onClick={openFile}
/>
{diffButton}
<ActionButton
className={hiddenButtonStyle}
iconName={'git-discard'}
title={'Discard changes'}
onClick={() => {
this.discardChanges(file);
}}
/>
</React.Fragment>
);
onDoubleClick = doubleClickDiff
? diffButton
? () => this._openDiffView(file, 'INDEX')
: () => undefined
: openFile;
contextMenu = this.contextMenuSimpleTracked;
}

return (
Expand All @@ -624,6 +683,8 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
markBox={true}
model={this.props.model}
onDoubleClick={onDoubleClick}
contextMenu={contextMenu}
selectFile={this.updateSelectedFile}
/>
);
})}
Expand Down Expand Up @@ -683,4 +744,6 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
private _contextMenuStaged: Menu;
private _contextMenuUnstaged: Menu;
private _contextMenuUntracked: Menu;
private _contextMenuSimpleTracked: Menu;
private _contextMenuSimpleUntracked: Menu;
}
8 changes: 6 additions & 2 deletions src/components/GitPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -418,11 +418,15 @@ export class GitPanel extends React.Component<
}

private _hasStagedFile(): boolean {
return this.state.files.some(file => file.status === 'staged');
return this.state.files.some(
file => file.status === 'staged' || file.status === 'partially-staged'
);
}

private _hasUnStagedFile(): boolean {
return this.state.files.some(file => file.status === 'unstaged');
return this.state.files.some(
file => file.status === 'unstaged' || file.status === 'partially-staged'
);
}

/**
Expand Down
7 changes: 6 additions & 1 deletion src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,5 +493,10 @@ export namespace Git {
toggle(fname: string): void;
}

export type Status = 'untracked' | 'staged' | 'unstaged' | null;
export type Status =
| 'untracked'
| 'staged'
| 'unstaged'
| 'partially-staged'
| null;
}
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export function decodeStage(x: string, y: string): Git.Status {
return 'untracked';
} else {
// If file is staged
if (x !== ' ' && y !== 'D') {
return 'staged';
if (x !== ' ') {
return y !== ' ' ? 'partially-staged' : 'staged';
}
// If file is unstaged but tracked
if (y !== ' ') {
Expand Down
Loading