Skip to content
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@
"command": "git-graph.version",
"title": "Get Version Information"
},
{
"category": "Git Graph",
"command": "git-graph.goToCommit",
"title": "Go to Commit",
"icon": "$(git-commit)",
"enablement": "isInDiffEditor || resourceScheme == scm-history-item"
},
{
"category": "Git Graph",
"command": "git-graph.openFile",
Expand Down Expand Up @@ -1518,12 +1525,21 @@
},
"menus": {
"commandPalette": [
{
"command": "git-graph.goToCommit",
"when": "isInDiffEditor || resourceScheme == scm-history-item"
},
{
"command": "git-graph.openFile",
"when": "isInDiffEditor && resourceScheme == git-graph && git-graph:codiconsSupported"
}
],
"editor/title": [
{
"command": "git-graph.goToCommit",
"group": "navigation@-150",
"when": "isInDiffEditor || resourceScheme == scm-history-item"
},
{
"command": "git-graph.openFile",
"group": "navigation",
Expand All @@ -1541,6 +1557,13 @@
"command": "git-graph.view",
"group": "inline"
}
],
"editor/context": [
{
"command": "git-graph.goToCommit",
"group": "navigation@1",
"when": "isInDiffEditor || resourceScheme == scm-history-item"
}
]
}
},
Expand Down
61 changes: 60 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CodeReviewData, CodeReviews, ExtensionState } from './extensionState';
import { GitGraphView } from './gitGraphView';
import { Logger } from './logger';
import { RepoManager } from './repoManager';
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, VsCodeVersionRequirement, abbrevCommit, abbrevText, copyToClipboard, doesVersionMeetRequirement, getExtensionVersion, getPathFromUri, getRelativeTimeDiff, getRepoName, getSortedRepositoryPaths, isPathInWorkspace, openFile, resolveToSymbolicPath, showErrorMessage, showInformationMessage } from './utils';
import { GitExecutable, UNABLE_TO_FIND_GIT_MSG, VsCodeVersionRequirement, abbrevCommit, abbrevText, copyToClipboard, doesVersionMeetRequirement, getExtensionVersion, getPathFromUri, getRelativeTimeDiff, getRepoName, getSortedRepositoryPaths, isPathInWorkspace, openFile, resolveToSymbolicPath, showErrorMessage, showInformationMessage, showWarningMessage } from './utils';
import { Disposable } from './utils/disposable';
import { Event } from './utils/event';

