Automate GIT with NodeJs


Files stranded, source codes updated daily, inbound files or scheduled drops? Why not automate a job to push everything (or some) to Git.

A simple nodejs app or script, hosted or ran asynchronously is all you need.

+ NodeGit :

(github, API Documentation) one of those links will take you to a page that says “NodeGit can be quickly and painlessly installed via NPM”. which is very much true.

npm install nodegit

For more comprehensive installation techniques, check out the Install Guides.

+ Set up SSH key authentication:

Refer SSH key Authentication 

Create your SSH keys with the ssh-keygen command from the bash/cmd prompt. This will create a 2048-bit RSA key for use with SSH. You can give a passphrase for your private key when prompted—this provides another layer of security for your private key. This produces the two keys needed for SSH authentication: your private key ( id_rsa ) and the public key ( id_rsa.pub ). The default location of these generated keys would be the root of this directory unless changed.

C:\Users\<User>\

You can choose to move the files in the project working directory or refer to this location in the properties. However, it is suggested to keep all the project related certificates at one place.

For AzureDevOps/TFS setup you will need to Add the public key to Azure DevOps Services/TFS

Refer Documentation about adding a public key to Azure DevOps/TFS `

Go to your Azure DevOps Profile and under SSH public keys

Click + New Keys, give any suitable Name Copy the contents of the public key (for example, id_rsa.pub) that you generated into the Public Key Data field.

NOTE: Avoid adding whitespace or new lines into the Key Data field, as they can cause Azure DevOps Services to use an invalid public key. When pasting in the key, a newline often is added at the end. Be sure to remove this newline if it occurs.

Code:

Now that you have everything let’s put code to start.
I will divide the code in 4 parts:
1. Setup
2. Config and Init
3. Clone repo (in case not cloned already)
4. Sync

  1. Setup
    const debug = ""; //Import your Logger here or use Console just in case.
    const nodegit = require("nodegit");
    const fs = require('fs');
    const path = require("path");
    const fse = require("fs-extra");
    const dir = `./backup/`
    const repoFolder = `${dir}.git`;
    var privateKey, publicKey, passphrase, twoWaySync, url, credentialsCallback, signature_name, signature_email;
    module.exports = async (options) => {
    ///Initialize Git config.
    InitGit(options);
    ///Clone Repo if not exist already.
    try {
    if (!fs.existsSync(repoFolder)) {
    debug.info(`Cloning Repo.`)
    await CloneRepo();
    }
    } catch (error) {
    debug.error("Error during Cloning Repo.");
    debug.error(error);
    }
    //Prepare files for commit, maybe cleanup or renaming or conditioning.
    PrepareFilesForCommit();
    /// Sync Once all files and folders are prepared.
    try {
    if (fs.existsSync(`${dir}.git`)) {
    debug.info(`Attempting to push to server.`)
    await InitSync();
    }
    } catch (error) {
    debug.error("Error during pushing to server.");
    debug.error(error);
    }
    }
    view raw nodegit_snippet1.js hosted with ❤ by GitHub
  2. Config and Init
    var config = {
    "options": {
    "privateKey": "<pathToPrivateKey>/MyKeyFile",
    "publicKey": "<pathToPublicKey>/MyKeyFile.pub",
    "passphrase": "Y0ur.P@5$p#rAse.G0e5.H3rE!",
    "twoWaySync": true,
    "sshUrl": "<get_this_sshUrl_from_git>",
    "signature_name": "Someone's Name",
    "signature_email": "SomeoneWho@isMakingThisCommit.dev"
    }
    }
    function InitGit(options) {
    debug.info("Initializing Git...");
    //Fetch values from config.
    privateKey = options.privateKey;
    publicKey = options.publicKey;
    passphrase = options.passphrase;
    twoWaySync = options.twoWaySync;
    url = options.sshUrl;
    signature_name = options.signature_name;
    signature_email = options.signature_email;
    credentialsCallback = {
    credentials: function (url, userName) {
    return nodegit.Cred.sshKeyNew(userName, publicKey, privateKey, passphrase);
    }
    }
    }
    view raw nodegit_snippet2.js hosted with ❤ by GitHub
  3. Clone Repo
    async function CloneRepo() {
    var cloneOptions = {
    fetchOpts: { callbacks: credentialsCallback }
    };
    nodegit.Clone(url, dir, cloneOptions).then(function (repo) {
    debug.verbose("Cloned " + path.basename(url) + " to " + repo.workdir());
    }).catch(function (err) {
    debug.verbose(err);
    });
    }
    view raw nodegit_snippet3.js hosted with ❤ by GitHub
  4. Sync
    async function InitSync() {
    var remote, repo, count, fileNames, fileContent;
    nodegit.Repository.open(repoFolder)
    .then(function (repoResult) {
    repo = repoResult
    count = 0;
    fileNames = [];
    fileContent = {};
    if (twoWaySync) {
    debug.info(`TwoWaySync is Enabled, fetching, fast-forwarding and merging changes to local.`)
    return PullRepo(repo);
    }
    }).then(async function () {
    /// Adding Files
    const files = await repo.getStatusExt();
    files.forEach(function (file) {
    if ((file.isNew() || file.isModified() || file.isTypechange() || file.isRenamed()) &&
    file.inWorkingTree()) {
    var status = file.isNew() ? "New" : file.isModified() ? "Modified" : file.isTypechange() ? "Type Changed" : file.isRenamed() ? "Renamed" : " -Unknown. ";
    var fileID = file.path();
    const path = `${dir}${fileID}`;
    if (fs.statSync(path).isFile()) {
    data = fs.readFileSync(path);
    fileContent[fileID] = data;
    debug.verbose(`Adding ${fileID} with status ${status}`);
    }
    }
    });
    fileNames = Object.keys(fileContent);
    count = fileNames.length;
    })
    .then(async function () {
    return repo.refreshIndex()
    .then(async function (index) {
    if (count > 0) {
    return Promise.all(fileNames.map(function (fileName) {
    fse.writeFile(
    path.join(repo.workdir(), fileName), fileContent[fileName]);
    }))
    .then(function () {
    // This will add all files to the index
    return index.addAll(fileNames, nodegit.Index.ADD_OPTION.ADD_CHECK_PATHSPEC);
    })
    .then(function () {
    // this will write files to the index
    return index.write();
    })
    .then(function () {
    return index.writeTree();
    })
    /// COMMIT
    .then(function (oidResult) {
    oid = oidResult;
    return nodegit.Reference.nameToId(repo, "HEAD");
    })
    .then(function (head) {
    return repo.getCommit(head);
    })
    .then(function (parent) {
    var author = nodegit.Signature.now(signature_name, signature_email);
    var committer = nodegit.Signature.now(signature_name, signature_email);
    var message = `some commit message that makes sense...`;
    return repo.createCommit("HEAD", author, committer, message, oid, [parent]);
    })
    .then(function (commitId) {
    debug.verbose(`New Commit: ${commitId}`);
    })
    /// PULL if TwoWaySync
    .then(function () {
    if (twoWaySync) {
    debug.info(`TwoWaySync is Enabled. Fetching, fast-forwarding and merging changes to local.`)
    return PullRepo(repo);
    }
    })
    /// PUSH
    .then(function () {
    return repo.getRemote('origin');
    })
    .then(function (remoteResult) {
    remote = remoteResult;
    // Create the push object for this remote
    return remote.push(
    ["refs/heads/master:refs/heads/master"], //consider have branch as an parameter, i choose to exploite master <grin>
    { callbacks: credentialsCallback }
    );
    })
    .then(function () {
    count = 0;
    fileNames = [];
    fileContent = {};
    debug.verbose('remote Pushed!')
    })
    .catch(function (reason) {
    debug.verbose(reason);
    });
    }
    });
    }).done(function () {
    debug.verbose(`Successfully pushed to server.`)
    });
    }
    async function PullRepo(repo) {
    return repo.fetchAll({
    callbacks: credentialsCallback
    }).then(function () {
    return repo.mergeBranches("master", "origin/master");
    });
    }
    view raw nodegit_snippet4.js hosted with ❤ by GitHub

You can compile this and deploy as an azure function, a Cron job or whatever suits your requirement .

P.s. I don’t have considerable time to enhance this or publish code at the moment; I am open to suggestion and improvements feel free to drop by. 🙂

Pingback for assistance, your Feedback’s are always a welcome… 🙂

Regards,
Aditya Deshpande

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s