Browse Source

Adds opening/showing specific commit in the Graph

- Adds Show Commit in Graph to the Commit Details view
Attempts to fix Graph data paging using `--boundary`
main
Eric Amodio 2 years ago
parent
commit
084f580fc4
15 changed files with 237 additions and 44 deletions
  1. +1
    -0
      src/constants.ts
  2. +14
    -0
      src/env/node/git/git.ts
  3. +153
    -15
      src/env/node/git/localGitProvider.ts
  4. +1
    -0
      src/git/models/graph.ts
  5. +2
    -1
      src/git/parsers/logParser.ts
  6. +3
    -2
      src/plus/github/githubGitProvider.ts
  7. +22
    -5
      src/plus/webviews/graph/graphWebview.ts
  8. +1
    -0
      src/plus/webviews/graph/protocol.ts
  9. +14
    -3
      src/webviews/apps/commitDetails/commitDetails.html
  10. +8
    -16
      src/webviews/apps/commitDetails/commitDetails.ts
  11. +4
    -0
      src/webviews/apps/plus/graph/GraphWrapper.tsx
  12. +4
    -0
      src/webviews/apps/shared/components/codicon.ts
  13. +1
    -1
      src/webviews/apps/shared/theme.ts
  14. +8
    -0
      src/webviews/commitDetails/commitDetailsWebviewView.ts
  15. +1
    -1
      src/webviews/commitDetails/protocol.ts

+ 1
- 0
src/constants.ts View File

