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

Add the feature for comparing two arbitrary commits #1108

Merged
merged 46 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f3327b7
Adjust git command handlers
quachtridat Mar 8, 2022
38b30ff
Add `credential_helper` to server settings
quachtridat Mar 8, 2022
0bad2a8
Implement logic for setting git credential helper
quachtridat Mar 8, 2022
bf13b83
Extend git commands to support credentials caching
quachtridat Mar 8, 2022
c88d70a
Add tests for the new credentials caching logic
quachtridat Mar 8, 2022
4199e48
Extend `Git.IAuth` and `CredentialsBox`
quachtridat Mar 23, 2022
e718d4b
Extend `clone()`, `push()`, `pull()` in model
quachtridat Mar 23, 2022
9a236c3
Extend model for `_fetchRemotes()`
quachtridat Mar 23, 2022
292b25c
Implement `fetch()`
quachtridat Mar 23, 2022
8e2c780
Remove a redundant member
quachtridat Mar 23, 2022
84bd60e
Extend `showGitOperationDialog()`
quachtridat Mar 23, 2022
996a5e7
Match the `IGitExtension` with `push()` in model
quachtridat Mar 23, 2022
4f0badf
Extend `StatusWidget`
quachtridat Mar 23, 2022
5abed4d
Reuse `showGitOperationDialog()` in `StatusWidget`
quachtridat Mar 23, 2022
71deeec
Add a notification dot to `StatusWidget`
quachtridat Mar 23, 2022
a578272
Implement `ensure_git_credential_cache_daemon()`
quachtridat Mar 23, 2022
2754f04
Extend `check/ensure_credential_helper()`
quachtridat Mar 23, 2022
7ed1908
Refactor `push/pull/fetch/clone`
quachtridat Mar 23, 2022
2a1e7ba
Refactor and adjust tests to adopt new changes
quachtridat Mar 23, 2022
e87b6ff
Include `"message"` in `fetch()`'s response
quachtridat Mar 23, 2022
17eed00
Lint files with ESLint
quachtridat Mar 23, 2022
be51d01
Adjust `test_handlers.py` to adopt new changes
quachtridat Mar 23, 2022
dff54f2
Revert `await` back to `call`
quachtridat Mar 23, 2022
b4ae04a
Adopt PR review suggestions
quachtridat Mar 29, 2022
66421b9
Fix CI
quachtridat Mar 31, 2022
d11c2dd
Reformat `model.ts` with Prettier
quachtridat Mar 31, 2022
ac5cd9d
Add new icons for commit comparison
quachtridat Apr 14, 2022
5cee9bf
Extend `Git.diff()` to accept two commit hashes
quachtridat Apr 14, 2022
7e2e20c
Implement `IGitExtension.diff()` in `src/model.ts`
quachtridat Apr 14, 2022
3b715ed
Implement new React component `CommitComparisonBox`
quachtridat Apr 14, 2022
462e309
Add two buttons to `PastCommitNode`
quachtridat Apr 14, 2022
836d438
Implement interface `ICommitComparison`
quachtridat Apr 14, 2022
46f5f8c
Move the function `openDiff()` out to `util.ts`
quachtridat Apr 14, 2022
582773a
Extend `HistorySideBar` for commit comparison
quachtridat Apr 14, 2022
f4ee4ff
Extend `GitPanel` to allow commit comparison
quachtridat Apr 14, 2022
a8256b8
Merge branch `master` into `quachtridat/issue985`
quachtridat Apr 14, 2022
e0b4b46
Fix linting and format problems
quachtridat Apr 14, 2022
547b1cd
Mock new props in `HistorySideBar` test file
quachtridat Apr 14, 2022
0da1024
Implemented naming convention changes
ZeshanFayyaz Apr 19, 2022
319425b
Merge pull request #1 from ZeshanFayyaz:quachtridat/issue985
quachtridat Apr 20, 2022
bcd4bf2
Add docstring for `ICommitComparisonBoxProps`
quachtridat Apr 20, 2022
4ad2774
Add a missing `trans` prop for CommitComparisonBox
quachtridat Apr 20, 2022
c480762
Remove the `linesplit` check
quachtridat Apr 20, 2022
4ca4009
Slight increase of the border width when comparing
quachtridat Apr 20, 2022
06f11c1
Remove commit comparison state redundancy
quachtridat Apr 20, 2022
fa20966
Apply suggestions from code review
fcollonval Apr 20, 2022
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
8 changes: 7 additions & 1 deletion jupyterlab_git/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,11 +616,17 @@ async def detailed_log(self, selected_hash, path):
"modified_files": result,
}

