mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-18 16:40:56 +00:00
Implement VCS module
This commit is contained in:
parent
001d7be622
commit
8d5bba9558
@ -78,6 +78,22 @@ exports.InvalidPath = class InvalidPath extends TypedError
|
||||
# Error code
|
||||
code: 1
|
||||
|
||||
exports.DirectoryDoesntExist = class DirectoryDoesntExist extends TypedError
|
||||
|
||||
# Construct a Directory Doesn't Exist error
|
||||
#
|
||||
# @param {String} directory the name of the directory that doesn't exist
|
||||
#
|
||||
# @example Directory doesn't exist error
|
||||
# throw new resin.errors.DirectoryDoesntExist('/tmp')
|
||||
# Error: Directory doesn't exist: /tmp
|
||||
#
|
||||
constructor: (directory) ->
|
||||
@message = "Directory doesn't exist: #{directory}"
|
||||
|
||||
# Error code
|
||||
code: 1
|
||||
|
||||
exports.NotAny = class NotAny extends TypedError
|
||||
|
||||
# Construct an Not Any error
|
||||
|
@ -8,4 +8,5 @@ module.exports =
|
||||
auth: require('./auth/auth')
|
||||
device: require('./device/device')
|
||||
os: require('./os/os')
|
||||
vcs: require('./vcs/vcs')
|
||||
settings: require('./settings')
|
||||
|
@ -11,6 +11,7 @@ settings =
|
||||
dataPrefix: path.join(userHome, '.resin')
|
||||
|
||||
sshKeyWidth: 43
|
||||
gitRemote: 'resin'
|
||||
|
||||
directories:
|
||||
plugins: 'plugins'
|
||||
|
106
lib/resin/vcs/git/git.coffee
Normal file
106
lib/resin/vcs/git/git.coffee
Normal file
@ -0,0 +1,106 @@
|
||||
fs = require('fs')
|
||||
fsPlus = require('fs-plus')
|
||||
_ = require('lodash')
|
||||
async = require('async')
|
||||
path = require('path')
|
||||
gitCli = require('git-cli')
|
||||
errors = require('../../errors/errors')
|
||||
settings = require('../../settings')
|
||||
|
||||
nodeify = (func) ->
|
||||
return ->
|
||||
return func.call(null, null, arguments...)
|
||||
|
||||
exports.getGitDirectory = (directory) ->
|
||||
return if not directory?
|
||||
if not _.isString(directory)
|
||||
throw new Error('Invalid git directory')
|
||||
return path.join(directory, '.git')
|
||||
|
||||
exports.getCurrentGitDirectory = ->
|
||||
currentDirectory = process.cwd()
|
||||
return exports.getGitDirectory(currentDirectory)
|
||||
|
||||
exports.isGitRepository = (directory, callback) ->
|
||||
gitDirectory = exports.getGitDirectory(directory)
|
||||
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
fs.exists(directory, nodeify(callback))
|
||||
|
||||
(exists, callback) ->
|
||||
return callback() if exists
|
||||
error = new errors.DirectoryDoesntExist(directory)
|
||||
return callback(error)
|
||||
|
||||
(callback) ->
|
||||
fsPlus.isDirectory(gitDirectory, nodeify(callback))
|
||||
|
||||
], callback)
|
||||
|
||||
exports.getRepository = (directory, callback) ->
|
||||
exports.isGitRepository directory, (error, isGitRepository) ->
|
||||
return callback(error) if error?
|
||||
|
||||
if not isGitRepository
|
||||
error = new Error("Not a git directory: #{directory}")
|
||||
return callback(error)
|
||||
|
||||
gitDirectory = exports.getGitDirectory(directory)
|
||||
repository = new gitCli.Repository(gitDirectory)
|
||||
return callback(null, repository)
|
||||
|
||||
exports.isValidGitApplication = (application) ->
|
||||
gitRepository = application.git_repository
|
||||
return false if not gitRepository?
|
||||
return false if not _.isString(gitRepository)
|
||||
return true
|
||||
|
||||
exports.hasRemote = (repository, name, callback) ->
|
||||
repository.listRemotes null, (error, remotes) ->
|
||||
return callback(error) if error?
|
||||
hasRemote = _.indexOf(remotes, name) isnt -1
|
||||
return callback(null, hasRemote)
|
||||
|
||||
# TODO: This should be better tested
|
||||
exports.addRemote = (repository, name, url, callback) ->
|
||||
if not _.isString(name)
|
||||
error = new Error("Invalid remote name: #{name}")
|
||||
return callback(error)
|
||||
|
||||
repository.addRemote(name, url, callback)
|
||||
|
||||
# TODO: This should be better tested
|
||||
exports.initApplication = (application, directory, callback) ->
|
||||
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
isValid = exports.isValidGitApplication(application)
|
||||
return callback() if isValid
|
||||
error = new Error("Invalid application: #{application}")
|
||||
return callback(error)
|
||||
|
||||
(callback) ->
|
||||
exports.getRepository(directory, callback)
|
||||
|
||||
(repository, callback) ->
|
||||
gitUrl = application.git_repository
|
||||
gitRemoteName = settings.get('gitRemote')
|
||||
exports.addRemote(repository, gitRemoteName, gitUrl, callback)
|
||||
|
||||
], callback)
|
||||
|
||||
# TODO: Find a sane way to test this
|
||||
exports.wasInitialized = (directory, callback) ->
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
exports.getRepository(directory, callback)
|
||||
|
||||
(repository, callback) ->
|
||||
gitRemoteName = settings.get('gitRemote')
|
||||
exports.hasRemote(repository, gitRemoteName, callback)
|
||||
|
||||
], callback)
|
231
lib/resin/vcs/git/git.spec.coffee
Normal file
231
lib/resin/vcs/git/git.spec.coffee
Normal file
@ -0,0 +1,231 @@
|
||||
_ = require('lodash')
|
||||
path = require('path')
|
||||
sinon = require('sinon')
|
||||
gitCli = require('git-cli')
|
||||
chai = require('chai')
|
||||
expect = chai.expect
|
||||
git = require('./git')
|
||||
mock = require('../../../../tests/utils/mock')
|
||||
settings = require('../../settings')
|
||||
|
||||
describe 'VCS Git:', ->
|
||||
|
||||
describe '#getGitDirectory()', ->
|
||||
|
||||
it 'should append .git', ->
|
||||
result = git.getGitDirectory('foobar')
|
||||
expect(result).to.equal("foobar#{path.sep}.git")
|
||||
|
||||
it 'should return undefined if no directory', ->
|
||||
for input in [ undefined, null ]
|
||||
result = git.getGitDirectory(input)
|
||||
expect(result).to.be.undefined
|
||||
|
||||
it 'should throw an error if directory is not a string', ->
|
||||
for input in [
|
||||
123
|
||||
{ hello: 'world' }
|
||||
[ 1, 2, 3 ]
|
||||
true
|
||||
false
|
||||
]
|
||||
func = _.partial(git.getGitDirectory, input)
|
||||
expect(func).to.throw(Error)
|
||||
|
||||
describe '#getCurrentGitDirectory()', ->
|
||||
|
||||
it 'should append .git to current working directory', ->
|
||||
result = git.getCurrentGitDirectory()
|
||||
expectedResult = path.join(process.cwd(), '.git')
|
||||
expect(result).to.equal(expectedResult)
|
||||
|
||||
describe '#getRepository()', ->
|
||||
|
||||
filesystem =
|
||||
gitRepo:
|
||||
name: '/repo'
|
||||
contents:
|
||||
'.git': {}
|
||||
|
||||
beforeEach ->
|
||||
mock.fs.init(filesystem)
|
||||
|
||||
afterEach ->
|
||||
mock.fs.restore()
|
||||
|
||||
it 'should throw an error if directory does not exist', (done) ->
|
||||
git.getRepository '/foobar', (error, repository) ->
|
||||
expect(error).to.be.an.instanceof(Error)
|
||||
expect(repository).to.not.exist
|
||||
done()
|
||||
|
||||
it 'should return a repository', (done) ->
|
||||
repo = filesystem.gitRepo
|
||||
git.getRepository repo.name, (error, repository) ->
|
||||
expect(error).to.not.exist
|
||||
expect(repository).to.exist
|
||||
|
||||
expectedPath = path.join(repo.name, '.git')
|
||||
expect(repository.path).to.equal(expectedPath)
|
||||
done()
|
||||
|
||||
describe '#isValidGitApplication()', ->
|
||||
|
||||
it 'should return false if no git_repository', ->
|
||||
result = git.isValidGitApplication({})
|
||||
expect(result).to.be.false
|
||||
|
||||
it 'should return false if git_repository is not a string', ->
|
||||
result = git.isValidGitApplication(git_repository: [ 1, 2, 3 ])
|
||||
expect(result).to.be.false
|
||||
|
||||
it 'should return true if git_repository is valid', ->
|
||||
repositoryUrl = 'git@git.resin.io:johndoe/app.git'
|
||||
result = git.isValidGitApplication(git_repository: repositoryUrl)
|
||||
expect(result).to.be.true
|
||||
|
||||
describe '#hasRemote()', ->
|
||||
|
||||
mockListRemotes = (result) ->
|
||||
return (options, callback) ->
|
||||
return callback(null, result)
|
||||
|
||||
beforeEach ->
|
||||
@repository =
|
||||
listRemotes: mockListRemotes([ 'resin', 'origin' ])
|
||||
|
||||
it 'should return true if it has the remote', (done) ->
|
||||
git.hasRemote @repository, 'resin', (error, hasRemote) ->
|
||||
expect(error).to.not.exist
|
||||
expect(hasRemote).to.be.true
|
||||
done()
|
||||
|
||||
it 'should return false if it does not have the remote', (done) ->
|
||||
git.hasRemote @repository, 'foobar', (error, hasRemote) ->
|
||||
expect(error).to.not.exist
|
||||
expect(hasRemote).to.be.false
|
||||
done()
|
||||
|
||||
describe '#addRemote()', ->
|
||||
|
||||
beforeEach ->
|
||||
@repository =
|
||||
addRemote: (name, url, callback) ->
|
||||
return callback()
|
||||
|
||||
@name = 'resin'
|
||||
@url = 'git@git.resin.io:johndoe/app.git'
|
||||
|
||||
# TODO: It'd be nice if we could actually test that
|
||||
# the remote was added to .git/config, but sadly
|
||||
# mockFs and child_process.exec don't seem to play well together.
|
||||
|
||||
it 'should call repository.addRemote with the correct parameters', (done) ->
|
||||
addRemoteSpy = sinon.spy(@repository, 'addRemote')
|
||||
|
||||
callback = (error) =>
|
||||
expect(error).to.not.exist
|
||||
expect(addRemoteSpy).to.have.been.calledWithExactly(@name, @url, callback)
|
||||
addRemoteSpy.restore()
|
||||
done()
|
||||
|
||||
git.addRemote(@repository, @name, @url, callback)
|
||||
|
||||
it 'should throw an error if name is not a string', (done) ->
|
||||
git.addRemote @repository, undefined, @url, (error) ->
|
||||
expect(error).to.be.an.instanceof(Error)
|
||||
done()
|
||||
|
||||
describe '#isGitRepository()', ->
|
||||
|
||||
filesystem =
|
||||
gitRepo:
|
||||
name: '/repo'
|
||||
contents:
|
||||
'.git': {}
|
||||
notGitRepo:
|
||||
name: '/not-repo'
|
||||
contents: {}
|
||||
invalidGitRepo:
|
||||
name: '/invalid-repo'
|
||||
contents:
|
||||
'.git': 'Plain text file'
|
||||
|
||||
beforeEach ->
|
||||
mock.fs.init(filesystem)
|
||||
|
||||
afterEach ->
|
||||
mock.fs.restore()
|
||||
|
||||
it 'should return true if it has a .git directory', (done) ->
|
||||
git.isGitRepository filesystem.gitRepo.name, (error, isGitRepo) ->
|
||||
expect(error).to.not.exist
|
||||
expect(isGitRepo).to.be.true
|
||||
done()
|
||||
|
||||
it 'should return false if it does not have a .git directory', (done) ->
|
||||
git.isGitRepository filesystem.notGitRepo.name, (error, isGitRepo) ->
|
||||
expect(error).to.not.exist
|
||||
expect(isGitRepo).to.be.false
|
||||
done()
|
||||
|
||||
it 'should throw an error if directory does not exist', (done) ->
|
||||
git.isGitRepository '/nonexistentdir', (error, isGitRepo) ->
|
||||
expect(error).to.be.an.instanceof(Error)
|
||||
expect(isGitRepo).to.be.undefined
|
||||
done()
|
||||
|
||||
it 'should return false it .git is a file', (done) ->
|
||||
git.isGitRepository filesystem.invalidGitRepo.name, (error, isGitRepo) ->
|
||||
expect(error).to.not.exist
|
||||
expect(isGitRepo).to.be.false
|
||||
done()
|
||||
|
||||
describe '#initApplication()', ->
|
||||
|
||||
filesystem =
|
||||
gitRepo:
|
||||
name: '/repo'
|
||||
contents:
|
||||
'.git': {}
|
||||
|
||||
notGitRepo:
|
||||
name: '/not-repo'
|
||||
contents: {}
|
||||
|
||||
beforeEach ->
|
||||
mock.fs.init(filesystem)
|
||||
@application =
|
||||
git_repository: 'git@git.resin.io:johndoe/app.git'
|
||||
|
||||
afterEach ->
|
||||
mock.fs.restore()
|
||||
|
||||
it 'should return an error if directory is not a git repo', (done) ->
|
||||
git.initApplication @application, filesystem.notGitRepo.name, (error) ->
|
||||
expect(error).to.be.an.instanceof(Error)
|
||||
done()
|
||||
|
||||
it 'should return an error if application does not contain a git repo url', (done) ->
|
||||
git.initApplication {}, filesystem.gitRepo.name, (error) ->
|
||||
expect(error).to.be.an.instanceof(Error)
|
||||
done()
|
||||
|
||||
it 'should add the remote', (done) ->
|
||||
mock.fs.restore()
|
||||
addRemoteStub = sinon.stub(git, 'addRemote')
|
||||
addRemoteStub.yields(null)
|
||||
mock.fs.init(filesystem)
|
||||
|
||||
git.initApplication @application, filesystem.gitRepo.name, (error) =>
|
||||
expect(error).to.not.exist
|
||||
expect(addRemoteStub).to.have.been.calledOnce
|
||||
|
||||
# TODO: There should be a better way to test this
|
||||
args = addRemoteStub.firstCall.args
|
||||
expect(args[1]).to.equal(settings.get('gitRemote'))
|
||||
expect(args[2]).to.equal(@application.git_repository)
|
||||
|
||||
addRemoteStub.restore()
|
||||
done()
|
||||
|
5
lib/resin/vcs/vcs.coffee
Normal file
5
lib/resin/vcs/vcs.coffee
Normal file
@ -0,0 +1,5 @@
|
||||
git = require('./git/git')
|
||||
|
||||
# We will delegate to git for now
|
||||
exports.initApplication = git.initApplication
|
||||
exports.wasInitialized = git.wasInitialized
|
Loading…
x
Reference in New Issue
Block a user