@ -163,6 +163,7 @@ export const enum Commands {
SearchCommitsInView = 'gitlens.views.searchAndCompare.searchCommits',
SetViewsLayout = 'gitlens.setViewsLayout',
ShowBranchesView = 'gitlens.showBranchesView',
ShowCommitInGraph = 'gitlens.showCommitInGraph',
ShowCommitInView = 'gitlens.showCommitInView',
ShowCommitsInView = 'gitlens.showCommitsInView',
ShowCommitsView = 'gitlens.showCommitsView',

+ 14
- 0
src/env/node/git/git.ts View File

@ -769,6 +769,20 @@ export class Git {
);
}
log2(repoPath: string, ref: string | undefined, ...args: unknown[]) {
const params = ['log', ...args];
if (ref && !GitRevision.isUncommittedStaged(ref)) {
params.push(ref);
}
return this.git<string>(
{ cwd: repoPath, configs: ['-c', 'diff.renameLimit=0', '-c', 'log.showSignature=false'] },
...params,
'--',
);
}
log__file(
repoPath: string,
fileName: string,

+ 153
- 15
src/env/node/git/localGitProvider.ts View File

@ -1609,8 +1609,75 @@ export class LocalGitProvider implements GitProvider, Disposable {
ref?: string;
},
): Promise<GitGraph> {
const scope = getLogScope();
let getLogForRefFn;
if (options?.ref != null) {
async function getLogForRef(this: LocalGitProvider): Promise<GitLog | undefined> {
let log;
const parser = GitLogParser.create<{ sha: string; date: string }>({ sha: '%H', date: '%ct' });
const data = await this.git.log(repoPath, options?.ref, { argsOrFormat: parser.arguments, limit: 0 });
let commit = first(parser.parse(data));
if (commit != null) {
log = await this.getLog(repoPath, {
all: options!.mode !== 'single',
ordering: 'date',
limit: 0,
extraArgs: [`--since="${Number(commit.date)}"`, '--boundary'],
});
let found = log?.commits.has(commit.sha) ?? false;
if (!found) {
Logger.debug(scope, `Could not find commit ${options!.ref}`);
debugger;
}
if (log?.more != null) {
const defaultItemLimit = configuration.get('graph.defaultItemLimit');
if (!found || log.commits.size < defaultItemLimit) {
Logger.debug(scope, 'Loading next page...');
log = await log.more(
(log.commits.size < defaultItemLimit
? defaultItemLimit
: configuration.get('graph.pageItemLimit')) ?? options?.limit,
);
// We need to clear the "pagedCommits", since we want to return the entire set
if (log != null) {
(log as Mutable<typeof log>).pagedCommits = undefined;
}
found = log?.commits.has(commit.sha) ?? false;
if (!found) {
Logger.debug(scope, `Still could not find commit ${options!.ref}`);
commit = undefined;
debugger;
}
}
}
if (!found) {
commit = undefined;
}
options!.ref = commit?.sha;
}
return (
log ??
this.getLog(repoPath, { all: options?.mode !== 'single', ordering: 'date', limit: options?.limit })
);
}
getLogForRefFn = getLogForRef;
}
const [logResult, stashResult, remotesResult] = await Promise.allSettled([
this.getLog(repoPath, { all: true, ordering: 'date', limit: options?.limit }),
getLogForRefFn?.call(this) ??
this.getLog(repoPath, { all: options?.mode !== 'single', ordering: 'date', limit: options?.limit }),
this.getStash(repoPath),
this.getRemotes(repoPath),
]);
@ -1632,9 +1699,10 @@ export class LocalGitProvider implements GitProvider, Disposable {
stash: GitStash | undefined,
remotes: GitRemote[] | undefined,
options?: {
ref?: string;
mode?: 'single' | 'local' | 'all';
branch?: string;
limit?: number;
mode?: 'single' | 'local' | 'all';
ref?: string;
},
): Promise<GitGraph> {
if (log == null) {
@ -1763,6 +1831,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
more: log.hasMore,
},
rows: rows,
sha: options?.ref,
more: async (limit: number | { until: string } | undefined): Promise<GitGraph | undefined> => {
const moreLog = await log.more?.(limit);
@ -2256,23 +2325,79 @@ export class LocalGitProvider implements GitProvider, Disposable {
ref?: string;
since?: number | string;
until?: number | string;
extraArgs?: string[];
},
): Promise<GitLog | undefined> {
const scope = getLogScope();
const limit = options?.limit ?? configuration.get('advanced.maxListItems') ?? 0;
try {
const limit = options?.limit ?? configuration.get('advanced.maxListItems') ?? 0;
const merges = options?.merges == null ? true : options.merges;
const ordering = options?.ordering ?? configuration.get('advanced.commitOrdering');
const similarityThreshold = configuration.get('advanced.similarityThreshold');
const args = [
`--format=${options?.all ? GitLogParser.allFormat : GitLogParser.defaultFormat}`,
'--name-status',
'--full-history',
`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`,
'-m',
];
if (options?.all) {
args.push('--all');
}
if (!merges) {
args.push('--first-parent');
}
if (ordering) {
args.push(`--${ordering}-order`);
}
if (options?.authors?.length) {
args.push(
'--use-mailmap',
'--author',
...options.authors.map(a => `--author=^${a.name} <${a.email}>$`),
);
}
let hasMoreOverride;
if (options?.since) {
hasMoreOverride = true;
args.push(`--since="${options.since}"`);
}
if (options?.until) {
hasMoreOverride = true;
args.push(`--until="${options.until}"`);
}
if (options?.extraArgs?.length) {
if (
options.extraArgs.some(
arg => arg.startsWith('-n') || arg.startsWith('--until=') || arg.startsWith('--since='),
)
) {
hasMoreOverride = true;
}
args.push(...options.extraArgs);
}
if (limit) {
hasMoreOverride = undefined;
args.push(`-n${limit + 1}`);
}
const data = await this.git.log2(repoPath, options?.ref, ...args);
// const parser = GitLogParser.defaultParser;
const data = await this.git.log(repoPath, options?.ref, {
...options,
// args: parser.arguments,
limit: limit,
merges: options?.merges == null ? true : options.merges,
ordering: options?.ordering ?? configuration.get('advanced.commitOrdering'),
similarityThreshold: configuration.get('advanced.similarityThreshold'),
});
// const data = await this.git.log2(repoPath, options?.ref, {
// ...options,
// // args: parser.arguments,
// limit: limit,
// merges: options?.merges == null ? true : options.merges,
// ordering: options?.ordering ?? configuration.get('advanced.commitOrdering'),
// similarityThreshold: configuration.get('advanced.similarityThreshold'),
// });
// const commits = [];
// const entries = parser.parse(data);
@ -2311,12 +2436,18 @@ export class LocalGitProvider implements GitProvider, Disposable {
limit,
false,
undefined,
hasMoreOverride,
);
if (log != null) {
log.query = (limit: number | undefined) => this.getLog(repoPath, { ...options, limit: limit });
if (log.hasMore) {
log.more = this.getLogMoreFn(log, options);
let opts;
if (options != null) {
let extraArgs;
({ extraArgs, ...opts } = options);
}
log.more = this.getLogMoreFn(log, opts);
}
}
@ -2414,12 +2545,19 @@ export class LocalGitProvider implements GitProvider, Disposable {
...options,
limit: moreUntil == null ? moreLimit : 0,
...(timestamp
? { until: timestamp }
? {
until: timestamp,
extraArgs: ['--boundary'],
}
: { ref: moreUntil == null ? `${ref}^` : `${moreUntil}^..${ref}^` }),
});
// If we can't find any more, assume we have everything
if (moreLog == null) return { ...log, hasMore: false, more: undefined };
if (timestamp != null && ref != null && !moreLog.commits.has(ref)) {
debugger;
}
const commits = new Map([...log.commits, ...moreLog.commits]);
const mergedLog: GitLog = {

+ 1
- 0
src/git/models/graph.ts View File

@ -22,6 +22,7 @@ export interface GitGraphRow extends GraphRow {
export interface GitGraph {
readonly repoPath: string;
readonly rows: GitGraphRow[];
readonly sha?: string;
readonly paging?: {
readonly limit: number | undefined;

+ 2
- 1
src/git/parsers/logParser.ts View File

@ -295,6 +295,7 @@ export class GitLogParser {
limit: number | undefined,
reverse: boolean,
range: Range | undefined,
hasMoreOverride?: boolean,
): GitLog | undefined {
if (!data) return undefined;
@ -595,7 +596,7 @@ export class GitLogParser {
count: i,
limit: limit,
range: range,
hasMore: Boolean(truncationCount && i > truncationCount && truncationCount !== 1),
hasMore: hasMoreOverride ?? Boolean(truncationCount && i > truncationCount && truncationCount !== 1),
};
return log;
}

+ 3
- 2
src/plus/github/githubGitProvider.ts View File

@ -1076,9 +1076,10 @@ export class GitHubGitProvider implements GitProvider, Disposable {
remote: GitRemote | undefined,
tags: GitTag[] | undefined,
options?: {
ref?: string;
mode?: 'single' | 'local' | 'all';
branch?: string;
limit?: number;
mode?: 'single' | 'local' | 'all';
ref?: string;
},
): Promise<GitGraph> {
if (log == null) {

+ 22
- 5
src/plus/webviews/graph/graphWebview.ts View File

@ -38,6 +38,10 @@ import {
UpdateSelectionCommandType,
} from './protocol';
export interface ShowCommitInGraphCommandArgs {
sha: string;
}
export interface GraphSelectionChangeEvent {
readonly selection: GitCommit[];
}
@ -74,6 +78,7 @@ export class GraphWebview extends WebviewBase {
private _etagSubscription?: number;
private _etagRepository?: number;
private _selectedSha?: string;
private _repositoryEventsDisposable: Disposable | undefined;
private _repositoryGraph?: GitGraph;
@ -101,6 +106,15 @@ export class GraphWebview extends WebviewBase {
},
},
this.container.subscription.onDidChange(this.onSubscriptionChanged, this),
registerCommand(Commands.ShowCommitInGraph, (args: ShowCommitInGraphCommandArgs) => {
this._selectedSha = args.sha;
if (this._panel == null) {
void this.show();
} else {
// TODO@eamodio we should be smarter here an look for the commit in the saved data before refreshing and only send the selectedSha
this.updateState();
}
}),
);
this.onConfigurationChanged();
@ -123,7 +137,6 @@ export class GraphWebview extends WebviewBase {
}
if (this.repository != null) {
this.resetRepositoryState();
this.updateState();
}
}
@ -301,11 +314,12 @@ export class GraphWebview extends WebviewBase {
}
private async onSelectionChanged(selection: string[]) {
const ref = selection[0];
const sha = selection[0];
this._selectedSha = sha;
let commits: GitCommit[] | undefined;
if (ref != null) {
const commit = await this.repository?.getCommit(ref);
if (sha != null) {
const commit = await this.repository?.getCommit(sha);
if (commit != null) {
commits = [commit];
}
@ -421,14 +435,16 @@ export class GraphWebview extends WebviewBase {
const data = await this.container.git.getCommitsForGraph(
this.repository.path,
this._panel!.webview.asWebviewUri,
{ limit: limit },
{ limit: limit, ref: this._selectedSha ?? 'HEAD' },
);
this._repositoryGraph = data;
this._selectedSha = data.sha;
return {
previewBanner: this.previewBanner,
repositories: formatRepositories(this.container.git.openRepositories),
selectedRepository: this.repository.path,
selectedSha: this._selectedSha,
selectedVisibility: access.visibility,
subscription: access.subscription.current,
allowed: access.allowed,
@ -445,6 +461,7 @@ export class GraphWebview extends WebviewBase {
private resetRepositoryState() {
this._repositoryGraph = undefined;
this._selectedSha = undefined;
}
}

+ 1
- 0
src/plus/webviews/graph/protocol.ts View File

@ -7,6 +7,7 @@ import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol'
export interface State {
repositories?: GraphRepository[];
selectedRepository?: string;
selectedSha?: string;
selectedVisibility?: RepositoryVisibility;
subscription?: Subscription;
allowed?: boolean;

+ 14
- 3
src/webviews/apps/commitDetails/commitDetails.html View File

@ -43,7 +43,7 @@
<a
class="commit-details__commit-action"
href="#"
data-action="commit-actions-pin"
data-action="pin"
aria-label="Pin Commit to View"
title="Pin Commit to View"
><code-icon icon="pin" data-region="commit-pin"></code-icon
@ -51,7 +51,8 @@
<a
class="commit-details__commit-action"
href="#"
data-action="commit-actions-sha"
data-action="commit-actions"
data-action-type="sha"
aria-label="Copy SHA
[⌥] Pick Commit..."
title="Copy SHA
@ -63,7 +64,17 @@
<a
class="commit-details__commit-action"
href="#"
data-action="commit-actions-more"
data-action="commit-actions"
data-action-type="graph"
aria-label="Show Commit on Commit Graph"
title="Show Commit on Commit Graph"
><code-icon icon="graph"></code-icon
></a>
<a
class="commit-details__commit-action"
href="#"
data-action="commit-actions"
data-action-type="more"
aria-label="Show Commit Actions"
title="Show Commit Actions"
><code-icon icon="kebab-vertical"></code-icon

+ 8
- 16
src/webviews/apps/commitDetails/commitDetails.ts View File

@ -2,7 +2,7 @@
import type { Serialized } from '../../../system/serialize';
import type { IpcMessage } from '../../../webviews/protocol';
import { onIpc } from '../../../webviews/protocol';
import type { State } from '../../commitDetails/protocol';
import type { CommitActionsParams, State } from '../../commitDetails/protocol';
import {
AutolinkSettingsCommandType,
CommitActionsCommandType,
@ -64,8 +64,7 @@ export class CommitDetailsApp extends App> {
DOM.on<FileChangeItem, FileChangeItemEventDetail>('file-change-item', 'file-more-actions', e =>
this.onFileMoreActions(e.detail),
),
DOM.on('[data-action="commit-actions-sha"]', 'click', e => this.onCommitShaActions(e)),
DOM.on('[data-action="commit-actions-more"]', 'click', e => this.onCommitMoreActions(e)),
DOM.on('[data-action="commit-actions"]', 'click', e => this.onCommitActions(e)),
DOM.on('[data-action="pick-commit"]', 'click', e => this.onPickCommit(e)),
DOM.on('[data-action="search-commit"]', 'click', e => this.onSearchCommit(e)),
DOM.on('[data-action="autolink-settings"]', 'click', e => this.onAutolinkSettings(e)),
@ -82,7 +81,7 @@ export class CommitDetailsApp extends App> {
$next?.focus();
}
}),
DOM.on('[data-action="commit-actions-pin"]', 'click', e => this.onTogglePin(e)),
DOM.on('[data-action="pin"]', 'click', e => this.onTogglePin(e)),
DOM.on<WebviewPane, WebviewPaneExpandedChangeEventDetail>(
'[data-region="rich-pane"]',
'expanded-change',
@ -173,24 +172,17 @@ export class CommitDetailsApp extends App> {
this.sendCommand(FileActionsCommandType, e);
}
private onCommitMoreActions(e: MouseEvent) {
private onCommitActions(e: MouseEvent) {
e.preventDefault();
if (this.state.selected === undefined) {
e.stopPropagation();
return;
}
this.sendCommand(CommitActionsCommandType, { action: 'more' });
}
private onCommitShaActions(e: MouseEvent) {
e.preventDefault();
if (this.state.selected === undefined) {
e.stopPropagation();
return;
}
const action = (e.target as HTMLElement)?.getAttribute('data-action-type');
if (action == null) return;
this.sendCommand(CommitActionsCommandType, { action: 'sha', alt: e.altKey });
this.sendCommand(CommitActionsCommandType, { action: action as CommitActionsParams['action'], alt: e.altKey });
}
renderCommit(state: Serialized<State>): state is CommitState {
@ -228,7 +220,7 @@ export class CommitDetailsApp extends App> {
}
renderPin(state: CommitState) {
const $el = document.querySelector<HTMLElement>('[data-action="commit-actions-pin"]');
const $el = document.querySelector<HTMLElement>('[data-action="pin"]');
if ($el == null) {
return;
}

+ 4
- 0
src/webviews/apps/plus/graph/GraphWrapper.tsx View File

@ -117,6 +117,7 @@ export function GraphWrapper({
repositories = [],
rows = [],
selectedRepository,
selectedSha,
subscription,
allowed,
config,
@ -135,6 +136,7 @@ export function GraphWrapper({
const [currentRepository, setCurrentRepository] = useState<GraphRepository | undefined>(
reposList.find(item => item.path === selectedRepository),
);
const [currentSha, setSelectedSha] = useState(selectedSha);
const [graphColSettings, setGraphColSettings] = useState(getGraphColSettingsModel(config));
const [pagingState, setPagingState] = useState(paging);
const [isLoading, setIsLoading] = useState(false);
@ -179,6 +181,7 @@ export function GraphWrapper({
setGraphList(state.rows ?? []);
setReposList(state.repositories ?? []);
setCurrentRepository(reposList.find(item => item.path === state.selectedRepository));
setSelectedSha(state.selectedSha);
setGraphColSettings(getGraphColSettingsModel(state.config));
setPagingState(state.paging);
setIsLoading(false);
@ -402,6 +405,7 @@ export function GraphWrapper({
getExternalIcon={getIconElementLibrary}
graphRows={graphList}
height={mainHeight}
isSelectedBySha={currentSha ? { [currentSha]: true } : undefined}
hasMoreCommits={pagingState?.more}
isLoadingRows={isLoading}
nonce={nonce}

+ 4
- 0
src/webviews/apps/shared/components/codicon.ts View File

@ -1500,6 +1500,10 @@ export class CodeIcon extends LitElement {
position: relative;
left: 1px;
}
:host([icon='graph']):before {
font-family: 'glicons';
content: '\\f102';
}
`;
@property()

+ 1
- 1
src/webviews/apps/shared/theme.ts View File

@ -8,7 +8,7 @@ export function initializeAndWatchThemeColors(callback?: () => void) {
const isLightTheme =
body.classList.contains('vscode-light') || body.classList.contains('vscode-high-contrast-light');
const isHighContrastTheme = body.classList.contains('vscode-high-contrast');
// const isHighContrastTheme = body.classList.contains('vscode-high-contrast');
const bodyStyle = body.style;

+ 8
- 0
src/webviews/commitDetails/commitDetailsWebviewView.ts View File

@ -9,6 +9,7 @@ import type { GitFileChange } from '../../git/models/file';
import { GitFile } from '../../git/models/file';
import type { IssueOrPullRequest } from '../../git/models/issue';
import type { PullRequest } from '../../git/models/pullRequest';
import type { ShowCommitInGraphCommandArgs } from '../../plus/webviews/graph/graphWebview';
import { executeCommand } from '../../system/command';
import { debug } from '../../system/decorators/log';
import type { Deferrable } from '../../system/function';
@ -180,6 +181,13 @@ export class CommitDetailsWebviewView extends WebviewViewBase
case CommitActionsCommandType.method:
onIpc(CommitActionsCommandType, e, params => {
switch (params.action) {
case 'graph':
if (this._context.commit == null) return;
void executeCommand<ShowCommitInGraphCommandArgs>(Commands.ShowCommitInGraph, {
sha: this._context.commit.sha,
});
break;
case 'more':
this.showCommitActions();
break;

+ 1
- 1
src/webviews/commitDetails/protocol.ts View File

@ -45,7 +45,7 @@ export type ShowCommitDetailsViewCommandArgs = string[];
// COMMANDS
export interface CommitActionsParams {
action: 'sha' | 'more';
action: 'graph' | 'more' | 'sha';
alt?: boolean;
}
export const CommitActionsCommandType = new IpcCommandType<CommitActionsParams>('commit/actions');

Loading…
Cancel
Save