Skip to content
This repository has been archived by the owner on Dec 17, 2021. It is now read-only.

Commit

Permalink
Add experimental history graph across branches
Browse files Browse the repository at this point in the history
  • Loading branch information
dcermak committed Apr 9, 2021
1 parent 89df5b3 commit 2755b23
Show file tree
Hide file tree
Showing 14 changed files with 957 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"]
project: ["./tsconfig.json", "./tsconfig.frontend.json"]
},
plugins: ["@typescript-eslint"],
rules: {
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ src/ui-tests/accounts/settings.json
dist/*
test-home/*
test-tmp/*
media/html/*
frontend_out/*

media/dark/endpoints_disconnected.svg
media/light/endpoints_disconnected.svg
1 change: 1 addition & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ test-home/**
scripts/**
generate_icons.sh
.dir-locals.el
frontend_out/**
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"keywords": ["open-build-service", "obs", "buildservice"],
"dependencies": {
"@gitgraph/js": "^1.4.0",
"@vscode-logging/logger": "^1.2",
"config-ini-parser": "^1.3",
"keytar": "^7",
Expand All @@ -51,7 +52,7 @@
"webpack-dev": "webpack --mode development --watch",
"test-compile": "tsc -p ./",
"cleandeps": "rm -rf node_modules/",
"clean": "rm -rf ./out ./coverage *vsix ./nyc_output ./documentation ./test-resources/ ./mocklibsecret/build/ ./.vscode-test/ ./test-home/ ./.log/ ./dist ./src/ui-tests/default/fakeHome/ ./src/ui-tests/accounts/fakeHome/",
"clean": "rm -rf ./out ./coverage *vsix ./nyc_output ./documentation ./test-resources/ ./mocklibsecret/build/ ./.vscode-test/ ./test-home/ ./.log/ ./dist ./src/ui-tests/default/fakeHome/ ./src/ui-tests/accounts/fakeHome/ ./frontend_out ./media/html/",
"coverage": "COVERAGE=1 ./runTests.sh && echo \"COVERAGE: $(cat coverage/coverage-summary.json | jq .total.lines.pct) %\"",
"test:ui": "./runUiTests.sh",
"mocklibsecret": "[ -e ./mocklibsecret/build/libsecret.so ] || (cd mocklibsecret && meson build && meson compile -C build)",
Expand All @@ -60,7 +61,7 @@
"generate_icons": "./generate_icons.sh",
"pretest": "yarn run compile",
"watch": "tsc -watch -p ./",
"compile": "tsc -p ./",
"compile": "tsc -p ./tsconfig.json && tsc -p ./tsconfig.frontend.json",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"format": "prettier --write \"src/**/*.ts\"",
"vscode:prepublish": "webpack --mode $([ \"${EXTENSION_DEBUG}\" = 1 ] && echo \"development\" || echo \"production\")"
Expand Down Expand Up @@ -413,6 +414,7 @@
],
"scm": [
{
"type": "webview",
"name": "Package History",
"id": "packageScmHistoryTree"
}
Expand Down Expand Up @@ -586,6 +588,7 @@
"onView:bookmarkedProjectsTree",
"onView:currentProjectTree",
"onView:repositoryTree",
"onView:packageScmHistoryTree",
"workspaceContains:**/.osc",
"workspaceContains:**/.osc_obs_ts"
],
Expand Down
31 changes: 10 additions & 21 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import { CheckOutHandler } from "./check-out-handler";
import { CurrentPackageWatcherImpl } from "./current-package-watcher";
import { CurrentProjectTreeProvider } from "./current-project-view";
import { EmptyDocumentForDiffProvider } from "./empty-file-provider";
import { HistoryGraph } from "./history-graph";
import { ObsServerInformation } from "./instance-info";
import { setupLogger } from "./logging";
import { OscBuildTaskProvider } from "./osc-build-task";
import { RemotePackageFileContentProvider } from "./package-file-contents";
import { ProjectBookmarkManager } from "./project-bookmarks";
import { RepositoryTreeProvider } from "./repository";
import { PackageScmHistoryTree } from "./scm-history";
import { PackageScm } from "./vcs";

