Skip to content

Commit 229eef6

Browse files
committed
Adds a commit signing indicator to the graph commit box
Surfaces the repo's signing status in the Commit Graph's working changes (WIP) commit box — a key icon appears when commits will be signed (repo `commit.gpgsign` or VS Code's `git.enableCommitSigning`), with the signing format (GPG, SSH, X.509, or OpenPGP) shown on hover. Delivers the status via the WIP payload (cached `getSigningConfig` read during assembly) and keeps it fresh on `.git/config` edits (watcher) and `git.enableCommitSigning` setting changes.
1 parent 79e3a52 commit 229eef6

7 files changed

Lines changed: 95 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1010

1111
- Adds ConfigCat-based feature flag service for A/B testing and experimentation support ([#5092](https://github.com/gitkraken/vscode-gitlens/issues/5092))
1212
- Adds an optional `avatar` URL template to custom remotes in the `gitlens.remotes` setting — enables corporate and self-hosted setups to resolve commit-author avatars via a templated URL with `${email}`, `${emailName}`, `${domain}`, and `${size}` tokens; identity values are component-encoded before interpolation to keep attacker-controllable commit emails from injecting URL-structural characters, and templates configured via workspace settings require explicit user approval on first use in a trusted workspace (revocable via _GitLens: Reset > Approved Avatar URL Templates..._) ([#302](https://github.com/gitkraken/vscode-gitlens/issues/302), [#5155](https://github.com/gitkraken/vscode-gitlens/pull/5155)) — thanks to [PR #1636](https://github.com/gitkraken/vscode-gitlens/pull/1636) by Tmk ([@tmkx](https://github.com/tmkx))
13+
- Adds a commit signing indicator to the _Commit Graph_'s working changes (WIP) commit box — a key icon appears when commits will be signed (via the repo's `commit.gpgsign` Git config or VS Code's `git.enableCommitSigning` setting), with the signing format (GPG, SSH, X.509, or OpenPGP) shown on hover
1314

1415
### Changed
1516

src/webviews/apps/plus/graph/components/gl-commit-box.css.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ export const commitBoxStyles = css`
2929
justify-content: space-between;
3030
}
3131
32+
.options-group {
33+
display: flex;
34+
align-items: center;
35+
gap: 0.4rem;
36+
}
37+
38+
.signing-indicator {
39+
display: inline-flex;
40+
color: var(--vscode-descriptionForeground);
41+
}
42+
43+
.signing-indicator:focus-visible {
44+
outline: 0.1rem solid var(--vscode-focusBorder);
45+
outline-offset: 0.2rem;
46+
}
47+
3248
.compose-icon {
3349
color: var(--gl-agent-working-color);
3450
}

src/webviews/apps/plus/graph/components/gl-commit-box.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { html, LitElement, nothing } from 'lit';
22
import { customElement, property } from 'lit/decorators.js';
33
import { isMac } from '@env/platform.js';
4+
import type { WipSigning } from '../../../../plus/graph/detailsProtocol.js';
45
import { elementBase, scrollableBase } from '../../../shared/components/styles/lit/base.css.js';
56
import { commitBoxStyles } from './gl-commit-box.css.js';
67
import '../../../shared/components/button.js';
@@ -55,21 +56,40 @@ export class GlCommitBox extends LitElement {
5556
@property()
5657
commitError?: string;
5758

59+
@property({ type: Object })
60+
signing?: WipSigning;
61+
5862
override render() {
5963
return html`
6064
<div class="options">
6165
${this.renderAmendToggle()}
62-
${this.aiEnabled
63-
? html`<gl-button appearance="secondary" @click=${this.onCompose}>
64-
<code-icon class="compose-icon" icon="wand" slot="prefix"></code-icon>
65-
Compose
66-
</gl-button>`
67-
: nothing}
66+
<div class="options-group">
67+
${this.renderSigningIndicator()}
68+
${this.aiEnabled
69+
? html`<gl-button appearance="secondary" @click=${this.onCompose}>
70+
<code-icon class="compose-icon" icon="wand" slot="prefix"></code-icon>
71+
Compose
72+
</gl-button>`
73+
: nothing}
74+
</div>
6875
</div>
6976
${this.renderTextarea()} ${this.renderActionBar()}
7077
`;
7178
}
7279

80+
private renderSigningIndicator() {
81+
if (!this.signing?.enabled) return nothing;
82+
83+
const label = `Commits will be signed using ${getSigningFormatLabel(this.signing.format)}`;
84+
return html`
85+
<gl-tooltip content=${label} placement="bottom">
86+
<span class="signing-indicator" tabindex="0" role="img" aria-label=${label}>
87+
<code-icon icon="key"></code-icon>
88+
</span>
89+
</gl-tooltip>
90+
`;
91+
}
92+
7393
private renderAmendToggle() {
7494
return html`
7595
<gl-checkbox
@@ -227,6 +247,19 @@ export class GlCommitBox extends LitElement {
227247
}
228248
}
229249

250+
function getSigningFormatLabel(format: WipSigning['format']): string {
251+
switch (format) {
252+
case 'ssh':
253+
return 'SSH';
254+
case 'x509':
255+
return 'X.509';
256+
case 'openpgp':
257+
return 'OpenPGP';
258+
default:
259+
return 'GPG';
260+
}
261+
}
262+
230263
declare global {
231264
interface HTMLElementTagNameMap {
232265
'gl-commit-box': GlCommitBox;

src/webviews/apps/plus/graph/components/gl-graph-details-panel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1907,6 +1907,7 @@ export class GlGraphDetailsPanel extends SignalWatcher(LitElement) {
19071907
.disabledReason=${this._actions.canCommitReason()}
19081908
.aiEnabled=${this._state.preferences.get()?.aiEnabled ?? false}
19091909
.commitError=${this._state.commitError.get()}
1910+
.signing=${wip.signing}
19101911
@message-change=${this.handleCommitMessageChange}
19111912
@amend-change=${this.handleAmendChange}
19121913
@commit=${this.handleCommit}

src/webviews/commitDetails/protocol.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { GitPausedOperationStatus } from '@gitlens/git/models/pausedOperati
66
import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';
77
import type { GitBranchReference } from '@gitlens/git/models/reference.js';
88
import type { GitCommitSearchContext } from '@gitlens/git/models/search.js';
9+
import type { SigningFormat } from '@gitlens/git/models/signature.js';
910
import type { CurrentUserNameStyle } from '@gitlens/git/utils/commit.utils.js';
1011
import type { DateTimeFormat } from '@gitlens/utils/date.js';
1112
import type { Config, DateStyle } from '../../config.js';
@@ -112,6 +113,13 @@ export interface WipStats {
112113
context?: string;
113114
}
114115

116+
/** Repo-level commit-signing status for the WIP commit box — see {@link Wip.signing}. */
117+
export interface WipSigning {
118+
/** Whether commits will be signed (repo `commit.gpgsign` or the host's `git.enableCommitSigning` override). */
119+
enabled: boolean;
120+
format: SigningFormat;
121+
}
122+
115123
export interface Wip {
116124
changes: WipChange | undefined;
117125
repositoryCount: number;
@@ -133,6 +141,12 @@ export interface Wip {
133141
* rely on it in practice (guard with `?.` for the shared-type contract).
134142
*/
135143
stats?: WipStats;
144+
/**
145+
* Commit-signing status for this wip's repo — drives the "will be signed" indicator in the
146+
* Graph's commit box. Optional for the same reason as {@link Wip.stats}: only the Graph's
147+
* `getWipForRepoAndStats` populates it.
148+
*/
149+
signing?: WipSigning;
136150
}
137151

138152
export interface DraftState {

src/webviews/plus/graph/detailsProtocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type {
1717
Preferences,
1818
State,
1919
Wip,
20+
WipSigning,
2021
WipStats,
2122
WorkingFileSorting,
2223
} from '../../commitDetails/protocol.js';

src/webviews/plus/graph/graphWebview.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3953,6 +3953,14 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
39533953
}
39543954

39553955
private onWorkspaceConfigurationChanged(e: ConfigurationChangeEvent) {
3956+
// The host signing override feeds `wip.signing` (the commit box's "will be signed"
3957+
// indicator) via `getSigningConfig`, which reads the setting through a live getter — a
3958+
// WIP re-push is enough to refresh it. Secondary-worktree panels refresh on their next
3959+
// watcher tick instead; acceptable for a rare settings change.
3960+
if (e.affectsConfiguration('git.enableCommitSigning')) {
3961+
void this.notifyDidChangeWorkingTree();
3962+
}
3963+
39563964
if (!e.affectsConfiguration('git.autofetch') && !e.affectsConfiguration('git.autofetchPeriod')) return;
39573965

39583966
void this.notifyDidChangeConfiguration();
@@ -4076,11 +4084,13 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
40764084
// Lightweight WIP refresh — covers staging/unstaging (`index` → stats), `.gitignore` edits
40774085
// (`ignores` → which untracked files appear in `git status`), secondary-worktree add/remove
40784086
// (`worktrees` → wipMetadataBySha; also falls through to the structural gate below as a
4079-
// backstop full-state push), and tracking changes (`head|heads|remotes` → wip.branch.upstream,
4080-
// which drives the "Publish" ↔ "Create PR" next-step row in the details panel). Unioned so the
4081-
// in-flight coalescer can't double-fire on a single multi-flag event (e.g. Pull's
4087+
// backstop full-state push), tracking changes (`head|heads|remotes` → wip.branch.upstream,
4088+
// which drives the "Publish" ↔ "Create PR" next-step row in the details panel), and
4089+
// `.git/config` edits (`config` → wip.signing; the watcher currently always pairs `config`
4090+
// with `remotes`, but don't rely on that classifier detail). Unioned so the in-flight
4091+
// coalescer can't double-fire on a single multi-flag event (e.g. Pull's
40824092
// `head, heads, remotes, index`).
4083-
if (e.changed('head', 'heads', 'index', 'ignores', 'remotes', 'worktrees')) {
4093+
if (e.changed('head', 'heads', 'index', 'ignores', 'remotes', 'worktrees', 'config')) {
40844094
void this.notifyDidChangeWorkingTree();
40854095
}
40864096

@@ -5040,7 +5050,7 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
50405050
// `head`/`heads`/`remotes`) so the secondary panel stays reactive to those same
50415051
// changes.
50425052
watcher.onDidChange(e => {
5043-
if (!e.changed('index', 'ignores', 'pausedOp', 'head', 'heads', 'remotes')) return;
5053+
if (!e.changed('index', 'ignores', 'pausedOp', 'head', 'heads', 'remotes', 'config')) return;
50445054

50455055
this._wipStatusCache.invalidate(path);
50465056
this.queueWipRefetch(sha, repo);
@@ -7908,18 +7918,21 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
79087918
(_cacheable, factorySignal) => svc.status.getStatus(undefined, factorySignal),
79097919
{ cancellation: signal },
79107920
);
7911-
const [statusResult, pausedOpStatusResult] = await Promise.allSettled([
7921+
const [statusResult, pausedOpStatusResult, signingConfigResult] = await Promise.allSettled([
79127922
statusFetch,
79137923
// `force` so a missed `'pausedOp'` FS-watcher tick (common on secondary worktrees
79147924
// whose `GlRepository` is closed) can't leave the WIP row stuck on a stale indicator.
79157925
svc.pausedOps?.getPausedOperationStatus?.({ force: true }, signal),
7926+
// Cached config read — drives the "will be signed" indicator in the commit box.
7927+
svc.config.getSigningConfig?.(),
79167928
]);
79177929
const status = getSettledValue(statusResult);
79187930
if (status == null) return undefined;
79197931

79207932
signal?.throwIfAborted();
79217933

79227934
const pausedOpStatus = getSettledValue(pausedOpStatusResult);
7935+
const signingConfig = getSettledValue(signingConfigResult);
79237936

79247937
const conflictMarkerCounts = new Map<string, number>();
79257938
if (status.hasConflicts) {
@@ -8034,6 +8047,10 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
80348047
: undefined,
80358048
},
80368049
stats: stats,
8050+
signing:
8051+
signingConfig != null
8052+
? { enabled: signingConfig.enabled, format: signingConfig.format }
8053+
: undefined,
80378054
},
80388055
};
80398056
}

0 commit comments

Comments
 (0)