async def diff(self, path):
async def diff(self, path, previous=None, current=None):
"""
Execute git diff command & return the result.
"""
cmd = ["git", "diff", "--numstat", "-z"]

if previous:
cmd.append(previous)
if current:
cmd.append(current)

code, my_output, my_error = await execute(cmd, cwd=path)

if code != 0:
Expand Down
11 changes: 10 additions & 1 deletion jupyterlab_git/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,16 @@ async def post(self, path: str = ""):
POST request handler, fetches differences between commits & current working
fcollonval marked this conversation as resolved.
Show resolved Hide resolved
tree.
"""
my_output = await self.git.diff(self.url2localpath(path))
data = self.get_json_body()

if data:
my_output = await self.git.diff(
self.url2localpath(path),
data.get("previous"),
data.get("current"),
)
else:
my_output = await self.git.diff(self.url2localpath(path))

if my_output["code"] != 0:
self.set_status(500)
Expand Down
308 changes: 308 additions & 0 deletions src/components/CommitComparisonBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
import { TranslationBundle } from '@jupyterlab/translation';
import {
caretDownIcon,
caretRightIcon,
fileIcon
} from '@jupyterlab/ui-components';
import { CommandRegistry } from '@lumino/commands';
import * as React from 'react';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { classes } from 'typestyle';
import { getDiffProvider } from '../model';
import {
clickableSpanStyle,
commitComparisonBoxChangedFileListStyle,
commitComparisonBoxDetailStyle
} from '../style/CommitComparisonBox';
import {
changeStageButtonStyle,
sectionAreaStyle,
sectionHeaderLabelStyle
} from '../style/GitStageStyle';
import {
deletionsMadeIcon,
diffIcon,
insertionsMadeIcon
} from '../style/icons';
import {
commitClass,
commitDetailFileClass,
commitDetailHeaderClass,
commitOverviewNumbersClass,
deletionsIconClass,
fileListClass,
iconClass,
insertionsIconClass
} from '../style/SinglePastCommitInfo';
import { Git, IGitExtension } from '../tokens';
import { ActionButton } from './ActionButton';
import { FilePath } from './FilePath';

const ITEM_HEIGHT = 24; // File list item height

interface ICommitComparisonBoxHeaderProps {
collapsible: boolean;
collapsed?: boolean;
label?: string;
trans: TranslationBundle;
onCollapseExpand?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onClickCancel?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
}
quachtridat marked this conversation as resolved.
Show resolved Hide resolved

const CommitComparisonBoxHeader: React.VFC<ICommitComparisonBoxHeaderProps> = (
props: ICommitComparisonBoxHeaderProps
) => {
return (
<div className={sectionAreaStyle} onClick={props.onCollapseExpand}>
{props.collapsible && (
<button className={changeStageButtonStyle}>
{props.collapsed ? <caretRightIcon.react /> : <caretDownIcon.react />}
</button>
)}
<span className={sectionHeaderLabelStyle}>{props.label}</span>
{props.onClickCancel && (
<span className={clickableSpanStyle} onClick={props.onClickCancel}>
{props.trans.__('Cancel')}
</span>
)}
</div>
);
};

interface ICommitComparisonBoxOverviewProps {
totalFiles: number;
totalInsertions: number;
totalDeletions: number;
trans: TranslationBundle;
}

const CommitComparisonBoxOverview: React.VFC<ICommitComparisonBoxOverviewProps> =
(props: ICommitComparisonBoxOverviewProps) => {
return (
<div className={commitClass}>
<div className={commitOverviewNumbersClass}>
<span title={props.trans.__('# Files Changed')}>
<fileIcon.react className={iconClass} tag="span" />
{props.totalFiles}
</span>
<span title={props.trans.__('# Insertions')}>
<insertionsMadeIcon.react
className={classes(iconClass, insertionsIconClass)}
tag="span"
/>
{props.totalInsertions}
</span>
<span title={props.trans.__('# Deletions')}>
<deletionsMadeIcon.react
className={classes(iconClass, deletionsIconClass)}
tag="span"
/>
{props.totalDeletions}
</span>
</div>
</div>
);
};

interface ICommitComparisonBoxChangedFileListProps {
files: Git.ICommitModifiedFile[];
trans: TranslationBundle;
onOpenDiff?: (
filePath: string,
isText: boolean,
previousFilePath?: string
) => (event: React.MouseEvent<HTMLLIElement, MouseEvent>) => void;
}

class CommitComparisonBoxChangedFileList extends React.Component<ICommitComparisonBoxChangedFileListProps> {
render() {
return (
<div className={commitComparisonBoxDetailStyle}>
<div className={commitDetailHeaderClass}>
{this.props.trans.__('Changed')}
</div>
{this.props.files.length > 0 && (
<FixedSizeList
className={classes(
commitComparisonBoxChangedFileListStyle,
fileListClass
)}
height={this.props.files.length * ITEM_HEIGHT}
innerElementType={'ul'}
itemCount={this.props.files.length}
itemData={this.props.files}
itemKey={(index, data) => data[index].modified_file_path}
itemSize={ITEM_HEIGHT}
style={{ overflowX: 'hidden' }}
width={'auto'}
>
{this._renderFile}
</FixedSizeList>
)}
</div>
);
}
/**
* Renders a modified file.
*
* @param props Row properties
* @returns React element
*/
private _renderFile = (
props: ListChildComponentProps<Git.ICommitModifiedFile[]>
): JSX.Element => {
const { data, index, style } = props;
const file = data[index];
const path = file.modified_file_path;
const previous = file.previous_file_path;
const flg = !!getDiffProvider(path) || !file.is_binary;
return (
<li
className={commitDetailFileClass}
onClick={this.props.onOpenDiff(path, flg, previous)}
style={style}
title={path}
>
<FilePath filepath={path} filetype={file.type} />
{flg ? (
<ActionButton
icon={diffIcon}
title={this.props.trans.__('View file changes')}
/>
) : null}
</li>
);
};
}

interface ICommitComparisonBoxBodyProps {
files: Git.ICommitModifiedFile[];
show: boolean;
trans: TranslationBundle;
onOpenDiff?: (
filePath: string,
isText: boolean,
previousFilePath?: string
) => (event: React.MouseEvent<HTMLLIElement, MouseEvent>) => void;
}

const CommitComparisonBoxBody: React.VFC<ICommitComparisonBoxBodyProps> = (
props: ICommitComparisonBoxBodyProps
) => {
const totalInsertions = props.files.reduce((acc, file) => {
const insertions = Number.parseInt(file.insertion, 10);
return acc + (Number.isNaN(insertions) ? 0 : insertions);
}, 0);
const totalDeletions = props.files.reduce((acc, file) => {
const deletions = Number.parseInt(file.deletion, 10);
return acc + (Number.isNaN(deletions) ? 0 : deletions);
}, 0);
return (
<React.Fragment>
<CommitComparisonBoxOverview
totalDeletions={totalDeletions}
totalFiles={props.files.length}
totalInsertions={totalInsertions}
trans={props.trans}
/>
<CommitComparisonBoxChangedFileList
files={props.files}
onOpenDiff={props.onOpenDiff}
trans={props.trans}
/>
</React.Fragment>
);
};

/**
* Interface describing ComparisonBox component properties.
*/
export interface ICommitComparisonBoxProps {
/**
* Is this collapsible?
*/
collapsible: boolean;

/**
* Jupyter App commands registry.
*/
commands: CommandRegistry;

/**
* The commit to compare against.
*/
referenceCommit: Git.ISingleCommitInfo | null;

/**
* The commit to compare.
*/
challengerCommit: Git.ISingleCommitInfo | null;

/**
* The commit comparison result.
*/
changedFiles: Git.ICommitModifiedFile[] | null;

/**
* Header text.
*/
header: string;

/**
* Extension data model.
*/
model: IGitExtension;

/**
* The application language translator.
*/
trans: TranslationBundle;

/**
* Returns a callback to be invoked on clicking cancel.
*/
onCancel?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;

/**
* Returns a callback to be invoked on click to display a file diff.
*
* @param filePath file path
* @param isText indicates whether the file supports displaying a diff
* @param previousFilePath when file has been relocated
* @returns callback
*/
onOpenDiff?: (
filePath: string,
isText: boolean,
previousFilePath?: string
) => (event: React.MouseEvent<HTMLLIElement, MouseEvent>) => void;
}

/**
* A component which displays a comparison between two commits.
*/
export const CommitComparisonBox: React.VFC<ICommitComparisonBoxProps> = (
props: ICommitComparisonBoxProps
) => {
const [collapsed, setCollapsed] = React.useState<boolean>(false);
return (
<React.Fragment>
<CommitComparisonBoxHeader
collapsible={props.collapsible}
collapsed={collapsed}
label={props.header}
onCollapseExpand={() => setCollapsed(!collapsed)}
onClickCancel={props.onCancel}
trans={props.trans}
/>
{!collapsed && props.changedFiles && (
<CommitComparisonBoxBody
files={props.changedFiles}
onOpenDiff={props.onOpenDiff}
show={!collapsed}
trans={props.trans}
/>
)}
</React.Fragment>
);
};
Loading