Skip to content

Commit 4e6b765

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 36fe63f commit 4e6b765

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
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1717
- Adds a _Reveal in Explorer View_ option for working tree (WIP) files in the _Inspect_ and _Commit Graph_ views — right-click a staged, unstaged, or conflicted working file to reveal and select it in VS Code's Explorer view ([#5387](https://github.com/gitkraken/vscode-gitlens/issues/5387))
1818
- Adds _Focus on Branch_, _Focus on Worktree_, and _Solo Branch_ options to the _Commit Graph_ — right-click a branch or worktree in the side bar (or a graph row) to focus the graph and minimap on it, and right-click a working tree (WIP) row to focus or solo the graph on that worktree's branch ([#5388](https://github.com/gitkraken/vscode-gitlens/issues/5388))
1919
- Adds manual take-side fallbacks and conflict-type labels to the AI **Resolve** mode in the _Commit Graph_ WIP details panel — conflicts the AI can't auto-merge (binary, symlink, submodule, file-mode, add/add, and rename/rename or rename/delete conflicts) are now labeled by type and offer inline _Take Current_, _Take Incoming_, and _Delete_ actions instead of dead-ending as "needs review"; resolving a rename/rename conflict also removes the other side's renamed file. Also decodes UTF-16/BOM-encoded files so their conflicts can be resolved rather than skipped ([#5393](https://github.com/gitkraken/vscode-gitlens/issues/5393))
20+
- 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
2021

2122
### Changed
2223

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
@@ -1911,6 +1911,7 @@ export class GlGraphDetailsPanel extends SignalWatcher(LitElement) {
19111911
.disabledReason=${this._actions.canCommitReason()}
19121912
.aiEnabled=${this._state.preferences.get()?.aiEnabled ?? false}
19131913
.commitError=${this._state.commitError.get()}
1914+
.signing=${wip.signing}
19141915
@message-change=${this.handleCommitMessageChange}
19151916
@amend-change=${this.handleAmendChange}
19161917
@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
@@ -4064,6 +4064,14 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
40644064
}
40654065

40664066
private onWorkspaceConfigurationChanged(e: ConfigurationChangeEvent) {
4067+
// The host signing override feeds `wip.signing` (the commit box's "will be signed"
4068+
// indicator) via `getSigningConfig`, which reads the setting through a live getter — a
4069+
// WIP re-push is enough to refresh it. Secondary-worktree panels refresh on their next
4070+
// watcher tick instead; acceptable for a rare settings change.
4071+
if (e.affectsConfiguration('git.enableCommitSigning')) {
4072+
void this.notifyDidChangeWorkingTree();
4073+
}
4074+
40674075
if (!e.affectsConfiguration('git.autofetch') && !e.affectsConfiguration('git.autofetchPeriod')) return;
40684076

40694077
void this.notifyDidChangeConfiguration();
@@ -4187,11 +4195,13 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
41874195
// Lightweight WIP refresh — covers staging/unstaging (`index` → stats), `.gitignore` edits
41884196
// (`ignores` → which untracked files appear in `git status`), secondary-worktree add/remove
41894197
// (`worktrees` → wipMetadataBySha; also falls through to the structural gate below as a
4190-
// backstop full-state push), and tracking changes (`head|heads|remotes` → wip.branch.upstream,
4191-
// which drives the "Publish" ↔ "Create PR" next-step row in the details panel). Unioned so the
4192-
// in-flight coalescer can't double-fire on a single multi-flag event (e.g. Pull's
4198+
// backstop full-state push), tracking changes (`head|heads|remotes` → wip.branch.upstream,
4199+
// which drives the "Publish" ↔ "Create PR" next-step row in the details panel), and
4200+
// `.git/config` edits (`config` → wip.signing; the watcher currently always pairs `config`
4201+
// with `remotes`, but don't rely on that classifier detail). Unioned so the in-flight
4202+
// coalescer can't double-fire on a single multi-flag event (e.g. Pull's
41934203
// `head, heads, remotes, index`).
4194-
if (e.changed('head', 'heads', 'index', 'ignores', 'remotes', 'worktrees')) {
4204+
if (e.changed('head', 'heads', 'index', 'ignores', 'remotes', 'worktrees', 'config')) {
41954205
void this.notifyDidChangeWorkingTree();
41964206
}
41974207

@@ -5151,7 +5161,7 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
51515161
// `head`/`heads`/`remotes`) so the secondary panel stays reactive to those same
51525162
// changes.
51535163
watcher.onDidChange(e => {
5154-
if (!e.changed('index', 'ignores', 'pausedOp', 'head', 'heads', 'remotes')) return;
5164+
if (!e.changed('index', 'ignores', 'pausedOp', 'head', 'heads', 'remotes', 'config')) return;
51555165

51565166
this._wipStatusCache.invalidate(path);
51575167
this.queueWipRefetch(sha, repo);
@@ -8053,18 +8063,21 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
80538063
(_cacheable, factorySignal) => svc.status.getStatus(undefined, factorySignal),
80548064
{ cancellation: signal },
80558065
);
8056-
const [statusResult, pausedOpStatusResult] = await Promise.allSettled([
8066+
const [statusResult, pausedOpStatusResult, signingConfigResult] = await Promise.allSettled([
80578067
statusFetch,
80588068
// `force` so a missed `'pausedOp'` FS-watcher tick (common on secondary worktrees
80598069
// whose `GlRepository` is closed) can't leave the WIP row stuck on a stale indicator.
80608070
svc.pausedOps?.getPausedOperationStatus?.({ force: true }, signal),
8071+
// Cached config read — drives the "will be signed" indicator in the commit box.
8072+
svc.config.getSigningConfig?.(),
80618073
]);
80628074
const status = getSettledValue(statusResult);
80638075
if (status == null) return undefined;
80648076

80658077
signal?.throwIfAborted();
80668078

80678079
const pausedOpStatus = getSettledValue(pausedOpStatusResult);
8080+
const signingConfig = getSettledValue(signingConfigResult);
80688081

80698082
const conflictMarkerCounts = new Map<string, number>();
80708083
if (status.hasConflicts) {
@@ -8179,6 +8192,10 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
81798192
: undefined,
81808193
},
81818194
stats: stats,
8195+
signing:
8196+
signingConfig != null
8197+
? { enabled: signingConfig.enabled, format: signingConfig.format }
8198+
: undefined,
81828199
},
81838200
};
81848201
}

0 commit comments

Comments
 (0)