@@ -6,7 +6,7 @@ import * as vscode from 'vscode';
66import { AskpassEnvironment , AskpassManager } from './askpass/askpassManager' ;
77import { getConfig } from './config' ;
88import { Logger } from './logger' ;
9- import { CommitOrdering , DateType , DeepWriteable , ErrorInfo , GitCommit , GitCommitDetails , GitCommitStash , GitConfigLocation , GitFileChange , GitFileStatus , GitPushBranchMode , GitRepoConfig , GitRepoConfigBranches , GitResetMode , GitSignatureStatus , GitStash , MergeActionOn , RebaseActionOn , SquashMessageFormat , TagType , Writeable } from './types' ;
9+ import { CommitOrdering , DateType , DeepWriteable , ErrorInfo , GitCommit , GitCommitDetails , GitCommitStash , GitConfigLocation , GitFileChange , GitFileStatus , GitPushBranchMode , GitRepoConfig , GitRepoConfigBranches , GitResetMode , GitSignature , GitSignatureStatus , GitStash , GitTagDetails , MergeActionOn , RebaseActionOn , SquashMessageFormat , TagType , Writeable } from './types' ;
1010import { GitExecutable , UNABLE_TO_FIND_GIT_MSG , UNCOMMITTED , abbrevCommit , constructIncompatibleGitVersionMessage , doesVersionMeetRequirement , getPathFromStr , getPathFromUri , openGitTerminal , pathWithTrailingSlash , realpath , resolveSpawnOutput , showErrorMessage } from './utils' ;
1111import { Disposable } from './utils/disposable' ;
1212import { Event } from './utils/event' ;
@@ -31,6 +31,15 @@ export const GIT_CONFIG = {
3131 }
3232} ;
3333
34+ const GPG_STATUS_CODE_PARSING_DETAILS : { [ statusCode : string ] : GpgStatusCodeParsingDetails } = {
35+ 'GOODSIG' : { status : GitSignatureStatus . GoodAndValid , uid : true } ,
36+ 'BADSIG' : { status : GitSignatureStatus . Bad , uid : true } ,
37+ 'ERRSIG' : { status : GitSignatureStatus . CannotBeChecked , uid : false } ,
38+ 'EXPSIG' : { status : GitSignatureStatus . GoodButExpired , uid : true } ,
39+ 'EXPKEYSIG' : { status : GitSignatureStatus . GoodButMadeByExpiredKey , uid : true } ,
40+ 'REVKEYSIG' : { status : GitSignatureStatus . GoodButMadeByRevokedKey , uid : true }
41+ } ;
42+
3443/**
3544 * Interfaces Git Graph with the Git executable to provide all Git integrations.
3645 */
@@ -494,21 +503,37 @@ export class DataSource extends Disposable {
494503 * @returns The tag details.
495504 */
496505 public getTagDetails ( repo : string , tagName : string ) : Promise < GitTagDetailsData > {
497- return this . spawnGit ( [ 'for-each-ref' , 'refs/tags/' + tagName , '--format=' + [ '%(objectname)' , '%(taggername)' , '%(taggeremail)' , '%(taggerdate:unix)' , '%(contents)' ] . join ( GIT_LOG_SEPARATOR ) ] , repo , ( stdout ) => {
498- let data = stdout . split ( GIT_LOG_SEPARATOR ) ;
506+ if ( this . gitExecutable !== null && ! doesVersionMeetRequirement ( this . gitExecutable . version , '1.7.8' ) ) {
507+ return Promise . resolve ( { details : null , error : constructIncompatibleGitVersionMessage ( this . gitExecutable , '1.7.8' , 'retrieving Tag Details' ) } ) ;
508+ }
509+
510+ const ref = 'refs/tags/' + tagName ;
511+ return this . spawnGit ( [ 'for-each-ref' , ref , '--format=' + [ '%(objectname)' , '%(taggername)' , '%(taggeremail)' , '%(taggerdate:unix)' , '%(contents:signature)' , '%(contents)' ] . join ( GIT_LOG_SEPARATOR ) ] , repo , ( stdout ) => {
512+ const data = stdout . split ( GIT_LOG_SEPARATOR ) ;
499513 return {
500- tagHash : data [ 0 ] ,
501- name : data [ 1 ] ,
502- email : data [ 2 ] . substring ( data [ 2 ] . startsWith ( '<' ) ? 1 : 0 , data [ 2 ] . length - ( data [ 2 ] . endsWith ( '>' ) ? 1 : 0 ) ) ,
503- date : parseInt ( data [ 3 ] ) ,
504- message : removeTrailingBlankLines ( data [ 4 ] . split ( EOL_REGEX ) ) . join ( '\n' ) ,
505- error : null
514+ hash : data [ 0 ] ,
515+ taggerName : data [ 1 ] ,
516+ taggerEmail : data [ 2 ] . substring ( data [ 2 ] . startsWith ( '<' ) ? 1 : 0 , data [ 2 ] . length - ( data [ 2 ] . endsWith ( '>' ) ? 1 : 0 ) ) ,
517+ taggerDate : parseInt ( data [ 3 ] ) ,
518+ message : removeTrailingBlankLines ( data . slice ( 5 ) . join ( GIT_LOG_SEPARATOR ) . replace ( data [ 4 ] , '' ) . split ( EOL_REGEX ) ) . join ( '\n' ) ,
519+ signed : data [ 4 ] !== ''
506520 } ;
507- } ) . then ( ( data ) => {
508- return data ;
509- } ) . catch ( ( errorMessage ) => {
510- return { tagHash : '' , name : '' , email : '' , date : 0 , message : '' , error : errorMessage } ;
511- } ) ;
521+ } ) . then ( async ( tag ) => ( {
522+ details : {
523+ hash : tag . hash ,
524+ taggerName : tag . taggerName ,
525+ taggerEmail : tag . taggerEmail ,
526+ taggerDate : tag . taggerDate ,
527+ message : tag . message ,
528+ signature : tag . signed
529+ ? await this . getTagSignature ( repo , ref )
530+ : null
531+ } ,
532+ error : null
533+ } ) ) . catch ( ( errorMessage ) => ( {
534+ details : null ,
535+ error : errorMessage
536+ } ) ) ;
512537 }
513538
514539 /**
@@ -1595,6 +1620,52 @@ export class DataSource extends Disposable {
15951620 } ) ;
15961621 }
15971622
1623+ /**
1624+ * Get the signature of a signed tag.
1625+ * @param repo The path of the repository.
1626+ * @param ref The reference identifying the tag.
1627+ * @returns A Promise resolving to the signature.
1628+ */
1629+ private getTagSignature ( repo : string , ref : string ) : Promise < GitSignature > {
1630+ return this . _spawnGit ( [ 'verify-tag' , '--raw' , ref ] , repo , ( stdout , stderr ) => stderr || stdout . toString ( ) , true ) . then ( ( output ) => {
1631+ const records = output . split ( EOL_REGEX )
1632+ . filter ( ( line ) => line . startsWith ( '[GNUPG:] ' ) )
1633+ . map ( ( line ) => line . split ( ' ' ) ) ;
1634+
1635+ let signature : Writeable < GitSignature > | null = null , trustLevel : string | null = null , parsingDetails : GpgStatusCodeParsingDetails | undefined ;
1636+ for ( let i = 0 ; i < records . length ; i ++ ) {
1637+ parsingDetails = GPG_STATUS_CODE_PARSING_DETAILS [ records [ i ] [ 1 ] ] ;
1638+ if ( parsingDetails ) {
1639+ if ( signature !== null ) {
1640+ throw new Error ( 'Multiple Signatures Exist: As Git currently doesn\'t support them, nor does Git Graph (for consistency).' ) ;
1641+ } else {
1642+ signature = {
1643+ status : parsingDetails . status ,
1644+ key : records [ i ] [ 2 ] ,
1645+ signer : parsingDetails . uid ? records [ i ] . slice ( 3 ) . join ( ' ' ) : '' // When parsingDetails.uid === TRUE, the signer is the rest of the record (so join the remaining arguments)
1646+ } ;
1647+ }
1648+ } else if ( records [ i ] [ 1 ] . startsWith ( 'TRUST_' ) ) {
1649+ trustLevel = records [ i ] [ 1 ] ;
1650+ }
1651+ }
1652+
1653+ if ( signature !== null && signature . status === GitSignatureStatus . GoodAndValid && ( trustLevel === 'TRUST_UNDEFINED' || trustLevel === 'TRUST_NEVER' ) ) {
1654+ signature . status = GitSignatureStatus . GoodWithUnknownValidity ;
1655+ }
1656+
1657+ if ( signature !== null ) {
1658+ return signature ;
1659+ } else {
1660+ throw new Error ( 'No Signature could be parsed.' ) ;
1661+ }
1662+ } ) . catch ( ( ) => ( {
1663+ status : GitSignatureStatus . CannotBeChecked ,
1664+ key : '' ,
1665+ signer : ''
1666+ } ) ) ;
1667+ }
1668+
15981669 /**
15991670 * Get the number of uncommitted changes in a repository.
16001671 * @param repo The path of the repository.
@@ -1715,21 +1786,24 @@ export class DataSource extends Disposable {
17151786 * Spawn Git, with the return value resolved from `stdout` as a buffer.
17161787 * @param args The arguments to pass to Git.
17171788 * @param repo The repository to run the command in.
1718- * @param resolveValue A callback invoked to resolve the data from `stdout`.
1789+ * @param resolveValue A callback invoked to resolve the data from `stdout` and `stderr`.
1790+ * @param ignoreExitCode Ignore the exit code returned by Git (default: `FALSE`).
17191791 */
1720- private _spawnGit < T > ( args : string [ ] , repo : string , resolveValue : { ( stdout : Buffer ) : T } ) {
1792+ private _spawnGit < T > ( args : string [ ] , repo : string , resolveValue : { ( stdout : Buffer , stderr : string ) : T } , ignoreExitCode : boolean = false ) {
17211793 return new Promise < T > ( ( resolve , reject ) => {
1722- if ( this . gitExecutable === null ) return reject ( UNABLE_TO_FIND_GIT_MSG ) ;
1794+ if ( this . gitExecutable === null ) {
1795+ return reject ( UNABLE_TO_FIND_GIT_MSG ) ;
1796+ }
17231797
17241798 resolveSpawnOutput ( cp . spawn ( this . gitExecutable . path , args , {
17251799 cwd : repo ,
17261800 env : Object . assign ( { } , process . env , this . askpassEnv )
17271801 } ) ) . then ( ( values ) => {
1728- let status = values [ 0 ] , stdout = values [ 1 ] ;
1729- if ( status . code === 0 ) {
1730- resolve ( resolveValue ( stdout ) ) ;
1802+ const status = values [ 0 ] , stdout = values [ 1 ] , stderr = values [ 2 ] ;
1803+ if ( status . code === 0 || ignoreExitCode ) {
1804+ resolve ( resolveValue ( stdout , stderr ) ) ;
17311805 } else {
1732- reject ( getErrorMessage ( status . error , stdout , values [ 2 ] ) ) ;
1806+ reject ( getErrorMessage ( status . error , stdout , stderr ) ) ;
17331807 }
17341808 } ) ;
17351809
@@ -1915,10 +1989,11 @@ interface GitStatusFiles {
19151989}
19161990
19171991interface GitTagDetailsData {
1918- tagHash : string ;
1919- name : string ;
1920- email : string ;
1921- date : number ;
1922- message : string ;
1992+ details : GitTagDetails | null ;
19231993 error : ErrorInfo ;
19241994}
1995+
1996+ interface GpgStatusCodeParsingDetails {
1997+ status : GitSignatureStatus ,
1998+ uid : boolean
1999+ }
0 commit comments