Expand Down Expand Up @@ -56,6 +56,7 @@ export class CommandManager extends Disposable {
this.registerCommand('git-graph.resumeWorkspaceCodeReview', () => this.resumeWorkspaceCodeReview());
this.registerCommand('git-graph.version', () => this.version());
this.registerCommand('git-graph.openFile', (arg) => this.openFile(arg));
this.registerCommand('git-graph.goToCommit', (arg) => this.goToCommit(arg));

this.registerDisposable(
onDidChangeGitExecutable((gitExecutable) => {
Expand Down Expand Up @@ -345,6 +346,64 @@ export class CommandManager extends Disposable {
}
}

/**
* Opens a position commit in Git Graph, based on a Git Graph URI (from the Diff View).
* The method run when the `git-graph.goToCommit` command is invoked.
* @param arg The Git Graph URI.
*/
private async goToCommit(arg?: vscode.Uri) {
const uri = arg || vscode.window.activeTextEditor?.document.uri;
const gitExtension = vscode.extensions.getExtension('vscode.git')?.exports;
const api = gitExtension.getAPI(1);
if (!gitExtension) {
showErrorMessage('Unable to load Git extension.');
return;
}

if (typeof uri === 'object' && uri) {
let commitHash = '';
let repository = undefined;

if (uri.scheme === 'git-graph') {
let diffDocUri = decodeDiffDocUri(uri);
commitHash = diffDocUri.commit;
repository = api.getRepository(diffDocUri.repo);
}
if (uri.scheme === 'git' || uri.scheme === 'gitlens') {
commitHash = JSON.parse(uri.query).ref;
repository = api.getRepository(uri);
}
if (uri.scheme === 'scm-history-item') {
commitHash = uri.path.split('..')[1];
repository = api.getRepository(uri);
}

if (commitHash !== '') {
if (!repository) {
showWarningMessage('Warning: no matching Git repository found for this file.');
}

if (commitHash.endsWith('^')) {
commitHash = commitHash.slice(0, -1); // it is difficult to find parents, especially when there may be more than one
}

if (GitGraphView.currentPanel) { // graph exist
GitGraphView.currentPanel.isPanelVisible = true;
await this.view(repository);
GitGraphView.scrollToCommit(commitHash, true, false, true, true);
} else { // graph is creating
await this.view(repository);
GitGraphView.scrollToCommit(commitHash, true, false, true, true);
}
return;
} else {
return showErrorMessage('Unable Go To Commit: The commit hash not found.');
}
} else {
return showErrorMessage('Unable Go To Commit: The command was not called with the required arguments.');
}
}


/* Helper Methods */

Expand Down
35 changes: 34 additions & 1 deletion src/gitGraphView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class GitGraphView extends Disposable {
private readonly repoManager: RepoManager;
private readonly logger: Logger;
private isGraphViewLoaded: boolean = false;
private isPanelVisible: boolean = true;
public isPanelVisible: boolean = true;
private currentRepo: string | null = null;
private loadViewTo: LoadGitGraphViewTo = null; // Is used by the next call to getHtmlForWebview, and is then reset to null

Expand Down Expand Up @@ -64,6 +64,20 @@ export class GitGraphView extends Disposable {
}
}

/**
* Scroll the view to a commit (if it exists).
* @param hash The hash of the commit to scroll to.
* @param alwaysCenterCommit TRUE => Always scroll the view to be centered on the commit. FALSE => Don't scroll the view if the commit is already within the visible portion of commits.
* @param flash Should the commit flash after it has been scrolled to.
* @param openDetails Open details of the specified commit.
* @param persistently Persistently find the commit even if it is not exists.
*/
public static scrollToCommit(hash: string, alwaysCenterCommit: boolean, flash: boolean = false, openDetails: boolean = false, persistently: boolean = false) {
if (GitGraphView.currentPanel) {
GitGraphView.currentPanel.respondScrollToCommit(hash, alwaysCenterCommit, flash, openDetails, persistently);
}
}

/**
* Creates a Git Graph View.
* @param extensionPath The absolute file path of the directory containing the extension.
Expand Down Expand Up @@ -818,6 +832,25 @@ export class GitGraphView extends Disposable {
loadViewTo: loadViewTo
});
}

/**
* Call the command to scroll to the specified commit to the front-end.
* @param hash The hash of the commit to scroll to.
* @param alwaysCenterCommit TRUE => Always scroll the view to be centered on the commit. FALSE => Don't scroll the view if the commit is already within the visible portion of commits.
* @param flash Should the commit flash after it has been scrolled to.
* @param openDetails Open details of the specified commit.
* @param persistently Persistently find the commit even if it is not exists.
*/
public respondScrollToCommit(hash: string, alwaysCenterCommit: boolean, flash: boolean = false, openDetails: boolean = false, persistently: boolean = false) {
this.sendMessage({
command: 'scrollToCommit',
hash: hash,
alwaysCenterCommit: alwaysCenterCommit,
flash: flash,
openDetails: openDetails,
persistently: persistently
});
}
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,15 @@ export interface ResponseLoadRepos extends BaseMessage {
readonly loadViewTo: LoadGitGraphViewTo;
}

export interface ResponseScrollToCommit extends BaseMessage {
readonly command: 'scrollToCommit';
readonly hash: string;
readonly alwaysCenterCommit: boolean;
readonly flash: boolean;
readonly openDetails: boolean;
readonly persistently: boolean;
}

export const enum MergeActionOn {
Branch = 'Branch',
RemoteTrackingBranch = 'Remote-tracking Branch',
Expand Down Expand Up @@ -1363,6 +1372,7 @@ export type ResponseMessage =
| ResponseLoadConfig
| ResponseLoadRepoInfo
| ResponseLoadRepos
| ResponseScrollToCommit
| ResponseMerge
| ResponseOpenExtensionSettings
| ResponseOpenExternalDirDiff
Expand Down
8 changes: 8 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,14 @@ export function showInformationMessage(message: string) {
return vscode.window.showInformationMessage(message).then(() => { }, () => { });
}

/**
* Show a Visual Studio Code Warning Message Dialog with the specified message.
* @param message The message to show.
*/
export function showWarningMessage(message: string) {
return vscode.window.showWarningMessage(message).then(() => { }, () => { });
}

/**
* Show a Visual Studio Code Error Message Dialog with the specified message.
* @param message The message to show.
Expand Down
79 changes: 74 additions & 5 deletions web/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ class GitGraphView {
};
private loadViewTo: GG.LoadGitGraphViewTo = null;

public scrollToCommitArgs: {
hash: string,
alwaysCenterCommit: boolean,
flash: boolean,
openDetails: boolean,
persistently: boolean
};

private readonly graph: Graph;
private readonly config: Config;

Expand Down Expand Up @@ -73,6 +81,14 @@ class GitGraphView {
requestingConfig: false
};

this.scrollToCommitArgs = {
hash: '',
alwaysCenterCommit: false,
flash: false,
openDetails: false,
persistently: false
};

this.controlsElem = document.getElementById('controls')!;
this.tableElem = document.getElementById('commitTable')!;
this.tableColHeadersElem = document.getElementById('tableColHeaders')!;
Expand Down Expand Up @@ -165,7 +181,7 @@ class GitGraphView {
currentBtn.innerHTML = SVG_ICONS.current;
currentBtn.addEventListener('click', () => {
if (this.commitHead) {
this.scrollToCommit(this.commitHead, true, true);
this.scrollToCommit(this.commitHead, true, true, false, true);
}
});
fetchBtn.title = 'Fetch' + (this.config.fetchAndPrune ? ' & Prune' : '') + ' from Remote(s)';
Expand Down Expand Up @@ -439,6 +455,10 @@ class GitGraphView {
}

this.finaliseRepoLoad(true);

if (this.scrollToCommitArgs.persistently) {
this.scrollToCommit(this.scrollToCommitArgs.hash, this.scrollToCommitArgs.alwaysCenterCommit, this.scrollToCommitArgs.flash, this.scrollToCommitArgs.openDetails, this.scrollToCommitArgs.persistently);
}
}

private finaliseRepoLoad(didLoadRepoData: boolean) {
Expand Down Expand Up @@ -581,7 +601,16 @@ class GitGraphView {
return options;
}
public getCommitId(hash: string) {
return typeof this.commitLookup[hash] === 'number' ? this.commitLookup[hash] : null;
if (typeof this.commitLookup[hash] === 'number') {
return this.commitLookup[hash];
}
// If a full match isn't found, try to find a matching partial hash
for (const key in this.commitLookup) {
if (key.startsWith(hash)) {
return this.commitLookup[key];
}
}
return null;
}

private getCommitOfElem(elem: HTMLElement) {
Expand Down Expand Up @@ -1971,11 +2000,34 @@ class GitGraphView {
* @param hash The hash of the commit to scroll to.
* @param alwaysCenterCommit TRUE => Always scroll the view to be centered on the commit. FALSE => Don't scroll the view if the commit is already within the visible portion of commits.
* @param flash Should the commit flash after it has been scrolled to.
* @param openDetails Open details of the specified commit.
* @param persistently Persistently find the commit even if it is not exists.
*/
public scrollToCommit(hash: string, alwaysCenterCommit: boolean, flash: boolean = false) {
const elem = findCommitElemWithId(getCommitElems(), this.getCommitId(hash));
if (elem === null) return;
public scrollToCommit(hash: string, alwaysCenterCommit: boolean, flash: boolean = false, openDetails: boolean = false, persistently: boolean = false) {
this.scrollToCommitArgs.persistently = false;

const elem = findCommitElemWithId(getCommitElems(), this.getCommitId(hash));
if (elem === null) {
if (persistently) {
// Scroll to the last loaded commit for trigger loadMoreCommits()
const commits = document.getElementsByClassName('commit');
if (commits.length === 0) {
return;
}
const lastCommit = commits[commits.length - 1];
lastCommit.scrollIntoView();

this.scrollToCommitArgs = {
hash: hash,
alwaysCenterCommit: alwaysCenterCommit,
flash: flash,
openDetails: openDetails,
persistently: persistently
};
}
// Do nothing
return;
}
let elemTop = this.controlsElem.clientHeight + elem.offsetTop;
if (alwaysCenterCommit || elemTop - 8 < this.viewElem.scrollTop || elemTop + 32 - this.viewElem.clientHeight > this.viewElem.scrollTop) {
this.viewElem.scroll(0, this.controlsElem.clientHeight + elem.offsetTop + 12 - this.viewElem.clientHeight / 2);
Expand All @@ -1987,6 +2039,10 @@ class GitGraphView {
elem.classList.remove('flash');
}, 850);
}

if (openDetails) {
this.loadCommitDetails(elem);
}
}

private loadMoreCommits() {
Expand Down Expand Up @@ -3456,6 +3512,19 @@ window.addEventListener('load', () => {
case 'loadRepos':
gitGraph.loadRepos(msg.repos, msg.lastActiveRepo, msg.loadViewTo);
break;
case 'scrollToCommit':
if (VSCODE_API.getState()?.currentRepoLoading) { // if graph is creating
gitGraph.scrollToCommitArgs = {
hash: msg.hash,
alwaysCenterCommit: msg.alwaysCenterCommit,
flash: msg.flash,
openDetails: msg.openDetails,
persistently: msg.persistently
};
} else { // if graph exist
gitGraph.scrollToCommit(msg.hash, msg.alwaysCenterCommit, msg.flash, msg.openDetails, msg.persistently);
}
break;
case 'merge':
refreshOrDisplayError(msg.error, 'Unable to Merge ' + msg.actionOn);
break;
Expand Down