diff --git a/app/classes/git/Repository.js b/app/classes/git/Repository.js new file mode 100644 index 0000000..d7069a1 --- /dev/null +++ b/app/classes/git/Repository.js @@ -0,0 +1,76 @@ +const { Injectable } = require('flitter-di') +const UniversalPath = require('../logical/UniversalPath') + +class Repository extends Injectable { + static fromString(string) { + const path = UniversalPath.fromString(string) + return new this(path) + } + + constructor(root_path) { + super() + this._host = root_path.host + this._path = root_path + } + + async is_repo() { + const git_dir = await this._path.child('.git') + await git_dir.classify() + return git_dir.is_directory() + } + + async init() { + if ( !(await this.is_repo()) ) { + await this._path.classify() + if ( !this._path.is_valid() ) await this._path.touch(true) + await this._git_cmd(`init`) + } + } + + async destroy() { + const git_dir = await this._path.child('.git') + await git_dir.unlink() + } + + async clone(from) { + await this._path.classify() + if ( !this._path.is_valid() ) await this._path.touch(true) + await this._git_cmd(`clone ${from} ${this._path.path}`) + } + + async stage(...blobs) { + await this._git_cmd(`add ${blobs.map(x => '"'+x+'"').join(' ')}`) + } + + async unstage(...blobs) { + await this._git_cmd(`reset ${blobs.map(x => '"'+x+'"').join(' ')}`) + } + + async commit(message) { + await this._git_cmd(`commit -m "${message}"`) + } + + async tag(label) { + await this._git_cmd(`tag "${label}"`) + } + + async push(target = false, ref = false) { + await this._git_cmd(`push${target ? ' '+target : ''}${ref ? ' '+ref : ''} --tags`) + } + + async checkout(ref = 'master') { + await this._git_cmd(`checkout "${ref}"`) + } + + async stash() { + await this._git_cmd(`stash`) + } + + async _git_cmd(command) { + const cd_cmd = await this._host.get_directory_change_command(this._path) + const cmd = `${cd_cmd} && git ${command}` + return this._host.run(cmd) + } +} + +module.exports = exports = Repository diff --git a/app/classes/logical/UniversalPath.js b/app/classes/logical/UniversalPath.js index 4cdc485..3799891 100644 --- a/app/classes/logical/UniversalPath.js +++ b/app/classes/logical/UniversalPath.js @@ -65,10 +65,16 @@ class UniversalPath extends Injectable { async parent() { await this.classify() - if ( this.is_directory() ) return path.resolve(this.path, '..') + if ( this.is_directory() ) return path.join(this.path, '..') return this.directory() } + async child(subpath) { + await this.classify() + if ( !this.is_directory() ) throw new Error('Cannot get sub-path: not a directory: '+this.path) + return this.constructor.fromString(path.join(String(this), subpath)) + } + name() { return path.basename(this.path) } diff --git a/app/classes/logical/error/FilesystemResourceNotFoundError.js b/app/classes/logical/error/FilesystemResourceNotFoundError.js new file mode 100644 index 0000000..f773b7c --- /dev/null +++ b/app/classes/logical/error/FilesystemResourceNotFoundError.js @@ -0,0 +1,5 @@ +class FilesystemResourceNotFoundError extends Error { + +} + +module.exports = exports = FilesystemResourceNotFoundError diff --git a/app/classes/metal/Host.js b/app/classes/metal/Host.js index f9955b6..77faefe 100644 --- a/app/classes/metal/Host.js +++ b/app/classes/metal/Host.js @@ -2,6 +2,7 @@ const { Injectable } = require('flitter-di') const ImplementationError = require('libflitter/errors/ImplementationError') const PermissionDeniedError = require('../logical/error/PermissionDeniedError') const CommandNotFoundError = require('../logical/error/CommandNotFoundError') +const FilesystemResourceNotFoundError = require('../logical/error/FilesystemResourceNotFoundError') const uuid = require('uuid/v4') const UniversalPath = require('../logical/UniversalPath') const SystemMetrics = require('../logical/SystemMetrics') @@ -181,7 +182,7 @@ class Host extends Injectable { const result = await this.execute(command) if ( result.exit_code !== 0 || result.clean_out.length < 1 ) { const E = this._get_result_error_class(result) - throw new E('Unable to get line output from command: '+command) + throw new E('Unable to get line output from command: '+command+'\n'+result.clean_err) } return this.utility.infer(result.clean_out[0].trim()) } @@ -190,7 +191,7 @@ class Host extends Injectable { const result = await this.execute(command) if ( result.exit_code !== 0 ) { const E = this._get_result_error_class(result) - throw new E('Unable to run command: '+command) + throw new E('Unable to run command: '+command+'\n'+result.clean_err) } return result } @@ -218,6 +219,13 @@ class Host extends Injectable { 'command not found', ] + const resource_not_found = [ + 'no such file', + 'no such directory', + 'no such file or directory', + 'no such directory or file', + ] + for ( const phrase of access_denied_phrases ) { if ( both.includes(phrase) ) return PermissionDeniedError } @@ -226,6 +234,10 @@ class Host extends Injectable { if ( both.includes(phrase) ) return CommandNotFoundError } + for ( const phrase of resource_not_found ) { + if ( both.includes(phrase) ) return FilesystemResourceNotFoundError + } + return Error } }