export async function activate(
Expand Down Expand Up @@ -90,25 +90,10 @@ export async function activate(
showCollapseAll,
treeDataProvider: repoTreeProvider
});
const [
packageScmHistoryTreeProvider,
oscBuildTaskProvider
] = await Promise.all([
PackageScmHistoryTree.createPackageScmHistoryTree(
currentPackageWatcher,
accountManager,
logger
),
OscBuildTaskProvider.createOscBuildTaskProvider(
currentPackageWatcher,
accountManager,
logger
)
]);

const packageScmHistoryTree = vscode.window.createTreeView(
"packageScmHistoryTree",
{ showCollapseAll, treeDataProvider: packageScmHistoryTreeProvider }
const oscBuildTaskProvider = await OscBuildTaskProvider.createOscBuildTaskProvider(
currentPackageWatcher,
accountManager,
logger
);

const pkgFileProv = new RemotePackageFileContentProvider(
Expand All @@ -124,7 +109,11 @@ export async function activate(
pkgFileProv,
currentPackageWatcher,
new PackageScm(currentPackageWatcher, accountManager, logger),
packageScmHistoryTree,
HistoryGraph.createHistoryGraph(
currentPackageWatcher,
accountManager,
logger
),
new ObsServerInformation(accountManager, logger),
new EmptyDocumentForDiffProvider(),
new CheckOutHandler(accountManager, logger),
Expand Down
240 changes: 240 additions & 0 deletions src/frontend/draw-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/**
* Copyright (c) 2021 SUSE LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import * as GitgraphJS from "@gitgraph/js";
import {
commitKeyToBranchName,
CommitWithChildrenFromJson,
getBranchName,
MessageType,
ReceivedHistoryReceivedMsg,
SendHistoryReceivedMsg,
StartFetchingHistoryMsg
} from "../history-graph-common";

interface Message {
type: MessageType;
payload?: any;
}

interface VSCode {
postMessage<T extends Message = Message>(message: T): void;
getState(): any;
setState(state: any): void;
}

declare function acquireVsCodeApi(): VSCode;

const getGraphTemplate = (): ReturnType<typeof GitgraphJS.templateExtend> => {
const graphContainerCss = window.getComputedStyle(
document.getElementById("graph-container")!
);
const font = graphContainerCss.font;
const fontSize = parseInt(graphContainerCss.fontSize.replace("px", ""));

const [foregroundColor, backgroundColor] = [
"foreground",
"background"
].map((color) =>
graphContainerCss.getPropertyValue(`--vscode-editor-${color}`)
);
const lineWidth = Math.ceil(fontSize / 10);
const commitSpacing = 3 * fontSize;
const dotSize = Math.ceil(fontSize * 0.75);
const branchSpacing = 2 * fontSize;
const arrowSize = Math.ceil(fontSize * 0.5);

return GitgraphJS.templateExtend(GitgraphJS.TemplateName.Metro, {
arrow: { size: arrowSize, color: foregroundColor },
branch: {
label: {
font,
strokeColor: foregroundColor,
bgColor: backgroundColor
},
lineWidth,
spacing: branchSpacing,
color: foregroundColor
},
commit: {
spacing: commitSpacing,
dot: {
size: dotSize,
strokeWidth: lineWidth,
strokeColor: foregroundColor,
font
},
message: {
// we add the author & hash ourselves, as we cannot make gitgraph.js
// omit the email
displayHash: false,
displayAuthor: false,
font,
color: foregroundColor
}
}
});
};

const drawSequentialHistoryGraph = (data: SendHistoryReceivedMsg): void => {
const branchMap = new Map<string, GitgraphJS.Branch>();

const graphContainer = document.getElementById("graph-container")!;
graphContainer.innerHTML = "";

const template = getGraphTemplate();
const graph = GitgraphJS.createGitgraph(graphContainer, {
template
});
graph.clear();

data.parentlessCommits.forEach((c) => {
const branchName = getBranchName(c);
branchMap.set(branchName, graph.branch(branchName));
});

const sortedCommits = data.commitMapInitializer
.map(([, commit]) => commit)
.sort((c1, c2) =>
c1.commitTime.getTime() < c2.commitTime.getTime()
? -1
: c1.commitTime.getTime() > c2.commitTime.getTime()
? 1
: 0
);

for (const commit of sortedCommits) {
const commitOpts = {
// We create the commit message ourselves here completely using the hash,
// commitMessage and userId not relying on gitgraph.js
// The issue with gitgraph.js is that it will use the hash to uniquely
// identify commits (the revisionHash is not guaranteed to be unique
// across branches though, so thereby we'd get two commits inside one)
// Furthermore, we do not have the user's email addresses at this point
// (and don't really want to fetch them), and as we cannot tell gitgraphjs
// to *not* include the email, we just append the userId as well
subject: [`r${commit.revision}`, commit.commitMessage, commit.userId]
.filter((s) => s !== undefined && s !== "")
.join(" - ")
};

if (commit.parentCommits.length > 1) {
const branchNames = commit.parentCommits.map((parentKey) =>
commitKeyToBranchName(parentKey)
);

const branches = branchNames
.map((branchName) => branchMap.get(branchName))
.filter((b) => b !== undefined) as GitgraphJS.Branch[];
branches.slice(1).map((b) => {
branches[0].merge({ branch: b, commitOptions: commitOpts });
});
} else {
const branchName = getBranchName(commit);
let branch = branchMap.get(branchName);
if (branch === undefined) {
branch = graph.branch(branchName);
branchMap.set(branchName, branch);
}

branch.commit(commitOpts);
}
}
};

function isReceivedHistoryReceivedMsg(
msg: any
): msg is ReceivedHistoryReceivedMsg {
return (
msg !== undefined &&
msg.commitMapInitializer !== undefined &&
msg.parentlessCommits !== undefined &&
msg.type === MessageType.HistoryReceived
);
}

function convertMessagePayload(msg: any): SendHistoryReceivedMsg | undefined {
if (!isReceivedHistoryReceivedMsg(msg)) {
return undefined;
}

const { commitMapInitializer, parentlessCommits, ...rest } = msg;
return {
...rest,
parentlessCommits: parentlessCommits.map((c) =>
CommitWithChildrenFromJson(c)
),
commitMapInitializer: commitMapInitializer.map(([k, commit]) => [
k,
CommitWithChildrenFromJson(commit)
])
};
}

function main(): void {
const vscode = acquireVsCodeApi();

const redrawGraph = (): void => {
const oldState = convertMessagePayload(vscode.getState());
if (oldState !== undefined) {
drawSequentialHistoryGraph(oldState);
}
};
redrawGraph();

let oldTheme = document.body.className;
const observer = new MutationObserver(() => {
if (document.body.className !== oldTheme) {
oldTheme = document.body.className;
redrawGraph();
}
});
observer.observe(document.body, { attributes: true });

window.addEventListener(
"message",
(
event: MessageEvent<ReceivedHistoryReceivedMsg | StartFetchingHistoryMsg>
) => {
const graphContainer = document.getElementById("graph-container")!;
switch (event.data.type) {
case MessageType.StartFetch:
graphContainer.innerHTML = `Fetching history of ${event.data.projectName}/${event.data.name}`;
break;

case MessageType.HistoryReceived: {
const data = convertMessagePayload(event.data);
if (data === undefined) {
break;
}
drawSequentialHistoryGraph(data);
vscode.setState(data);
break;
}

default:
console.error("Received an invalid message: ", event.data);
}
}
);
}

main();
Loading

0 comments on commit 2755b23

Please sign in to comment.