From fe4d3601d55f487b71a601b22446187e88a9fa8a Mon Sep 17 00:00:00 2001 From: joshaber Date: Wed, 2 Dec 2015 15:28:09 -0500 Subject: [PATCH] Organize similarly to git-repository.coffee so we can more easily tell what we're missing. --- src/git-repository-async.js | 447 ++++++++++++++++++++++++------------ 1 file changed, 294 insertions(+), 153 deletions(-) diff --git a/src/git-repository-async.js b/src/git-repository-async.js index 38e098b6a..77c07013a 100644 --- a/src/git-repository-async.js +++ b/src/git-repository-async.js @@ -59,14 +59,209 @@ export default class GitRepositoryAsync { } } + // Event subscription + // ================== + + onDidDestroy (callback) { + return this.emitter.on('did-destroy', callback) + } + + onDidChangeStatus (callback) { + return this.emitter.on('did-change-status', callback) + } + + onDidChangeStatuses (callback) { + return this.emitter.on('did-change-statuses', callback) + } + + // Repository details + // ================== + + // Public: A {String} indicating the type of version control system used by + // this repository. + // + // Returns `"git"`. + getType () { + return 'git' + } + + // Public: Returns a {Promise} which resolves to the {String} path of the + // repository. getPath () { return this.repoPromise.then(repo => repo.path().replace(/\/$/, '')) } - isPathIgnored (_path) { - return this.repoPromise.then(repo => Git.Ignore.pathIsIgnored(repo, _path)) + // Public: Returns a {Promise} which resolves to the {String} working + // directory path of the repository. + getWorkingDirectory () { + throw new Error('Unimplemented') } + // Public: Returns a {Promise} that resolves to true if at the root, false if + // in a subfolder of the repository. + isProjectAtRoot () { + if (!this.projectAtRoot && this.project) { + this.projectAtRoot = Promise.resolve(() => { + return this.repoPromise.then(repo => this.project.relativize(repo.workdir)) + }) + } + + return this.projectAtRoot + } + + // Public: Makes a path relative to the repository's working directory. + relativize (_path, workingDirectory) { + // Cargo-culted from git-utils. The original implementation also handles + // this.openedWorkingDirectory, which is set by git-utils when the + // repository is opened. Those branches of the if tree aren't included here + // yet, but if we determine we still need that here it should be simple to + // port. + // + // The original implementation also handled null workingDirectory as it + // pulled it from a sync function that could return null. We require it + // to be passed here. + if (!_path || !workingDirectory) { + return _path + } + + if (process.platform === 'win32') { + _path = _path.replace(/\\/g, '/') + } else { + if (_path[0] !== '/') { + return _path + } + } + + if (!/\/$/.test(workingDirectory)) { + workingDirectory = `${workingDirectory}/` + } + + if (this.isCaseInsensitive) { + const lowerCasePath = _path.toLowerCase() + + workingDirectory = workingDirectory.toLowerCase() + if (lowerCasePath.indexOf(workingDirectory) === 0) { + return _path.substring(workingDirectory.length) + } else { + if (lowerCasePath === workingDirectory) { + return '' + } + } + } + + return _path + } + + // Public: Returns true if the given branch exists. + hasBranch (branch) { + throw new Error('Unimplemented') + } + + // Public: Retrieves a shortened version of the HEAD reference value. + // + // This removes the leading segments of `refs/heads`, `refs/tags`, or + // `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7 + // characters. + // + // * `path` An optional {String} path in the repository to get this information + // for, only needed if the repository contains submodules. + // + // Returns a {String}. + getShortHead (path) { + throw new Error('Unimplemented') + } + + // Public: Is the given path a submodule in the repository? + // + // * `path` The {String} path to check. + // + // Returns a {Promise} that resolves true if the given path is a submodule in + // the repository. + isSubmodule (_path) { + return this.repoPromise + .then(repo => repo.openIndex()) + .then(index => { + const entry = index.getByPath(_path) + const submoduleMode = 57344 // TODO compose this from libgit2 constants + return entry.mode === submoduleMode + }) + } + + // Public: Returns the number of commits behind the current branch is from the + // its upstream remote branch. + // + // * `reference` The {String} branch reference name. + // * `path` The {String} path in the repository to get this information for, + // only needed if the repository contains submodules. + getAheadBehindCount (reference, path) { + throw new Error('Unimplemented') + } + + // Public: Get the cached ahead/behind commit counts for the current branch's + // upstream branch. + // + // * `path` An optional {String} path in the repository to get this information + // for, only needed if the repository has submodules. + // + // Returns an {Object} with the following keys: + // * `ahead` The {Number} of commits ahead. + // * `behind` The {Number} of commits behind. + getCachedUpstreamAheadBehindCount (path) { + throw new Error('Unimplemented') + } + + // Public: Returns the git configuration value specified by the key. + // + // * `path` An optional {String} path in the repository to get this information + // for, only needed if the repository has submodules. + getConfigValue (key, path) { + throw new Error('Unimplemented') + } + + // Public: Returns the origin url of the repository. + // + // * `path` (optional) {String} path in the repository to get this information + // for, only needed if the repository has submodules. + getOriginURL (path) { + throw new Error('Unimplemented') + } + + // Public: Returns the upstream branch for the current HEAD, or null if there + // is no upstream branch for the current HEAD. + // + // * `path` An optional {String} path in the repo to get this information for, + // only needed if the repository contains submodules. + // + // Returns a {String} branch name such as `refs/remotes/origin/master`. + getUpstreamBranch (path) { + throw new Error('Unimplemented') + } + + // Public: Gets all the local and remote references. + // + // * `path` An optional {String} path in the repository to get this information + // for, only needed if the repository has submodules. + // + // Returns an {Object} with the following keys: + // * `heads` An {Array} of head reference names. + // * `remotes` An {Array} of remote reference names. + // * `tags` An {Array} of tag reference names. + getReferences (path) { + throw new Error('Unimplemented') + } + + // Public: Returns the current {String} SHA for the given reference. + // + // * `reference` The {String} reference to get the target of. + // * `path` An optional {String} path in the repo to get the reference target + // for. Only needed if the repository contains submodules. + getReferenceTarget (reference, path) { + throw new Error('Unimplemented') + } + + // Reading Status + // ============== + isPathModified (_path) { return this._filterStatusesByPath(_path).then(statuses => { return statuses.filter(status => status.isModified()).length > 0 @@ -79,29 +274,36 @@ export default class GitRepositoryAsync { }) } - checkoutHead (_path) { - return this.repoPromise - .then(repo => { - const checkoutOptions = new Git.CheckoutOptions() - checkoutOptions.paths = [this.relativize(_path, repo.workdir())] - checkoutOptions.checkoutStrategy = Git.Checkout.STRATEGY.FORCE | Git.Checkout.STRATEGY.DISABLE_PATHSPEC_MATCH - return Git.Checkout.head(repo, checkoutOptions) - }) - .then(() => this.refreshStatusForPath(_path)) + isPathIgnored (_path) { + return this.repoPromise.then(repo => Git.Ignore.pathIsIgnored(repo, _path)) } - checkoutHeadForEditor (editor) { - return new Promise((resolve, reject) => { - const filePath = editor.getPath() - if (filePath) { - if (editor.buffer.isModified()) { - editor.buffer.reload() - } - resolve(filePath) - } else { - reject() - } - }).then(filePath => this.checkoutHead(filePath)) + // Get the status of a directory in the repository's working directory. + // + // * `directoryPath` The {String} path to check. + // + // Returns a promise resolving to a {Number} representing the status. This value can be passed to + // {::isStatusModified} or {::isStatusNew} to get more information. + + getDirectoryStatus (directoryPath) { + let relativePath + // XXX _filterSBD already gets repoPromise + return this.repoPromise + .then(repo => { + relativePath = this.relativize(directoryPath, repo.workdir()) + return this._filterStatusesByDirectory(relativePath) + }) + .then(statuses => { + return Promise.all(statuses.map(s => s.statusBit())).then(bits => { + let directoryStatus = 0 + const filteredBits = bits.filter(b => b > 0) + if (filteredBits.length > 0) { + filteredBits.forEach(bit => directoryStatus |= bit) + } + + return directoryStatus + }) + }) } // Refresh the status bit for the given path. @@ -142,34 +344,83 @@ export default class GitRepositoryAsync { return this.refreshStatusForPath(_path) } - // Get the status of a directory in the repository's working directory. + // Public: Get the cached status for the given path. // - // * `directoryPath` The {String} path to check. + // * `path` A {String} path in the repository, relative or absolute. // - // Returns a promise resolving to a {Number} representing the status. This value can be passed to - // {::isStatusModified} or {::isStatusNew} to get more information. + // Returns a {Promise} which resolves to a status {Number} or null if the + // path is not in the cache. + getCachedPathStatus (_path) { + return this.repoPromise + .then(repo => this.relativize(_path, repo.workdir())) + .then(relativePath => this.pathStatusCache[relativePath]) + } - getDirectoryStatus (directoryPath) { - let relativePath - // XXX _filterSBD already gets repoPromise + isStatusNew (statusBit) { + return (statusBit & newStatusFlags) > 0 + } + + isStatusModified (statusBit) { + return (statusBit & modifiedStatusFlags) > 0 + } + + isStatusStaged (statusBit) { + return (statusBit & indexStatusFlags) > 0 + } + + isStatusIgnored (statusBit) { + return (statusBit & (1 << 14)) > 0 + } + + isStatusDeleted (statusBit) { + return (statusBit & deletedStatusFlags) > 0 + } + + // Checking Out + // ============ + + // Public: Restore the contents of a path in the working directory and index + // to the version at `HEAD`. + // + // This is essentially the same as running: + // + // ```sh + // git reset HEAD -- + // git checkout HEAD -- + // ``` + // + // * `path` The {String} path to checkout. + // + // Returns a {Promise} that resolves or rejects depending on whether the + // method was successful. + checkoutHead (_path) { return this.repoPromise .then(repo => { - relativePath = this.relativize(directoryPath, repo.workdir()) - return this._filterStatusesByDirectory(relativePath) - }) - .then(statuses => { - return Promise.all(statuses.map(s => s.statusBit())).then(bits => { - let directoryStatus = 0 - const filteredBits = bits.filter(b => b > 0) - if (filteredBits.length > 0) { - filteredBits.forEach(bit => directoryStatus |= bit) - } - - return directoryStatus - }) + const checkoutOptions = new Git.CheckoutOptions() + checkoutOptions.paths = [this.relativize(_path, repo.workdir())] + checkoutOptions.checkoutStrategy = Git.Checkout.STRATEGY.FORCE | Git.Checkout.STRATEGY.DISABLE_PATHSPEC_MATCH + return Git.Checkout.head(repo, checkoutOptions) }) + .then(() => this.refreshStatusForPath(_path)) } + checkoutHeadForEditor (editor) { + return new Promise((resolve, reject) => { + const filePath = editor.getPath() + if (filePath) { + if (editor.buffer.isModified()) { + editor.buffer.reload() + } + resolve(filePath) + } else { + reject() + } + }).then(filePath => this.checkoutHead(filePath)) + } + + // Private + // ======= + // Get the current branch and update this.branch. // // Returns :: Promise @@ -241,74 +492,6 @@ export default class GitRepositoryAsync { return } - relativize (_path, workingDirectory) { - // Cargo-culted from git-utils. The original implementation also handles - // this.openedWorkingDirectory, which is set by git-utils when the - // repository is opened. Those branches of the if tree aren't included here - // yet, but if we determine we still need that here it should be simple to - // port. - // - // The original implementation also handled null workingDirectory as it - // pulled it from a sync function that could return null. We require it - // to be passed here. - if (!_path || !workingDirectory) { - return _path - } - - if (process.platform === 'win32') { - _path = _path.replace(/\\/g, '/') - } else { - if (_path[0] !== '/') { - return _path - } - } - - if (!/\/$/.test(workingDirectory)) { - workingDirectory = `${workingDirectory}/` - } - - if (this.isCaseInsensitive) { - const lowerCasePath = _path.toLowerCase() - - workingDirectory = workingDirectory.toLowerCase() - if (lowerCasePath.indexOf(workingDirectory) === 0) { - return _path.substring(workingDirectory.length) - } else { - if (lowerCasePath === workingDirectory) { - return '' - } - } - } - - return _path - } - - getCachedPathStatus (_path) { - return this.repoPromise.then(repo => { - return this.pathStatusCache[this.relativize(_path, repo.workdir())] - }) - } - - isStatusNew (statusBit) { - return (statusBit & newStatusFlags) > 0 - } - - isStatusModified (statusBit) { - return (statusBit & modifiedStatusFlags) > 0 - } - - isStatusStaged (statusBit) { - return (statusBit & indexStatusFlags) > 0 - } - - isStatusIgnored (statusBit) { - return (statusBit & (1 << 14)) > 0 - } - - isStatusDeleted (statusBit) { - return (statusBit & deletedStatusFlags) > 0 - } - _filterStatusesByPath (_path) { // Surely I'm missing a built-in way to do this let basePath = null @@ -329,46 +512,4 @@ export default class GitRepositoryAsync { return statuses.filter(status => status.path().indexOf(directoryPath) === 0) }) } - // Event subscription - // ================== - - onDidChangeStatus (callback) { - return this.emitter.on('did-change-status', callback) - } - - onDidChangeStatuses (callback) { - return this.emitter.on('did-change-statuses', callback) - } - - onDidDestroy (callback) { - return this.emitter.on('did-destroy', callback) - } - - // - // Section: Repository Details - // - - // Returns a {Promise} that resolves true if at the root, false if in a - // subfolder of the repository. - isProjectAtRoot () { - if (this.projectAtRoot === undefined) { - this.projectAtRoot = Promise.resolve(() => { - return this.repoPromise.then(repo => this.project.relativize(repo.workdir)) - }) - } - - return this.projectAtRoot - } - - // Returns a {Promise} that resolves true if the given path is a submodule in - // the repository. - isSubmodule (_path) { - return this.repoPromise - .then(repo => repo.openIndex()) - .then(index => { - const entry = index.getByPath(_path) - const submoduleMode = 57344 // TODO compose this from libgit2 constants - return entry.mode === submoduleMode - }) - } }