Managing your Git repo via NodeJS

INTRODUCTION

The purpose of this blog post is to share steps for creating a node API which manages git repository on an online server.

Such an API was the requirement of a client of ours whose project runs on an Amazon EC2 instance. The project consists of a git repository with a submodule. Each branch of the submodule represents different version/functionality of the project. What the client needed was an ability to switch these branches on the server just like it happens in Sourcetree.

Our first attempt was to automate the entire process so that all the local changes would be automatically applied on the server side. This included commits, updating files and branch switching. This solution was to be implemented by using git hooks which are used for firing custom scripts on a specific event. 

However, the problem with this solution was that it needed client side configuration which our client was not familiar and comfortable with. Hence we decided to switch to another solution that would implement a nodeJS API with simple user interface on server side to manage the repository.

We used nodeJS along with git-controller library to make all of this work. Following are the steps needed to make this repo management API.

Steps for making Node API

Step 1: Setting up environment

Make sure you have all the softwares installed before starting the project. The softwares required are:

Its important that you also configure ssh authentication as the library we use authenticates via ssh.(Bitbucket ssh configuration/ Github ssh configuration).

Step 2: Initial scaffolding

Its better to save time and use templates. I have used express scaffolding for this project.

Using your console first install express-generator:

npm install express-generator –g

Then make your project with express scaffolding:

express myProject

You would see the following result on console:

Step 3: Installing libraries

The next thing you need to do is to install libraries.
The required libraries are:

  • Libraries mentioned in Package.json dependencies.
  • Git controller, which runs git commands.
  • Bootstrap, used to make a clean and responsive UI.

Installing dependencies and git-controller

First go to your project directory. Run

cd myProject

Then Install project dependencies

npm install

Add new lib and add it as a dependency

npm install git-controller --save

final package.json would look like this:

Screen Shot 2015-11-23 at 4.40.40 PM.png

Just to verify, your npm_module directory should be like this:

Adding bootstrap

For adding bootstrap, we need to make a bower file and put bootstrap as dependency. While setting this up, answering the questions is optional. If you don’t want to answer any questions, simply skip it by pressing enter. Run the following command to create your file:

bower init

final output of command:

bower-init.png

now you need to add bootstrap

bower install bootstrap

this would add bootstrap and jquery libs.

Step 4: adding favicon

Note: If you don’t wish to add a favicon, please skip this step.

It is always a good idea to add favicon to your project. It add to the user experience.

Open app.js file and un-comment the line

app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));

Add favicon.ico in public folder. Now your directory looks something like this:

Step 5: adding bootstrap and jQuery routes

The view engine 'express' uses by default is jade. It is a high performance template engine for writing HTML. Go to the view folder, open layout.jade and insert the following code under the head tag

link(rel='stylesheet', href='/bower_components/bootstrap/dist/css/bootstrap.min.css')
link(rel='stylesheet', href='/bower_components/bootstrap/dist/css/bootstrap-theme.min.css')
script(src='/bower_components/jquery/dist/jquery.min.js')
script(src='/bower_components/bootstrap/dist/js/bootstrap.min.js')

Also add route in app.js by adding the following code

app.use('/bower_components', express.static(__dirname + '/bower_components'));

STEP 6: RUN TO CHECK

Simple run command to check everything is done correctly.

node app.js

If you get any errors you need to recheck all the steps and see what has gone wrong.

STEP 7: INITIALIZE PROJECT AS REPO AND ADD SUBMODULE

We need to initialize the project as git repo before we can add the submodule. Type in the command in console:

git init

Now we need to add the repo (as submodule) we want to manage on server. I am adding node-git-demo-submodule repo as my submodule. Simply add your submodule with this command:

git@github.com:marketlytics/node-git-demo-submodule.git

Note: It is very important that you clone this repo with ssh link or your project will NOT work.

STEP 8: REFERENCE REPO TO BE MANAGED

Now we need to use the git library. To set it up, make a file inside routes folder and name it git.controller.js. Require git-controller and give path of your submodule. Following would be the code:

