Skip to content

Commit 544ab54

Browse files
committed
feat(graph): add signature status column to commit table
- Add Signature column to defaultColumnVisibility configuration - Modify git log format to include signature data (%G?, %GS, %GK) - Add GitCommitSignature interface to types.ts - Update frontend to render signature icons with colors - Add CSS styles for signature column and icons - Signature icons: ✓ (green), ✗ (red), ⚠ (orange), ❓ (gray) - Hover tooltip shows signer and key ID details Related to mhutchie#909
1 parent d7f43f4 commit 544ab54

6 files changed

Lines changed: 141 additions & 9 deletions

File tree

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,14 +533,19 @@
533533
"Commit": {
534534
"type": "boolean",
535535
"title": "Visibility of the Commit column"
536+
},
537+
"Signature": {
538+
"type": "boolean",
539+
"title": "Visibility of the Signature column"
536540
}
537541
},
538542
"default": {
539543
"Date": true,
540544
"Author": true,
541-
"Commit": true
545+
"Commit": true,
546+
"Signature": true
542547
},
543-
"description": "An object specifying the default visibility of the Date, Author & Commit columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true}"
548+
"description": "An object specifying the default visibility of the Date, Author, Commit & Signature columns. Example: {\"Date\": true, \"Author\": true, \"Commit\": true, \"Signature\": true}"
544549
},
545550
"git-graph.dialog.addTag.pushToRemote": {
546551
"type": "boolean",

src/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ class Config {
162162
get defaultColumnVisibility(): DefaultColumnVisibility {
163163
let obj: any = this.config.get('defaultColumnVisibility', {});
164164
if (typeof obj === 'object' && obj !== null && typeof obj['Date'] === 'boolean' && typeof obj['Author'] === 'boolean' && typeof obj['Commit'] === 'boolean') {
165-
return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'] };
165+
return { author: obj['Author'], commit: obj['Commit'], date: obj['Date'], signature: obj['Signature'] !== undefined ? obj['Signature'] : true };
166166
} else {
167-
return { author: true, commit: true, date: true };
167+
return { author: true, commit: true, date: true, signature: true };
168168
}
169169
}
170170

src/dataSource.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ export class DataSource extends Disposable {
113113
this.gitFormatLog = [
114114
'%H', '%P', // Hash & Parent Information
115115
useMailmap ? '%aN' : '%an', useMailmap ? '%aE' : '%ae', dateType, // Author / Commit Information
116-
'%s' // Subject
116+
'%s', // Subject
117+
...(config.showSignatureStatus && this.gitExecutableSupportsGpgInfo ? ['%G?', '%GS', '%GK'] : ['', '', '']) // GPG Key Information
117118
].join(GIT_LOG_SEPARATOR);
118119

119120
this.gitFormatStash = [
@@ -200,7 +201,18 @@ export class DataSource extends Disposable {
200201

201202
for (i = 0; i < commits.length; i++) {
202203
commitLookup[commits[i].hash] = i;
203-
commitNodes.push({ ...commits[i], heads: [], tags: [], remotes: [], stash: null });
204+
commitNodes.push({
205+
...commits[i],
206+
heads: [],
207+
tags: [],
208+
remotes: [],
209+
stash: null,
210+
signature: commits[i].signature ? {
211+
status: commits[i].signature.status,
212+
signer: commits[i].signature.signer,
213+
keyId: commits[i].signature.keyId
214+
} : null
215+
});
204216
}
205217

206218
/* Insert Stashes */
@@ -1533,8 +1545,24 @@ export class DataSource extends Disposable {
15331545
let commits: GitCommitRecord[] = [];
15341546
for (let i = 0; i < lines.length - 1; i++) {
15351547
let line = lines[i].split(GIT_LOG_SEPARATOR);
1536-
if (line.length !== 6) break;
1537-
commits.push({ hash: line[0], parents: line[1] !== '' ? line[1].split(' ') : [], author: line[2], email: line[3], date: parseInt(line[4]), message: line[5] });
1548+
const hasSignatureFields = line.length === 9;
1549+
if (line.length !== 6 && !hasSignatureFields) break;
1550+
const commit: GitCommitRecord = {
1551+
hash: line[0],
1552+
parents: line[1] !== '' ? line[1].split(' ') : [],
1553+
author: line[2],
1554+
email: line[3],
1555+
date: parseInt(line[4]),
1556+
message: line[5]
1557+
};
1558+
if (hasSignatureFields && line[6] !== '') {
1559+
commit.signature = {
1560+
status: line[6],
1561+
signer: line[7],
1562+
keyId: line[8]
1563+
};
1564+
}
1565+
commits.push(commit);
15381566
}
15391567
return commits;
15401568
});
@@ -1961,6 +1989,11 @@ interface GitCommitRecord {
19611989
email: string;
19621990
date: number;
19631991
message: string;
1992+
signature?: {
1993+
status: string;
1994+
signer: string;
1995+
keyId: string;
1996+
};
19641997
}
19651998

19661999
interface GitCommitData {

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface GitCommit {
1111
readonly tags: ReadonlyArray<GitCommitTag>;
1212
readonly remotes: ReadonlyArray<GitCommitRemote>;
1313
readonly stash: GitCommitStash | null; // null => not a stash, otherwise => stash info
14+
readonly signature: GitCommitSignature | null; // null => no signature, otherwise => signature info
1415
}
1516

1617
export interface GitCommitTag {
@@ -53,6 +54,12 @@ export const enum GitSignatureStatus {
5354
Bad = 'B'
5455
}
5556

57+
export interface GitCommitSignature {
58+
readonly status: string; // G, B, U, X, Y, R, E, or empty
59+
readonly signer: string; // Signer name
60+
readonly keyId: string; // Key ID
61+
}
62+
5663
export interface GitSignature {
5764
readonly key: string;
5865
readonly signer: string;
@@ -451,6 +458,7 @@ export interface DefaultColumnVisibility {
451458
readonly date: boolean;
452459
readonly author: boolean;
453460
readonly commit: boolean;
461+
readonly signature: boolean;
454462
}
455463

456464
export interface DialogDefaults {

web/main.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,8 @@ class GitGraphView {
821821
let html = '<tr id="tableColHeaders"><th id="tableHeaderGraphCol" class="tableColHeader" data-col="0">Graph</th><th class="tableColHeader" data-col="1">Description</th>' +
822822
(colVisibility.date ? '<th class="tableColHeader dateCol" data-col="2">Date</th>' : '') +
823823
(colVisibility.author ? '<th class="tableColHeader authorCol" data-col="3">Author</th>' : '') +
824-
(colVisibility.commit ? '<th class="tableColHeader" data-col="4">Commit</th>' : '') +
824+
(colVisibility.signature ? '<th class="tableColHeader signatureCol" data-col="4">Signature</th>' : '') +
825+
(colVisibility.commit ? '<th class="tableColHeader" data-col="' + (4 + (colVisibility.signature ? 1 : 0)) + '">Commit</th>' : '') +
825826
'</tr>';
826827

827828
for (let i = 0; i < this.commits.length; i++) {
@@ -869,6 +870,7 @@ class GitGraphView {
869870
(this.config.referenceLabels.branchLabelsAlignedToGraph ? '<td>' + (refBranches !== '' ? '<span style="margin-left:' + (widthsAtVertices[i] - 4) + 'px"' + refBranches.substring(5) : '') + '</td><td><span class="description">' + commitDot : '<td></td><td><span class="description">' + commitDot + refBranches) + (this.config.referenceLabels.tagLabelsOnRight ? message + refTags : refTags + message) + '</span></td>' +
870871
(colVisibility.date ? '<td class="dateCol text" title="' + date.title + '">' + date.formatted + '</td>' : '') +
871872
(colVisibility.author ? '<td class="authorCol text" title="' + escapeHtml(commit.author + ' <' + commit.email + '>') + '">' + (this.config.fetchAvatars ? '<span class="avatar" data-email="' + escapeHtml(commit.email) + '">' + (typeof this.avatars[commit.email] === 'string' ? '<img class="avatarImg" src="' + this.avatars[commit.email] + '">' : '') + '</span>' : '') + escapeHtml(commit.author) + '</td>' : '') +
873+
(colVisibility.signature ? '<td class="signatureCol text">' + generateSignatureIconHtml(commit.signature) + '</td>' : '') +
872874
(colVisibility.commit ? '<td class="text" title="' + escapeHtml(commit.hash) + '">' + abbrevCommit(commit.hash) + '</td>' : '') +
873875
'</tr>';
874876
}
@@ -927,6 +929,7 @@ class GitGraphView {
927929
document.getElementById('uncommittedChanges')!.innerHTML = '<td></td><td><b>' + escapeHtml(this.commits[0].message) + '</b></td>' +
928930
(colVisibility.date ? '<td class="dateCol text" title="' + date.title + '">' + date.formatted + '</td>' : '') +
929931
(colVisibility.author ? '<td class="authorCol text" title="* <>">*</td>' : '') +
932+
(colVisibility.signature ? '<td class="signatureCol text">' + generateSignatureIconHtml(null) + '</td>' : '') +
930933
(colVisibility.commit ? '<td class="text" title="*">*</td>' : '');
931934
}
932935

@@ -3958,6 +3961,62 @@ function generateSignatureHtml(signature: GG.GitSignature) {
39583961
+ '</span>';
39593962
}
39603963

3964+
function generateSignatureIconHtml(signature: GG.GitCommitSignature | null) {
3965+
if (!signature || !signature.status) {
3966+
return '<span class="signature-icon signature-none">-</span>';
3967+
}
3968+
const status = signature.status;
3969+
let icon = '';
3970+
let cssClass = '';
3971+
let title = '';
3972+
switch (status) {
3973+
case 'G':
3974+
icon = '✓';
3975+
cssClass = 'signature-valid';
3976+
title = 'Good signature';
3977+
break;
3978+
case 'B':
3979+
icon = '✗';
3980+
cssClass = 'signature-invalid';
3981+
title = 'Bad signature';
3982+
break;
3983+
case 'U':
3984+
icon = '⚠';
3985+
cssClass = 'signature-warning';
3986+
title = 'Good signature, unknown validity';
3987+
break;
3988+
case 'X':
3989+
icon = '⚠';
3990+
cssClass = 'signature-warning';
3991+
title = 'Good signature, expired';
3992+
break;
3993+
case 'Y':
3994+
icon = '⚠';
3995+
cssClass = 'signature-warning';
3996+
title = 'Good signature, made by expired key';
3997+
break;
3998+
case 'R':
3999+
icon = '✗';
4000+
cssClass = 'signature-invalid';
4001+
title = 'Good signature, revoked';
4002+
break;
4003+
case 'E':
4004+
icon = '❓';
4005+
cssClass = 'signature-error';
4006+
title = 'Cannot check signature';
4007+
break;
4008+
default:
4009+
icon = '-';
4010+
cssClass = 'signature-none';
4011+
title = '';
4012+
break;
4013+
}
4014+
const tooltip = title && signature.signer
4015+
? title + '. Signed by ' + escapeHtml(signature.signer) + ' (Key: ' + escapeHtml(signature.keyId) + ')'
4016+
: title;
4017+
return '<span class="signature-icon ' + cssClass + '"' + (tooltip ? ' title="' + tooltip + '"' : '') + '>' + icon + '</span>';
4018+
}
4019+
39614020
function closeDialogAndContextMenu() {
39624021
if (dialog.isOpen()) dialog.close();
39634022
if (contextMenu.isOpen()) contextMenu.close();

web/styles/main.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ code{
258258
#commitTable.autoLayout td.authorCol, #commitTable.autoLayout th.authorCol{
259259
max-width:124px;
260260
}
261+
#commitTable.autoLayout td.signatureCol, #commitTable.autoLayout th.signatureCol{
262+
max-width:45px;
263+
text-align:center;
264+
}
261265
@media screen and (min-width: 775px) and (max-width: 850px) {
262266
#commitTable.autoLayout td.dateCol, #commitTable.autoLayout th.dateCol, #commitTable.autoLayout td.authorCol, #commitTable.autoLayout th.authorCol{
263267
max-width:100px;
@@ -456,6 +460,29 @@ code{
456460
opacity:0.8;
457461
}
458462

463+
/* Signature icon styles for main table */
464+
.signature-icon{
465+
font-size:14px;
466+
cursor:help;
467+
display:inline-block;
468+
vertical-align:middle;
469+
}
470+
.signature-valid{
471+
color:#4ec9b0;
472+
}
473+
.signature-invalid{
474+
color:#f48771;
475+
}
476+
.signature-warning{
477+
color:#dcdcaa;
478+
}
479+
.signature-error{
480+
color:#9cdcfe;
481+
}
482+
.signature-none{
483+
color:#808080;
484+
}
485+
459486
#cdvFiles{
460487
left:50%;
461488
right:0;

0 commit comments

Comments
 (0)