Home Reference Source Repository

lib/git.js

// Based on https://github.com/shinnn/gulp-gh-pages/blob/master/index.js
const git = require('gift')
const rimraf = require('rimraf')
const wrapPromise = require('wrap-promise')

/*
 * Git Constructor
**/
function Git(repo, initialBranch) {
  this._repo = repo
  this._staged = []
  this._localBranches = []
  this._remoteBranches = []
  this._currentBranch = initialBranch
  this._commits = []
}

/*
 * Caller abstract method
 * for promisifying traditional callback methods
**/
function caller() {
  const returnedArgs = Array.prototype.slice.call(arguments)
  const fn = returnedArgs.shift()
  const self = this

  return wrapPromise(function (resolve, reject) {
    returnedArgs.push(function (err, args) {
      if (err) {
        reject(err)
        return
      }

      resolve(args)
    })

    fn.apply(self, returnedArgs)
  })
}

/*
 * Gets the URL for the specified remote of a repo
 */
function getRemoteUrl(repo, remote) {
  return wrapPromise(function (resolve, reject) {
    repo.config(function (err, config) {
      if (err) {
        reject(new Error('Failed to find git repository in ' + config.path))
        return
      }

      resolve(config.items['remote.' + remote + '.url'])
    })
  })
}

/*
 * Clone repo
 * Returns repo object
**/
function prepareRepo(remoteUrl, origin, dir) {
  let promise

  if (remoteUrl) {
    // if a remoteUrl was provided, use it
    promise = wrapPromise.Promise.resolve(remoteUrl)
  } else {
    // else try to extract it from the .git folder of
    // the current directory.
    promise = getRemoteUrl(git(process.cwd()), origin)
  }

  return promise.catch(function (e) { console.error(e) }).then(function (rUrl) {
    remoteUrl = rUrl

    return wrapPromise(function (resolve, reject) {
      function initRepo(repo) {
        repo.branch(function (err, head) {
          if (err) {
            reject(err)
            return
          }

          resolve(new Git(repo, head.name).status())
        })
      }

      function clearAndInitRepo() {
        rimraf(dir, function (rmErr) {
          if (rmErr) {
            reject(rmErr)
            return
          }

          git.clone(rUrl, dir, function (cloneErr, repo) {
            if (cloneErr) {
              reject(cloneErr)
              return
            }

            initRepo(repo)
          })
        })
      }

      // assume that if there is a .git folder get its remoteUrl
      // and check if it mathces the one we want to use.
      getRemoteUrl(git(dir), origin).then(function (cwdRemoteUrl) {
        if (remoteUrl === cwdRemoteUrl) {
          initRepo(git(dir))
          return
        }

        clearAndInitRepo()
      }, function () {

        clearAndInitRepo()
      })
    })
  }).catch(function (err) {
    console.error(err)
  })
}

/*
 * List Local branches
**/
function listLocalBranches(repo) {
  return caller.call(repo, repo.branches).then(function (branches) {
    return branches.map(function (branch) {
      return branch.name
    })
  })
}

function listRemoteBranches(repo) {
  return caller.call(repo, repo.git, 'branch', { r: true }, [])
  .then(function (branches) {
    branches = branches.split('\n')
    branches.shift()
    branches.pop()
    return branches.map(function (branchName) {
      branchName = branchName.trim()
      return branchName
    })
  })
}

/*
 * List commits for specific branch
**/
function getCommits(repo, branchName) {
  return caller.call(repo, repo.commits, branchName)
  .then(function (commits) {
    return commits.map(function (commitObj) {
      return {
        id: commitObj.id,
        message: commitObj.message,
        committed_date: commitObj.committed_date
      }
    })
  })
}

Git.prepareRepo = prepareRepo
Git.getRemoteUrl = getRemoteUrl

/*
 * Status
 * files - Array of String paths; or a String path.
**/
Git.prototype.status = function () {
  const self = this

  return wrapPromise(function (resolve, reject) {
    self._repo.status(function (err, repo) {
      if (err) {
        reject(err)
        return
      }

      self._repo = repo.repo
      self._staged = repo.files
      wrapPromise.Promise.all([
        getCommits(self._repo, self._currentBranch),
        listRemoteBranches(self._repo),
        listLocalBranches(self._repo)
      ])
      .then(function (args) {
        self._remoteBranches = args[1]
        self._localBranches = args[2]
        self._commits = args[0]
        resolve(self)
      }, reject)
    })
  })
}

/*
 * Checkout a specific branch in a repo
 * @param name {String} -  String name of the branch.
**/
Git.prototype.checkoutBranch = function (name) {
  const self = this

  return wrapPromise(function (resolve, reject) {
    self._repo.checkout(name, function (err) {
      if (err) {
        reject(err)
        return
      }

      self._currentBranch = name
      resolve(self.status())
    })
  })
}

/*
 * Create a branch
 * @param name {String} -  String name of the new branch.
**/
Git.prototype.createBranch = function (name) {
  const self = this

  return wrapPromise(function (resolve, reject) {
    self._repo.create_branch(name, function (err) {
      if (err) {
        reject(err)
      } else {
        self._currentBranch = name
        resolve(self.status())
      }
    })
  })
}

/*
 * Create and checkout a branch
 * @param name {String} -  String name of the new branch.
**/
Git.prototype.createAndCheckoutBranch = function (name) {
  return this.createBranch(name)
  .then(function (repo) {
    return repo.checkoutBranch(name)
  })
}

Git.prototype.addFiles = function (files, options) {
  const self = this

  return wrapPromise(function (resolve, reject) {
    self._repo.add(files, options, function (err) {
      if (err) {
        reject(err)
        return
      }

      resolve(self.status())
    })
  })
}

Git.prototype.commit = function (commitMsg) {
  const self = this

  return wrapPromise(function (resolve, reject) {
    self._repo.commit(commitMsg, { all: true }, function (err) {
      if (err) {
        reject(err)
      } else {
        resolve(self.status())
      }
    })
  })
}

module.exports = Git