Skip to content

Commit bc13205

Browse files
committed
Fetch three-way-diff content for merge conflicts (#896)
- [WIP] console logs content - TODO: display diff
1 parent 033ecb8 commit bc13205

File tree

5 files changed

+106
-12
lines changed

5 files changed

+106
-12
lines changed

jupyterlab_git/git.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,38 @@ async def _get_tag(self, path, commit_sha):
11731173
)
11741174
)
11751175

1176+
async def show_from_object(self, ref, path):
1177+
"""
1178+
Execute git show <ref> command & return the result.
1179+
"""
1180+
command = ["git", "show", ref]
1181+
1182+
code, output, error = await execute(command, cwd=path)
1183+
1184+
error_messages = map(
1185+
lambda n: n.lower(),
1186+
[
1187+
"fatal: Object '{}' exists on disk, but not here".format(ref),
1188+
"fatal: Object '{}' does not exist (neither on disk nor in the index)".format(
1189+
ref
1190+
),
1191+
# TODO fix this
1192+
"fatal: Object '{}' does not exist in 'HEAD'".format(ref),
1193+
"fatal: Invalid object name 'HEAD'",
1194+
],
1195+
)
1196+
lower_error = error.lower()
1197+
if code == 0:
1198+
return output
1199+
elif any([msg in lower_error for msg in error_messages]):
1200+
return ""
1201+
else:
1202+
raise tornado.web.HTTPError(
1203+
log_message="Error [{}] occurred while executing [{}] command to retrieve plaintext diff.".format(
1204+
error, " ".join(command)
1205+
)
1206+
)
1207+
11761208
async def show(self, filename, ref, path):
11771209
"""
11781210
Execute git show <ref:filename> command & return the result.
@@ -1448,3 +1480,32 @@ async def tag_checkout(self, path, tag):
14481480
"command": " ".join(command),
14491481
"message": error,
14501482
}
1483+
1484+
async def get_unmerged_file_contents(self, filename, path):
1485+
"""
1486+
Returns the three-way-diff contents of an unmerged file
1487+
1488+
Execute `git ls-files -uz <filename>` command & return the file contents
1489+
Of the three object refs.
1490+
1491+
Args:
1492+
filename (str): Filename (relative to the git repository)
1493+
path (str): Git repository filepath
1494+
"""
1495+
command = ["git", "ls-files", "-u", "-z", filename]
1496+
1497+
code, output, error = await execute(command, cwd=path)
1498+
if code != 0:
1499+
return {
1500+
"code": code,
1501+
"command": " ".join(command),
1502+
"message": error,
1503+
}
1504+
1505+
result = []
1506+
line_iterable = iter(strip_and_split(output))
1507+
for line in line_iterable:
1508+
ref = line.split(" ")[1]
1509+
content = await self.show_from_object(ref, path)
1510+
result.append(content)
1511+
return {"code": code, "contents": result}

jupyterlab_git/handlers.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,22 @@ async def post(self, path: str = ""):
805805
self.finish(json.dumps(result))
806806

807807

808+
class GitMergeDiffHandler(GitHandler):
809+
"""
810+
Handler to show a 3-way diff of a file with merge conflicts.
811+
"""
812+
813+
@tornado.web.authenticated
814+
async def post(self, path: str = ""):
815+
data = self.get_json_body()
816+
filename = data["filename"]
817+
response = await self.git.get_unmerged_file_contents(
818+
filename,
819+
self.url2localpath(path),
820+
)
821+
self.finish(json.dumps(response))
822+
823+
808824
def setup_handlers(web_app):
809825
"""
810826
Setups all of the git command handlers.
@@ -842,6 +858,7 @@ def setup_handlers(web_app):
842858
("/tags", GitTagHandler),
843859
("/tag_checkout", GitTagCheckoutHandler),
844860
("/add", GitAddHandler),
861+
("/merge_diff", GitMergeDiffHandler),
845862
]
846863

847864
handlers = [

src/commandsAndMenu.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -562,16 +562,6 @@ export function addCommands(
562562
for (const file of files) {
563563
const { context, filePath, isText, status } = file;
564564

565-
// Merge conflict
566-
// TODO: handle
567-
if (status === 'unmerged') {
568-
logger.log({
569-
message: 'File has conflicts!',
570-
level: Level.ERROR
571-
});
572-
continue;
573-
}
574-
575565
// nothing to compare to for untracked files
576566
if (status === 'untracked') {
577567
continue;
@@ -580,6 +570,14 @@ export function addCommands(
580570
const repositoryPath = gitModel.getRelativeFilePath();
581571
const filename = PathExt.join(repositoryPath, filePath);
582572

573+
// Merge conflict
574+
if (status === 'unmerged') {
575+
const t = await gitModel.getMergeDiff(filename);
576+
// TODO: handle
577+
console.log(t);
578+
continue;
579+
}
580+
583581
let diffContext = context;
584582
if (!diffContext) {
585583
const specialRef =

src/components/FileList.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
363363
return (
364364
<FileItem
365365
trans={this.props.trans}
366-
actions={diffButton}
366+
actions={!file.is_binary && diffButton}
367367
file={file}
368368
contextMenu={this.openContextMenu}
369369
model={this.props.model}
@@ -387,12 +387,13 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
387387
title={this.props.trans.__('Stage all changes')}
388388
onClick={() => {
389389
// TODO open modal with confirmation to stage with merge conflicts
390+
// TODO REMOVE ACTION
390391
}}
391392
/>
392393
}
393394
collapsible
394395
files={files}
395-
heading={this.props.trans.__('Unmerged')}
396+
heading={this.props.trans.__('Conflicted')}
396397
height={height}
397398
rowRenderer={this._renderUnmergedRow}
398399
/>

src/model.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,23 @@ export class GitExtension implements IGitExtension {
11661166
await this.commit(message);
11671167
}
11681168

1169+
/**
1170+
* TODO: @navn
1171+
*/
1172+
async getMergeDiff(filename: string): Promise<any> {
1173+
const path = await this._getPathRepository();
1174+
return await this._taskHandler.execute<any>(
1175+
'git:merge_diff:view', // TODO: what is this for?
1176+
async () => {
1177+
return await requestAPI<any>(
1178+
URLExt.join(path, 'merge_diff'),
1179+
'POST',
1180+
{ filename }
1181+
);
1182+
}
1183+
);
1184+
}
1185+
11691186
/**
11701187
* Make request for a list of all git branches in the repository
11711188
* Retrieve a list of repository branches.

0 commit comments

Comments
 (0)