Skip to content

Commit 4eac96f

Browse files
Tim Lupotelamonian
authored andcommitted
Plain text diff
First version using CodeMirror First good looking Restructure CSS + improve look and feel Add gutters Remove `less` call Use message in lower case Rebase on master + comment
1 parent a656567 commit 4eac96f

File tree

18 files changed

+3436
-1279
lines changed

18 files changed

+3436
-1279
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.DS_Store
12
# Byte-compiled / optimized / DLL files
23
__pycache__/
34
*.py[cod]

jupyterlab_git/__init__.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,10 @@ def _jupyter_server_extension_paths():
1212
return [{"module": "jupyterlab_git"}]
1313

1414

15-
def _jupyter_nbextension_paths():
16-
"""
17-
Declare the Jupyter notebook extension paths.
18-
"""
19-
return [{"section": "notebook", "dest": "jupyterlab_git"}]
20-
21-
2215
def load_jupyter_server_extension(nbapp):
2316
"""
2417
Load the Jupyter server extension.
2518
"""
26-
git = Git(nbapp.web_app.settings.get('server_root_dir'))
19+
git = Git(nbapp.web_app.settings['contents_manager'])
2720
nbapp.web_app.settings["git"] = git
2821
setup_handlers(nbapp.web_app)

jupyterlab_git/git.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ class Git:
6464
A single parent class containing all of the individual git methods in it.
6565
"""
6666

67-
def __init__(self, root_dir):
68-
self.root_dir = os.path.expanduser(root_dir)
67+
def __init__(self, contents_manager):
68+
self.contents_manager = contents_manager
69+
self.root_dir = os.path.realpath(os.path.expanduser(contents_manager.root_dir))
6970

7071
def config(self, top_repo_path, **kwargs):
7172
"""Get or set Git options.
@@ -892,3 +893,58 @@ def _get_tag(self, current_path, commit_sha):
892893
error.decode("utf-8"), " ".join(command)
893894
)
894895
)
896+
897+
def show(self, filename, ref, top_repo_path):
898+
"""
899+
Execute git show<ref:filename> command & return the result.
900+
"""
901+
command = ["git", "show", '{}:{}'.format(ref, filename)]
902+
p = subprocess.Popen(
903+
command,
904+
stdout=PIPE,
905+
stderr=PIPE,
906+
cwd=top_repo_path
907+
)
908+
output, error = p.communicate()
909+
910+
error_messages = map(lambda n: n.lower(), [
911+
"fatal: Path '{}' exists on disk, but not in '{}'".format(filename, ref),
912+
"fatal: Path '{}' does not exist (neither on disk nor in the index)".format(filename)
913+
])
914+
lower_error = error.decode('utf-8').lower()
915+
if p.returncode == 0:
916+
return output.decode('utf-8')
917+
elif any([msg in lower_error for msg in error_messages]):
918+
return ""
919+
else:
920+
raise Exception('Error [{}] occurred while executing [{}] command to retrieve plaintext diff.'.format(
921+
error.decode('utf-8'),
922+
' '.join(command)
923+
))
924+
925+
def get_content(self, filename, top_repo_path):
926+
"""
927+
Get the file content of filename.
928+
"""
929+
relative_repo = os.path.relpath(top_repo_path, self.root_dir)
930+
model = self.contents_manager.get(path=os.path.join(relative_repo, filename))
931+
return model['content']
932+
933+
def diff_content(self, filename, prev_ref, curr_ref, top_repo_path):
934+
"""
935+
Collect get content of prev and curr and return.
936+
"""
937+
try:
938+
prev_content = self.show(filename, prev_ref["git"], top_repo_path)
939+
if "special" in curr_ref:
940+
if curr_ref["special"] == "WORKING":
941+
curr_content = self.get_content(filename, top_repo_path)
942+
elif curr_ref["special"] == "INDEX":
943+
curr_content = self.show(filename, "", top_repo_path)
944+
else:
945+
raise Exception("Error while retrieving plaintext diff, unknown special ref '{}'.".format(curr_ref["specialref"]))
946+
else:
947+
curr_content = self.show(filename, curr_ref["git"], top_repo_path)
948+
return {"prev_content": prev_content, "curr_content": curr_content}
949+
except:
950+
raise

