Skip to content

Instantly share code, notes, and snippets.

@jarrodek
Created November 9, 2019 02:18
Show Gist options
  • Save jarrodek/218f0469691ab12b4254db2ff191c9f5 to your computer and use it in GitHub Desktop.
Save jarrodek/218f0469691ab12b4254db2ff191c9f5 to your computer and use it in GitHub Desktop.
This gist demonstrates how to use nodegit to sign commits using GPG keys.
const nodegit = require('nodegit');
const path = require('path');
const fs = require('fs-extra');
const openpgp = require('openpgp');
const fileName = 'newfile.txt';
const fileContent = 'hello world';
const directoryName = 'salad/toast/strangerinastrangeland/theresnowaythisexists';
/**
* This example creates a certain file `newfile.txt`, adds it to the git
* index and commits with GOG signaature it to head. Similar to a `git add newfile.txt`
* followed by a `git commit -s`
*
* This example uses `openpgp` library (https://github.com/openpgpjs/openpgpjs)
* to read the key and encrypt the commit message. This involves two functions:
* - the `decryptGpg` function which is responsible for decrypting the GPG key
* - the `onSignature` function which is called during commit operation
*
* The `onSignature` function is the callback function passed to the
* `createCommitWithSignature()` function of Repository instance.
*
* This example uses dummy GPG key. Learn how to generate a GPG key on HitHub
* help pages: https://help.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key
**/
/**
* Decrypts GPG key to be used to sign commits.
* @return {Promise}
*/
async function decryptGpg() {
const key = path.join(__dirname, 'secret-key-489B1963.asc');
const pass = 'nodegit';
const buff = await fs.readFile(key);
const armored = await openpgp.key.readArmored(buff);
const keyObj = armored.keys[0];
const decrypted = keyObj.decrypt(pass);
if (decrypted) {
return keyObj;
}
}
/**
* Callback for GPG signature when signing the commit.
* @param {String} tosign A string to sign.
* @return {Promise}
*/
async function onSignature(tosign) {
const privateKeyResult = await decryptGpg();
if (!privateKeyResult) {
throw new Error('GPG key decoding error.');
}
const buf = new Uint8Array(tosign.length);
for (let i = 0; i < tosign.length; i++) {
buf[i] = tosign.charCodeAt(i);
}
const options = {
message: openpgp.message.fromBinary(buf),
privateKeys: [privateKeyResult],
detached: true
};
const signed = await openpgp.sign(options);
return {
code: nodegit.Git.Error.CODE.OK,
field: 'gpgsig',
signedData: signed.signature
};
}
/**
* Creates files in the local filesystem. This files will be added to the
* repository.
* @param {Repository} repo
* @return {Promise} A promise resolved when files are created
*/
async function addFiles(repo) {
await fs.ensureDir(path.join(repo.workdir(), directoryName));
await fs.writeFile(path.join(repo.workdir(), fileName), fileContent);
await fs.writeFile(
path.join(repo.workdir(), directoryName, fileName),
fileContent
);
}
/**
* Adds files by path and writes the state to the index.
* @param {Repository} repo
* @return {Promise} A promise resolved to an Oid
*/
async function addAndWrite(repo) {
const index = await repo.refreshIndex();
await index.addByPath(fileName);
await index.addByPath(path.posix.join(directoryName, fileName));
await index.write();
return await index.writeTree();
}
/**
* Commits changes using GPG key.
* @param {Repository} repo
* @param {Oid} oid Write Oid
* @return {Promise} Resolved when the commit is ready.
*/
async function commitSigned(repo, oid) {
const head = await nodegit.Reference.nameToId(repo, 'HEAD');
const parent = await repo.getCommit(head);
const author = nodegit.Signature.now('Pawel Psztyc', 'jarrodek@gmail.com');
const committer = nodegit.Signature.now('Pawel Psztyc', 'jarrodek@gmail.com');
return await repo.createCommitWithSignature(
'HEAD',
author,
committer,
'message',
oid,
[parent],
onSignature
);
}
let repo;
nodegit.Repository.open(path.resolve(__dirname, '../.git'))
.then((result) => {
repo = result;
return addFiles(repo);
})
.then(() => addAndWrite(repo))
.then((oid) => commitSigned(repo, oid))
.then((commitId) => {
console.log(`New commit: ${commitId}`);
});
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBF3GGN0BCAC5S10y3xUYccBF/ElgVYPM61CAmf5vKbGvsEuDIAqBIvHddCw1
myaHqrK67/mkavlbLp3HD9jTgkd5sRrzVVSFGxeGRf2ysFamsGH+rLwvqzpyb4ru
49ThwMUVsuVExdoQB6Il7cdZLnnEZejr+ud9ZsDP24v5nleqOjAkU8GUiLdOaVgx
e6ObEiSQbmzW9mz+DzLrKgbyar4ID+cC4+5y7D2w+eB9Az6EOCJGVxLxUSo9DyFX
3x0e67MrwrdFTPeKjS+HirzGdyJI7oTJsdWJD3rUDWwxSw7v52I8xGWxBOu67dUT
Ruuhe/JNrAtjnDxZC2qfiFgTB98jGov/FENHABEBAAG0JU5vZGVnaXQgRXhhbXBs
ZSA8bm9kZWdpdEBub2RlZ2l0Lm9yZz6JAU4EEwEKADgWIQRQmkqTSsOlqpBdFEs/
lSx6SJsZYwUCXcYY3QIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRA/lSx6
SJsZY9cCB/9uU/Q4GOHAKP5jnD6ZRdV2GM7DIKQKsUHmq/Zmxr6xGnUQoCw8iNcN
5bwJusJbl0/BbVI0MO4oHWTNZYc6KHz6E7NnM/c8yOZHpvl8PcvprNwUTOTUERGE
noDXr4HEwOUvpkzSnkmy8O0mmmX3gtRs5KZCXJFUsVNII2N04w4wNCITNZvMY8ov
A/xRtUuDsljtyrsUq7ZgVLCwC1AvnLqreTOEbu9KaEykgQ8SkfjFmcl9DXtXkKdw
uKonTKpOo7aeXETDHzQictsGAW6BPm2Dac+c1SbsAx0oa6eF9tqrjkkSEAC7SEFy
Qe5EcuMZfqK8LPzFAWR2ICBnDiuZc71TuQENBF3GGN0BCADYlgAEmX2G2Cdkgh/n
4Q8xyqqOw31JClVtMIIV/c7/VY3Od8f3/OTKb3WrKGOsPWs+nVopeJ5fFlIOyDz6
qsujJvP/EltWit6l0SSXufsM7C9HyHS3vEkBW2ltJ/urZXW1bZ/z7ocwn417OnHK
3k90HcncYca1HvctbZ8VPlyhVkyLllaBFDRvfi/asjZEY1hL5j0HvAGi9jvq/UXg
H+/mskc95SQD5CbrSMkkAogfFkX+g/y7gxSAgY09NWco6+6Uiiq2WAp1MOsPa+Y6
rbfYltIU5UCmcfJ2996y4zgCkDlmITY+T7CvkVs6JeKfYiRMZiNMarFoexjGDzEm
EAiLABEBAAGJATYEGAEKACAWIQRQmkqTSsOlqpBdFEs/lSx6SJsZYwUCXcYY3QIb
DAAKCRA/lSx6SJsZYy/8B/sFT1snW1ldY7zICkUNi4aHdqXWNdPsdSRzGn0C2/Vh
lyPQXWUhcI8a0J9DPOZ9LylMB+MD7Bgt2SNjym711Wce77kPURyNby5/j1kc4LU8
3ArKv9ULiJO2C5NBTgK4uevdOALlMy1AJdt3vi5zO9MQW9QnxnHxBNUxAc/BUPqB
en+XpAmxpr0UqU/q7VY8m8Ep8VA5gp2zuib5R1kUl26lctF8Zae1iFVA+KWhK07l
HJsp5zG8TVlGdHSRhhb/Z5f3AbJzRmSrIeJoWUdSaSbJk5kq7jPaYXtPw3EDyv2F
U/m6Om3LBBsBsIzBadNHTtNdt4PTJByA9dGgqTL46Bjp
=7n4W
-----END PGP PUBLIC KEY BLOCK-----
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment