@@ -3,7 +3,7 @@ const fp = require('fs').promises
3
3
const path = require ( 'path' )
4
4
const url = require ( 'url' )
5
5
const EventEmitter = require ( 'events' ) . EventEmitter
6
- const { indexPack, plugins, readObject, verify } = require ( 'isomorphic-git' )
6
+ const { E , indexPack, plugins, readObject, verify } = require ( 'isomorphic-git' )
7
7
const { serveInfoRefs, serveReceivePack, parseReceivePackRequest } = require ( 'isomorphic-git/dist/for-node/isomorphic-git/internal-apis.js' )
8
8
const { pgp } = require ( '@isomorphic-git/pgp-plugin' )
9
9
@@ -21,6 +21,10 @@ function pad (str) {
21
21
return ( str + ' ' ) . slice ( 0 , 7 )
22
22
}
23
23
24
+ function abbr ( oid ) {
25
+ return oid . slice ( 0 , 7 )
26
+ }
27
+
24
28
const sleep = ms => new Promise ( cb => setTimeout ( cb , ms ) )
25
29
26
30
function log ( req , res ) {
@@ -86,37 +90,43 @@ function factory (config) {
86
90
87
91
// send HTTP response headers
88
92
const { headers } = await serveReceivePack ( { type : 'service' , service } )
89
- for ( const header in headers ) {
90
- res . setHeader ( header , headers [ header ] )
91
- }
92
- res . statusCode = 200
93
+ res . writeHead ( 200 , headers )
93
94
94
95
// index packfile
95
96
res . write ( await serveReceivePack ( { type : 'print' , message : 'Indexing packfile...' } ) )
97
+ console . log ( 'Indexing packfile...' )
98
+ await sleep ( 1 )
96
99
let currentPhase = null
97
100
const listener = async ( { phase, loaded, total, lengthComputable } ) => {
98
101
let np = phase !== currentPhase ? '\n' : '\r'
99
102
currentPhase = phase
100
103
res . write ( await serveReceivePack ( { type : 'print' , message : `${ np } ${ phase } ${ loaded } /${ total } ` } ) )
104
+ res . flush ( )
101
105
}
102
- let problem = false
103
106
let oids
104
107
try {
105
108
ee . on ( `${ last20 } :progress` , listener )
106
109
oids = await indexPack ( { fs, gitdir, dir, filepath, emitterPrefix : `${ last20 } :` } )
107
- res . write ( await serveReceivePack ( { type : 'print' , message : '\nIndexing a success!' } ) )
110
+ await sleep ( 1 )
111
+ res . write ( await serveReceivePack ( { type : 'print' , message : '\nIndexing completed' } ) )
108
112
res . write ( await serveReceivePack ( { type : 'unpack' , unpack : 'ok' } ) )
109
113
} catch ( e ) {
110
- problem = true
111
114
res . write ( await serveReceivePack ( { type : 'print' , message : '\nOh dear!' } ) )
112
115
res . write ( await serveReceivePack ( { type : 'unpack' , unpack : e . message } ) )
116
+
117
+ for ( const update of updates ) {
118
+ res . write ( await serveReceivePack ( { type : 'ng' , ref : update . fullRef , message : 'Could not index pack' } ) )
119
+ }
120
+ throw e
113
121
} finally {
114
122
ee . removeListener ( `${ last20 } :progress` , listener )
115
123
}
124
+ await sleep ( 1 )
116
125
117
126
// Move packfile and index into repo
118
127
await fp . rename ( path . join ( dir , filepath ) , path . join ( gitdir , 'objects' , 'pack' , filepath ) )
119
128
await fp . rename ( path . join ( dir , filepath . replace ( / \. p a c k $ / , '.idx' ) ) , path . join ( gitdir , 'objects' , 'pack' , filepath . replace ( / \. p a c k $ / , '.idx' ) ) )
129
+ await fp . rmdir ( path . join ( dir ) )
120
130
121
131
// Verify objects (ideally we'd do this _before_ moving it into the repo... but I think we'd need a custom 'fs' implementation with overlays)
122
132
res . write ( await serveReceivePack ( { type : 'print' , message : '\nVerifying objects...\n' } ) )
@@ -128,44 +138,63 @@ function factory (config) {
128
138
const { type, object } = await readObject ( { gitdir, oid } )
129
139
if ( type === 'commit' || type === 'tag' ) {
130
140
const email = type === 'commit' ? object . author . email : object . tagger . email
131
- res . write ( await serveReceivePack ( { type : 'print' , message : `\nVerifying ${ type } ${ oid } by ${ email } \n` } ) )
132
- const keys = await lookup ( email )
141
+ res . write ( await serveReceivePack ( { type : 'print' , message : `\nVerifying ${ type } ${ abbr ( oid ) } by ${ email } : ` } ) )
142
+ let keys
143
+ try {
144
+ keys = await lookup ( email )
145
+ } catch ( e ) {
146
+ res . write ( await serveReceivePack ( { type : 'print' , message : `no keys found 👎\n` } ) )
147
+ throw e
148
+ }
149
+ if ( keys . length === 0 ) {
150
+ res . write ( await serveReceivePack ( { type : 'print' , message : `no keys found 👎\n` } ) )
151
+ throw new Error ( `\nSignature verification failed for ${ type } ${ abbr ( oid ) } . No PGP keys could be found for ${ email } .\n` )
152
+ }
133
153
let ok = false
134
154
for ( const key of keys ) {
135
155
const result = await verify ( { gitdir, ref : oid , publicKeys : key } )
136
156
if ( result === false ) {
137
157
demote ( email , key )
138
158
} else {
139
- res . write ( await serveReceivePack ( { type : 'print' , message : `\nSigned by ${ result [ 0 ] } \n` } ) )
159
+ res . write ( await serveReceivePack ( { type : 'print' , message : `signed with ${ result [ 0 ] } 👍 \n` } ) )
140
160
ok = true
141
161
break
142
162
}
143
163
}
144
164
if ( ! ok ) {
145
- res . write ( await serveReceivePack ( { type : 'error' , message : `\nNo valid signature for ${ type } ${ oid } \n` } ) )
146
- throw new Error ( 'NO SIGNATURE' )
165
+ res . write ( await serveReceivePack ( { type : 'print' , message : `no keys matched 👎\n` } ) )
166
+ throw new Error ( `\nSignature verification failed for ${ type } ${ abbr ( oid ) } . It was not signed with a key publicly associated with the email address "${ email } ".
167
+
168
+ Learn how you can associate your GPG key with your email account using GitHub here:
169
+ https://help.github.com/en/github/authenticating-to-github/adding-a-new-gpg-key-to-your-github-account
170
+ ` )
147
171
}
148
172
}
149
173
// await sleep(1)
150
174
}
151
175
176
+ res . write ( await serveReceivePack ( { type : 'print' , message : `\nVerification complete` } ) )
177
+
152
178
// refs
153
179
for ( const update of updates ) {
154
- if ( ! problem ) {
155
- res . write ( await serveReceivePack ( { type : 'ok' , ref : update . fullRef } ) )
156
- } else {
157
- res . write ( await serveReceivePack ( { type : 'ng' , ref : update . fullRef , message : 'Could not index pack' } ) )
158
- }
180
+ res . write ( await serveReceivePack ( { type : 'ok' , ref : update . fullRef } ) )
159
181
}
160
182
161
183
// gratuitous banner
162
184
res . write ( await serveReceivePack ( { type : 'print' , message : '\n' + require ( './logo.js' ) } ) )
163
185
} catch ( e ) {
164
186
if ( e . message === 'Client is done' ) {
165
187
res . statusCode = 200
188
+ } else if ( e . code && e . code === E . NoSignatureError ) {
189
+ res . write ( await serveReceivePack ( { type : 'print' , message : `no signature 👎\n` } ) )
190
+ res . write ( await serveReceivePack ( { type : 'error' , message : e . message + `
191
+
192
+ This server's policy is to only accept GPG-signed commits.
193
+ Learn how you can create a GPG key and configure git to sign commits here:
194
+ https://help.github.com/en/github/authenticating-to-github/managing-commit-signature-verification
195
+ ` } ) )
166
196
} else {
167
- console . log ( e )
168
- res . statusCode = 500
197
+ res . write ( await serveReceivePack ( { type : 'error' , message : e . message } ) )
169
198
}
170
199
} finally {
171
200
// fin
0 commit comments