jupyterlab_git/handlers.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
Module with all the individual handlers, which execute git commands and return the results to the frontend.
33
"""
44
import json
5+
import os
56

6-
from notebook.utils import url_path_join as ujoin
7+
from notebook.utils import url_path_join as ujoin, url2path
78
from notebook.base.handlers import APIHandler
89

910

@@ -470,6 +471,22 @@ def post(self):
470471
self.set_status(201)
471472
self.finish(json.dumps(response))
472473

474+
class GitDiffContentHandler(GitHandler):
475+
"""
476+
Handler for plain text diffs. Uses git show $REF:$FILE
477+
Returns `prev_content` and `curr_content` with content of given file.
478+
"""
479+
480+
def post(self):
481+
cm = self.contents_manager
482+
data = self.get_json_body()
483+
filename = data["filename"]
484+
prev_ref = data["prev_ref"]
485+
curr_ref = data["curr_ref"]
486+
top_repo_path = os.path.join(cm.root_dir, url2path(data["top_repo_path"]))
487+
response = self.git.diff_content(filename, prev_ref, curr_ref, top_repo_path)
488+
self.finish(json.dumps(response))
489+
473490

474491
class GitServerRootHandler(GitHandler):
475492

@@ -509,7 +526,8 @@ def setup_handlers(web_app):
509526
("/git/upstream", GitUpstreamHandler),
510527
("/git/config", GitConfigHandler),
511528
("/git/changed_files", GitChangedFilesHandler),
512-
("/git/server_root", GitServerRootHandler)
529+
("/git/server_root", GitServerRootHandler),
530+
("/git/diffcontent", GitDiffContentHandler)
513531
]
514532

515533
# add the baseurl to our paths

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "0.10.0",
44
"description": "A JupyterLab extension for version control using git",
55
"main": "lib/index.js",
6+
"types": "lib/index.d.ts",
67
"style": "style/index.css",
78
"license": "BSD-3-Clause",
89
"author": "Jupyter Development Team",
@@ -26,7 +27,7 @@
2627
"files": [
2728
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
2829
"schema/**/*.{json,}",
29-
"style/**/*.{css,svg}"
30+
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
3031
],
3132
"sideEffects": [
3233
"style/*.css"
@@ -49,6 +50,7 @@
4950
"dependencies": {
5051
"@jupyterlab/application": "^1.1.0",
5152
"@jupyterlab/apputils": "^1.1.0",
53+
"@jupyterlab/codemirror": "^1.1.0",
5254
"@jupyterlab/console": "^1.1.0",
5355
"@jupyterlab/coreutils": "^3.1.0",
5456
"@jupyterlab/filebrowser": "^1.1.0",
@@ -57,6 +59,7 @@
5759
"@jupyterlab/terminal": "^1.1.0",
5860
"@jupyterlab/ui-components": "^1.1.0",
5961
"@phosphor/widgets": "^1.8.0",
62+
"diff-match-patch": "^1.0.4",
6063
"nbdime": "~5.0.1",
6164
"react": "~16.8.4",
6265
"react-dom": "~16.8.4",
@@ -67,6 +70,7 @@
6770
"@babel/preset-env": "^7.5.0",
6871
"@jupyterlab/testutils": "^1.1.0",
6972
"@types/codemirror": "^0.0.79",
73+
"@types/diff-match-patch": "^1.0.32",
7074
"@types/enzyme": "3.1.15",
7175
"@types/jest": "^24",
7276
"@types/react": "~16.8.13",
@@ -87,7 +91,10 @@
8791
"tslint-config-prettier": "1.18.0",
8892
"tslint-plugin-prettier": "^2.0.0",
8993
"typescript": "~3.5.1",
90-
"typescript-tslint-plugin": "0.3.1"
94+
"typescript-tslint-plugin": "^0.5.4"
95+
},
96+
"peerDependencies": {
97+
"codemirror": "^5.0.0"
9198
},
9299
"directories": {
93100
"lib": "lib"

src/components/diff/Diff.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
33
import * as React from 'react';
44
import { IDiffContext } from './model';
55
import { NBDiff } from './NbDiff';
6+
import { isText, PlainTextDiff } from './PlainTextDiff';
67

78
/**
89
* A registry which maintains mappings of file extension to diff provider components.
@@ -14,17 +15,30 @@ const DIFF_PROVIDER_REGISTRY: { [key: string]: typeof React.Component } = {
1415
/**
1516
* Determines if a given file is supported for diffs.
1617
*
17-
* This will be removed when "Plaintext" diffs are supported since that will be used
18-
* for cases where there is not a dedicated diff provider.
19-
*
2018
* @param path the file path
2119
*/
2220
export function isDiffSupported(path: string): boolean {
23-
return PathExt.extname(path).toLocaleLowerCase() in DIFF_PROVIDER_REGISTRY;
21+
return (
22+
PathExt.extname(path).toLocaleLowerCase() in DIFF_PROVIDER_REGISTRY ||
23+
isText(path)
24+
);
2425
}
2526

27+
/** Diff component properties */
2628
export interface IDiffProps {
29+
/**
30+
* Path of the file to diff.
31+
* It is relative to the git repository root folder
32+
*/
2733
path: string;
34+
/**
35+
* Git respository folder path.
36+
* It is relative to the server root.
37+
*/
38+
topRepoPath: string;
39+
/**
40+
* References to show the diff for.
41+
*/
2842
diffContext: IDiffContext;
2943
}
3044

@@ -37,9 +51,9 @@ export function Diff(props: IDiffProps) {
3751
if (fileExtension in DIFF_PROVIDER_REGISTRY) {
3852
const DiffProvider = DIFF_PROVIDER_REGISTRY[fileExtension];
3953
return <DiffProvider {...props} />;
54+
} else if (isText(props.path)) {
55+
return <PlainTextDiff {...props} />;
4056
} else {
41-
// This will be removed and delegated to a "Plaintext" diff provider for
42-
// cases where the file extension does not have a dedicated diff provider.
4357
console.log(`Diff is not supported for ${fileExtension} files`);
4458
return null;
4559
}

src/components/diff/DiffWidget.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@ export async function openDiffView(
3535
mainAreaItem = mainAreaItems.next();
3636
}
3737
if (!mainAreaItem) {
38-
const relativeFilePath = model.getRelativeFilePath(filePath);
38+
const serverRepoPath = model.getRelativeFilePath();
3939
const nbDiffWidget = ReactWidget.create(
4040
<RenderMimeProvider value={renderMime}>
41-
<Diff path={relativeFilePath} diffContext={diffContext} />
41+
<Diff
42+
path={filePath}
43+
diffContext={diffContext}
44+
topRepoPath={serverRepoPath}
45+
/>
4246
</RenderMimeProvider>
4347
);
4448
nbDiffWidget.id = id;

src/components/diff/NbDiff.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { nbformat } from '@jupyterlab/coreutils';
1+
import { nbformat, PathExt } from '@jupyterlab/coreutils';
22
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
33
import { ServerConnection } from '@jupyterlab/services/lib/serverconnection';
44
import { IDiffEntry } from 'nbdime/lib/diff/diffentries';
@@ -178,7 +178,7 @@ export class NBDiff extends React.Component<IDiffProps, INBDiffState> {
178178
}
179179

180180
httpGitRequest('/nbdime/api/gitdiff', 'POST', {
181-
file_path: this.props.path,
181+
file_path: PathExt.join(this.props.topRepoPath, this.props.path),
182182
ref_local: { git: diffContext.previousRef.gitRef },
183183
ref_remote: currentRefValue
184184
}).then((response: Response) => {

0 commit comments

Comments
 (0)