diff --git a/README.md b/README.md
index 348d72e..28fe8ec 100644
--- a/README.md
+++ b/README.md
@@ -719,14 +719,15 @@ GitLens is highly customizable and provides many configuration settings to allow
## Status Bar Settings [#](#status-bar-settings- 'Status Bar Settings')
-| Name | Description |
-| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `gitlens.statusBar.alignment` | Specifies the blame alignment in the status bar
`left` - aligns to the left `right` - aligns to the right |
-| `gitlens.statusBar.command` | Specifies the command to be executed when the blame status bar item is clicked
`gitlens.toggleFileBlame` - toggles file blame annotations `gitlens.diffWithPrevious` - opens line changes with the previous revision `gitlens.diffWithWorking` - opens line changes with the working file `gitlens.revealCommitInView` - reveals the commit in the Side Bar `gitlens.showCommitsInView` - searches for the commit `gitlens.toggleCodeLens` - toggles the Git code lens `gitlens.showQuickCommitDetails` - shows details of the commit `gitlens.showQuickCommitFileDetails` - show file details of the commit `gitlens.showQuickFileHistory` - shows the current file history `gitlens.showQuickRepoHistory` - shows the current branch history |
-| `gitlens.statusBar.dateFormat` | Specifies how to format absolute dates (e.g. using the `${date}` token) in the blame information in the status bar. See the [Moment.js docs](https://momentjs.com/docs/#/displaying/format/) for valid formats |
-| `gitlens.statusBar.enabled` | Specifies whether to provide blame information in the status bar |
-| `gitlens.statusBar.format` | Specifies the format of the blame information in the status bar. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs. Date formatting is controlled by the `gitlens.statusBar.dateFormat` setting |
-| `gitlens.statusBar.reduceFlicker` | Specifies whether to avoid clearing the previous blame information when changing lines to reduce status bar "flashing" |
+| Name | Description |
+| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `gitlens.statusBar.alignment` | Specifies the blame alignment in the status bar
`left` - aligns to the left `right` - aligns to the right |
+| `gitlens.statusBar.command` | Specifies the command to be executed when the blame status bar item is clicked
`gitlens.toggleFileBlame` - toggles file blame annotations `gitlens.diffWithPrevious` - opens line changes with the previous revision `gitlens.diffWithWorking` - opens line changes with the working file `gitlens.revealCommitInView` - reveals the commit in the Side Bar `gitlens.showCommitsInView` - searches for the commit `gitlens.toggleCodeLens` - toggles the Git code lens `gitlens.showQuickCommitDetails` - shows details of the commit `gitlens.showQuickCommitFileDetails` - show file details of the commit `gitlens.showQuickFileHistory` - shows the current file history `gitlens.showQuickRepoHistory` - shows the current branch history |
+| `gitlens.statusBar.dateFormat` | Specifies how to format absolute dates (e.g. using the `${date}` token) in the blame information in the status bar. See the [Moment.js docs](https://momentjs.com/docs/#/displaying/format/) for valid formats |
+| `gitlens.statusBar.enabled` | Specifies whether to provide blame information in the status bar |
+| `gitlens.statusBar.format` | Specifies the format of the blame information in the status bar. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs. Date formatting is controlled by the `gitlens.statusBar.dateFormat` setting |
+| `gitlens.statusBar.pullRequests.enabled` | Specifies whether to provide information about the Pull Request (if any) that introduced the commit in the status bar. Requires a connection to a supported remote service (e.g. GitHub) |
+| `gitlens.statusBar.reduceFlicker` | Specifies whether to avoid clearing the previous blame information when changing lines to reduce status bar "flashing" |
## Hover Settings [#](#hover-settings- 'Hover Settings')
diff --git a/package.json b/package.json
index 02d0a47..4063f25 100644
--- a/package.json
+++ b/package.json
@@ -1571,10 +1571,16 @@
},
"gitlens.statusBar.format": {
"type": "string",
- "default": "${author}, ${agoOrDate}",
+ "default": "${author}, ${agoOrDate}${' via 'pullRequest}",
"markdownDescription": "Specifies the format of the blame information in the status bar. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs. Date formatting is controlled by the `#gitlens.statusBar.dateFormat#` setting",
"scope": "window"
},
+ "gitlens.statusBar.pullRequests.enabled": {
+ "type": "boolean",
+ "default": true,
+ "markdownDescription": "Specifies whether to provide information about the Pull Request (if any) that introduced the commit in the status bar. Requires a connection to a supported remote service (e.g. GitHub)",
+ "scope": "window"
+ },
"gitlens.statusBar.reduceFlicker": {
"type": "boolean",
"default": false,
diff --git a/src/config.ts b/src/config.ts
index 08efdee..d1d5a44 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -114,6 +114,9 @@ export interface Config {
enabled: boolean;
format: string;
reduceFlicker: boolean;
+ pullRequests: {
+ enabled: boolean;
+ };
};
strings: {
codeLens: {
diff --git a/src/statusbar/statusBarController.ts b/src/statusbar/statusBarController.ts
index 6c947f9..909a793 100644
--- a/src/statusbar/statusBarController.ts
+++ b/src/statusbar/statusBarController.ts
@@ -1,17 +1,28 @@
'use strict';
-import { ConfigurationChangeEvent, Disposable, StatusBarAlignment, StatusBarItem, TextEditor, window } from 'vscode';
+import {
+ CancellationToken,
+ CancellationTokenSource,
+ ConfigurationChangeEvent,
+ Disposable,
+ StatusBarAlignment,
+ StatusBarItem,
+ TextEditor,
+ window,
+} from 'vscode';
import { Commands } from '../commands';
import { configuration, StatusBarCommand } from '../configuration';
-import { isTextEditor } from '../constants';
+import { GlyphChars, isTextEditor } from '../constants';
import { Container } from '../container';
-import { CommitFormatter, GitCommit } from '../git/git';
+import { CommitFormatter, GitBlameCommit, PullRequest } from '../git/git';
import { LinesChangeEvent } from '../trackers/gitLineTracker';
-import { debug } from '../system';
+import { debug, Promises } from '../system';
+import { LogCorrelationContext, Logger } from '../logger';
export class StatusBarController implements Disposable {
- private _blameStatusBarItem: StatusBarItem | undefined;
+ private _cancellation: CancellationTokenSource | undefined;
private readonly _disposable: Disposable;
- private _modeStatusBarItem: StatusBarItem | undefined;
+ private _statusBarBlame: StatusBarItem | undefined;
+ private _statusBarMode: StatusBarItem | undefined;
constructor() {
this._disposable = Disposable.from(configuration.onDidChange(this.onConfigurationChanged, this));
@@ -21,8 +32,8 @@ export class StatusBarController implements Disposable {
dispose() {
this.clearBlame();
- this._blameStatusBarItem?.dispose();
- this._modeStatusBarItem?.dispose();
+ this._statusBarBlame?.dispose();
+ this._statusBarMode?.dispose();
Container.lineTracker.stop(this);
this._disposable.dispose();
@@ -41,22 +52,22 @@ export class StatusBarController implements Disposable {
: StatusBarAlignment.Left;
if (configuration.changed(e, 'mode', 'statusBar', 'alignment')) {
- if (this._modeStatusBarItem?.alignment !== alignment) {
- this._modeStatusBarItem?.dispose();
- this._modeStatusBarItem = undefined;
+ if (this._statusBarMode?.alignment !== alignment) {
+ this._statusBarMode?.dispose();
+ this._statusBarMode = undefined;
}
}
- this._modeStatusBarItem =
- this._modeStatusBarItem ??
+ this._statusBarMode =
+ this._statusBarMode ??
window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 999 : 1);
- this._modeStatusBarItem.command = Commands.SwitchMode;
- this._modeStatusBarItem.text = mode.statusBarItemName;
- this._modeStatusBarItem.tooltip = 'Switch GitLens Mode';
- this._modeStatusBarItem.show();
+ this._statusBarMode.command = Commands.SwitchMode;
+ this._statusBarMode.text = mode.statusBarItemName;
+ this._statusBarMode.tooltip = 'Switch GitLens Mode';
+ this._statusBarMode.show();
} else {
- this._modeStatusBarItem?.dispose();
- this._modeStatusBarItem = undefined;
+ this._statusBarMode?.dispose();
+ this._statusBarMode = undefined;
}
}
@@ -67,16 +78,16 @@ export class StatusBarController implements Disposable {
Container.config.statusBar.alignment !== 'left' ? StatusBarAlignment.Right : StatusBarAlignment.Left;
if (configuration.changed(e, 'statusBar', 'alignment')) {
- if (this._blameStatusBarItem?.alignment !== alignment) {
- this._blameStatusBarItem?.dispose();
- this._blameStatusBarItem = undefined;
+ if (this._statusBarBlame?.alignment !== alignment) {
+ this._statusBarBlame?.dispose();
+ this._statusBarBlame = undefined;
}
}
- this._blameStatusBarItem =
- this._blameStatusBarItem ??
+ this._statusBarBlame =
+ this._statusBarBlame ??
window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 1000 : 0);
- this._blameStatusBarItem.command = Container.config.statusBar.command;
+ this._statusBarBlame.command = Container.config.statusBar.command;
if (configuration.changed(e, 'statusBar', 'enabled')) {
Container.lineTracker.start(
@@ -87,8 +98,8 @@ export class StatusBarController implements Disposable {
} else if (configuration.changed(e, 'statusBar', 'enabled')) {
Container.lineTracker.stop(this);
- this._blameStatusBarItem?.dispose();
- this._blameStatusBarItem = undefined;
+ this._statusBarBlame?.dispose();
+ this._statusBarBlame = undefined;
}
}
@@ -110,7 +121,7 @@ export class StatusBarController implements Disposable {
if (!e.pending && e.selections != null) {
const state = Container.lineTracker.getState(e.selections[0].active);
if (state?.commit != null) {
- this.updateBlame(state.commit, e.editor!);
+ void this.updateBlame(e.editor!, state.commit);
return;
}
@@ -124,53 +135,118 @@ export class StatusBarController implements Disposable {
}
clearBlame() {
- this._blameStatusBarItem?.hide();
+ this._cancellation?.cancel();
+ this._statusBarBlame?.hide();
}
- private updateBlame(commit: GitCommit, editor: TextEditor) {
+ @debug({ args: false })
+ private async updateBlame(editor: TextEditor, commit: GitBlameCommit, options?: { pr?: PullRequest | undefined }) {
const cfg = Container.config.statusBar;
- if (!cfg.enabled || this._blameStatusBarItem == null || !isTextEditor(editor)) return;
+ if (!cfg.enabled || this._statusBarBlame == null || !isTextEditor(editor)) return;
+
+ const cc = Logger.getCorrelationContext();
+
+ // TODO: Make this configurable?
+ const timeout = 100;
+ const [getBranchAndTagTips, pr] = await Promise.all([
+ CommitFormatter.has(cfg.format, 'tips')
+ ? Container.git.getBranchesAndTagsTipsFn(commit.repoPath)
+ : undefined,
+ cfg.pullRequests.enabled &&
+ CommitFormatter.has(
+ cfg.format,
+ 'pullRequest',
+ 'pullRequestAgo',
+ 'pullRequestAgoOrDate',
+ 'pullRequestDate',
+ 'pullRequestState',
+ )
+ ? options?.pr ?? this.getPullRequest(commit, { timeout: timeout })
+ : undefined,
+ ]);
+
+ if (pr != null) {
+ this._cancellation?.cancel();
+ this._cancellation = new CancellationTokenSource();
+ void this.waitForPendingPullRequest(editor, commit, pr, this._cancellation.token, timeout, cc);
+ }
- this._blameStatusBarItem.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, {
- messageTruncateAtNewLine: true,
+ this._statusBarBlame.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, {
dateFormat: cfg.dateFormat === null ? Container.config.defaultDateFormat : cfg.dateFormat,
+ getBranchAndTagTips: getBranchAndTagTips,
+ messageTruncateAtNewLine: true,
+ pullRequestOrRemote: pr,
})}`;
switch (cfg.command) {
case StatusBarCommand.ToggleFileBlame:
- this._blameStatusBarItem.tooltip = 'Toggle File Blame Annotations';
+ this._statusBarBlame.tooltip = 'Toggle File Blame Annotations';
break;
case StatusBarCommand.DiffWithPrevious:
- this._blameStatusBarItem.command = Commands.DiffLineWithPrevious;
- this._blameStatusBarItem.tooltip = 'Open Line Changes with Previous Revision';
+ this._statusBarBlame.command = Commands.DiffLineWithPrevious;
+ this._statusBarBlame.tooltip = 'Open Line Changes with Previous Revision';
break;
case StatusBarCommand.DiffWithWorking:
- this._blameStatusBarItem.command = Commands.DiffLineWithWorking;
- this._blameStatusBarItem.tooltip = 'Open Line Changes with Working File';
+ this._statusBarBlame.command = Commands.DiffLineWithWorking;
+ this._statusBarBlame.tooltip = 'Open Line Changes with Working File';
break;
case StatusBarCommand.ToggleCodeLens:
- this._blameStatusBarItem.tooltip = 'Toggle Git CodeLens';
+ this._statusBarBlame.tooltip = 'Toggle Git CodeLens';
break;
case StatusBarCommand.RevealCommitInView:
- this._blameStatusBarItem.tooltip = 'Reveal Commit in the Side Bar';
+ this._statusBarBlame.tooltip = 'Reveal Commit in the Side Bar';
break;
case StatusBarCommand.ShowCommitsInView:
- this._blameStatusBarItem.tooltip = 'Search for Commit';
+ this._statusBarBlame.tooltip = 'Search for Commit';
break;
case StatusBarCommand.ShowQuickCommitDetails:
- this._blameStatusBarItem.tooltip = 'Show Commit';
+ this._statusBarBlame.tooltip = 'Show Commit';
break;
case StatusBarCommand.ShowQuickCommitFileDetails:
- this._blameStatusBarItem.tooltip = 'Show Commit (file)';
+ this._statusBarBlame.tooltip = 'Show Commit (file)';
break;
case StatusBarCommand.ShowQuickFileHistory:
- this._blameStatusBarItem.tooltip = 'Show File History';
+ this._statusBarBlame.tooltip = 'Show File History';
break;
case StatusBarCommand.ShowQuickCurrentBranchHistory:
- this._blameStatusBarItem.tooltip = 'Show Branch History';
+ this._statusBarBlame.tooltip = 'Show Branch History';
break;
}
- this._blameStatusBarItem.show();
+ this._statusBarBlame.show();
+ }
+
+ private async getPullRequest(commit: GitBlameCommit, { timeout }: { timeout?: number } = {}) {
+ const remote = await Container.git.getRemoteWithApiProvider(commit.repoPath);
+ if (remote?.provider == null) return undefined;
+
+ const { provider } = remote;
+ try {
+ return await Container.git.getPullRequestForCommit(commit.ref, provider, { timeout: timeout });
+ } catch (ex) {
+ return ex;
+ }
+ }
+
+ private async waitForPendingPullRequest(
+ editor: TextEditor,
+ commit: GitBlameCommit,
+ pr: PullRequest | Promises.CancellationError> | undefined,
+ cancellationToken: CancellationToken,
+ timeout: number,
+ cc: LogCorrelationContext | undefined,
+ ) {
+ if (cancellationToken.isCancellationRequested || !(pr instanceof Promises.CancellationError)) return;
+
+ // If the PR timed out, refresh the status bar once it completes
+ Logger.debug(cc, `${GlyphChars.Dot} pull request query took too long (over ${timeout} ms)`);
+
+ pr = await pr.promise;
+
+ if (cancellationToken.isCancellationRequested) return;
+
+ Logger.debug(cc, `${GlyphChars.Dot} pull request query completed; refreshing...`);
+
+ void this.updateBlame(editor, commit, { pr: pr });
}
}
diff --git a/src/system/promise.ts b/src/system/promise.ts
index 95c76c7..3b1b1a0 100644
--- a/src/system/promise.ts
+++ b/src/system/promise.ts
@@ -15,13 +15,15 @@ export class CancellationErrorWithId extends CancellationErro
}
export function cancellable(
- promise: Thenable,
- timeoutOrToken: number | CancellationToken,
+ promise: Promise,
+ timeoutOrToken?: number | CancellationToken,
options: {
cancelMessage?: string;
onDidCancel?(resolve: (value?: T | PromiseLike | undefined) => void, reject: (reason?: any) => void): void;
} = {},
): Promise {
+ if (timeoutOrToken == null) return promise;
+
return new Promise((resolve, reject) => {
let fulfilled = false;
let timer: NodeJS.Timer | undefined;
diff --git a/src/webviews/apps/settings/partials/blame.html b/src/webviews/apps/settings/partials/blame.html
index c67930c..6a6354e 100644
--- a/src/webviews/apps/settings/partials/blame.html
+++ b/src/webviews/apps/settings/partials/blame.html
@@ -56,16 +56,16 @@
id="blame.format"
name="blame.format"
type="text"
- placeholder="${message|40?} ${agoOrDate|14-}"
+ placeholder="${message|50?} ${agoOrDate|14-}"
data-setting
- data-default-value="${message|40?} ${agoOrDate|14-}"
+ data-default-value="${message|50?} ${agoOrDate|14-}"
data-popup-trigger
/>
-
+