var express = require('express');
var router = express.Router();
var fs = require('fs');
var git = require('git-controller');
var myRepo = git(__dirname + '/../testing');

STEP 9: WRITE GIT FUNCTIONS

Now we need to add functionalities for git command. We will be adding functions in git.controller.js.

INDEX

This function is used to view the main page.

exports.index = function(req, res) {
getBranches(res);
};

GET BRANCHES

This is a private function to get the current status of remote and local branches. We first run command to get current local branch then we run command to get status of both the local and remote branches. The git command myRepo.getBranches() runs is git branch.

 result.current is the branch name at which HEAD is pointing locally.

function getBranches(res) {
    myRepo.getBranches(function(err, result) {
        if (err) return console.log(err);
        remoteLocalStatus(res, result.current);
    });
}

REMOTELOCALSTATUS

This function gets Repo information and status of remote and local branches and finally updates it on the view. The git command remoteLocalStatus runs is git remote show origin.

function remoteLocalStatus(res, currentBranch) {
    myRepo.remote('origin', ['show'], function(err, result) {
        if (err) return console.log(err);
        var head = null;

        for (var i = 0; i < result.localInfo.length; i++) {
            if (result.localInfo[i].branchName == currentBranch) {
                head = result.localInfo[i];
                result.localInfo.splice(i, 1);
                break;
            }
        }

        res.render('index', {
            title: 'Node API',
            localHead: head,
            remoteInfo: result.remoteInfo,
            localInfo: result.localInfo
        });
    });
}

CHECKOUT

The purpose of this function is to checkout the local branch or checkout and track the remote branch locally The function myRepo.checkout() corresponds to two git commands depending on the parameter provided in the routes. The commands are git checkout branchName and git checkout origin/branchName --track.

exports.checkout = function(req, res) {
    var flags = [];
    var branch = req.params.branch;

    if (req.params.action === "track") {
        flags = ['--track'];
        branch = 'origin/' + branch;
    }

    myRepo.checkout(branch, flags, function(err, result) {
        if (err) return console.log(err);
        remoteLocalStatus(res, result.current);
    });
};

PULL

This function pulls current branch changes. The git command myRepo.pull() runs is git pull origin/branchName.

exports.pull = function(req, res) {
    myRepo.pull('origin', req.params.branch, [], {
        username: null,
        password: null
    }, function(err, result) {
        if (err) return console.log(err);

        getBranches(res);
    });
};

FETCH

This function fetches all changes from remote. The git command myRepo.fetch() runs is git fetch --all -p

exports.fetch = function(req, res) {
    myRepo.fetch(['--all', '-p'], {
        username: null,
        password: null
    }, function(err, result) {
        if (err) return console.log(err);
        getBranches(res);
    });
};

STEP 10: CREATING THE VIEW

Now its time to create the view. Put the code at this link in view/index.jade.

The view shows data which is dynamic. It is being populated while being rendered.

STEP 11: LINK VIEW ROUTES

IMPORT ROUTER IN APP.JS

Require index.js in the beginning. Replace the line

var routes = require('./routes/index'); 

with

var routes = require('./routes/index.js');

Now redirect the page by adding following code:

app.use('/', routes);

ADD ROUTES IN INDEX.JS

We would then add a route for every action. On every route, a specific function is called from git-controller. Following are the routes you need to add: index.js code

STEP 12: RUN AND TEST

Now it is time to test and run your project.

SETTING UP THE SERVER

To run the project, you need to create a server and listen to it. Create a server by adding the following code in app.js after var app = express(); line:

var http = require('http');
var server = http.createServer(app);
app.set('port', process.env.PORT || 3000);

Now listen to the server by adding the following code before the module.exports = app; line:

server.listen(app.get('port'), function() {
    console.log("Express server listening on port " + app.get('port'));
});

Now run the following command:

node app.js

You will see "Express server listening on port 3000" on console.

Now go to localhost to test your project.

You can see the changes in submodule after checking out each branch at index.html.

To checkout the complete code and demo click here.