Remove GitRepositoryAsync

Signed-off-by: Nathan Sobo <nathan@github.com>
This commit is contained in:
Max Brunsfeld 2016-07-13 14:03:29 -07:00 committed by Nathan Sobo
parent 7b11c31e07
commit 58e7892492
6 changed files with 0 additions and 1496 deletions

View File

@ -8,7 +8,6 @@ module.exports =
BufferedNodeProcess: require '../src/buffered-node-process'
BufferedProcess: require '../src/buffered-process'
GitRepository: require '../src/git-repository'
GitRepositoryAsync: require '../src/git-repository-async'
Notification: require '../src/notification'
TextBuffer: TextBuffer
Point: Point

View File

@ -42,7 +42,6 @@
"mocha": "2.5.1",
"normalize-package-data": "^2.0.0",
"nslog": "^3",
"ohnogit": "0.0.13",
"oniguruma": "^5",
"pathwatcher": "~6.5",
"property-accessors": "^1.1.3",

View File

@ -1,918 +0,0 @@
'use babel'
import fs from 'fs-plus'
import path from 'path'
import temp from 'temp'
import {it, beforeEach, afterEach} from './async-spec-helpers'
import GitRepositoryAsync from '../src/git-repository-async'
import Project from '../src/project'
temp.track()
function openFixture (fixture) {
return GitRepositoryAsync.open(path.join(__dirname, 'fixtures', 'git', fixture))
}
function copyRepository (name = 'working-dir') {
const workingDirPath = temp.mkdirSync('atom-working-dir')
fs.copySync(path.join(__dirname, 'fixtures', 'git', name), workingDirPath)
fs.renameSync(path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git'))
return fs.realpathSync(workingDirPath)
}
function copySubmoduleRepository () {
const workingDirectory = copyRepository('repo-with-submodules')
const reGit = (name) => {
fs.renameSync(path.join(workingDirectory, name, 'git.git'), path.join(workingDirectory, name, '.git'))
}
reGit('jstips')
reGit('You-Dont-Need-jQuery')
return workingDirectory
}
fdescribe('GitRepositoryAsync', () => {
let repo
afterEach(() => {
if (repo != null) repo.destroy()
})
describe('@open(path)', () => {
it('should throw when no repository is found', async () => {
repo = GitRepositoryAsync.open(path.join(temp.dir, 'nogit.txt'))
let threw = false
try {
await repo.getRepo()
} catch (e) {
threw = true
}
expect(threw).toBe(true)
})
})
describe('openedPath', () => {
it('is the path passed to .open', () => {
const workingDirPath = copyRepository()
repo = GitRepositoryAsync.open(workingDirPath)
expect(repo.openedPath).toBe(workingDirPath)
})
})
describe('.getRepo()', () => {
beforeEach(() => {
const workingDirectory = copySubmoduleRepository()
repo = GitRepositoryAsync.open(workingDirectory)
waitsForPromise(() => repo.refreshStatus())
})
it('returns the repository when not given a path', async () => {
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo()
expect(nodeGitRepo1.workdir()).toBe(nodeGitRepo2.workdir())
})
it('returns the repository when given a non-submodule path', async () => {
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo('README')
expect(nodeGitRepo1.workdir()).toBe(nodeGitRepo2.workdir())
})
it('returns the submodule repository when given a submodule path', async () => {
const nodeGitRepo1 = await repo.getRepo()
const nodeGitRepo2 = await repo.getRepo('jstips')
expect(nodeGitRepo1.workdir()).not.toBe(nodeGitRepo2.workdir())
const nodeGitRepo3 = await repo.getRepo('jstips/README.md')
expect(nodeGitRepo1.workdir()).not.toBe(nodeGitRepo3.workdir())
expect(nodeGitRepo2.workdir()).toBe(nodeGitRepo3.workdir())
})
})
describe('.openRepository()', () => {
it('returns a new repository instance', async () => {
repo = openFixture('master.git')
const originalRepo = await repo.getRepo()
expect(originalRepo).not.toBeNull()
const nodeGitRepo = repo.openRepository()
expect(nodeGitRepo).not.toBeNull()
expect(originalRepo).not.toBe(nodeGitRepo)
})
})
describe('.getPath()', () => {
it('returns the repository path for a repository path', async () => {
repo = openFixture('master.git')
const repoPath = await repo.getPath()
expect(repoPath).toEqualPath(path.join(__dirname, 'fixtures', 'git', 'master.git'))
})
})
describe('.isPathIgnored(path)', () => {
beforeEach(() => {
repo = openFixture('ignore.git')
})
it('resolves true for an ignored path', async () => {
const ignored = await repo.isPathIgnored('a.txt')
expect(ignored).toBe(true)
})
it('resolves false for a non-ignored path', async () => {
const ignored = await repo.isPathIgnored('b.txt')
expect(ignored).toBe(false)
})
})
describe('.isPathModified(path)', () => {
let filePath, newPath, emptyPath
beforeEach(() => {
const workingDirPath = copyRepository()
repo = GitRepositoryAsync.open(workingDirPath)
filePath = path.join(workingDirPath, 'a.txt')
newPath = path.join(workingDirPath, 'new-path.txt')
fs.writeFileSync(newPath, "i'm new here")
emptyPath = path.join(workingDirPath, 'empty-path.txt')
})
describe('when the path is unstaged', () => {
it('resolves false if the path has not been modified', async () => {
const modified = await repo.isPathModified(filePath)
expect(modified).toBe(false)
})
it('resolves true if the path is modified', async () => {
fs.writeFileSync(filePath, 'change')
const modified = await repo.isPathModified(filePath)
expect(modified).toBe(true)
})
it('resolves false if the path is new', async () => {
const modified = await repo.isPathModified(newPath)
expect(modified).toBe(false)
})
it('resolves false if the path is invalid', async () => {
const modified = await repo.isPathModified(emptyPath)
expect(modified).toBe(false)
})
})
})
describe('.isPathNew(path)', () => {
let newPath
beforeEach(() => {
const workingDirPath = copyRepository()
repo = GitRepositoryAsync.open(workingDirPath)
newPath = path.join(workingDirPath, 'new-path.txt')
fs.writeFileSync(newPath, "i'm new here")
})
describe('when the path is unstaged', () => {
it('returns true if the path is new', async () => {
const isNew = await repo.isPathNew(newPath)
expect(isNew).toBe(true)
})
it("returns false if the path isn't new", async () => {
const modified = await repo.isPathModified(newPath)
expect(modified).toBe(false)
})
})
})
describe('.checkoutHead(path)', () => {
let filePath
beforeEach(() => {
const workingDirPath = copyRepository()
repo = GitRepositoryAsync.open(workingDirPath)
filePath = path.join(workingDirPath, 'a.txt')
})
it('no longer reports a path as modified after checkout', async () => {
let modified = await repo.isPathModified(filePath)
expect(modified).toBe(false)
fs.writeFileSync(filePath, 'ch ch changes')
modified = await repo.isPathModified(filePath)
expect(modified).toBe(true)
await repo.checkoutHead(filePath)
modified = await repo.isPathModified(filePath)
expect(modified).toBe(false)
})
it('restores the contents of the path to the original text', async () => {
fs.writeFileSync(filePath, 'ch ch changes')
await repo.checkoutHead(filePath)
expect(fs.readFileSync(filePath, 'utf8')).toBe('')
})
it('fires a did-change-status event if the checkout completes successfully', async () => {
fs.writeFileSync(filePath, 'ch ch changes')
await repo.getPathStatus(filePath)
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatus(statusHandler)
await repo.checkoutHead(filePath)
expect(statusHandler.callCount).toBe(1)
expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: 0})
await repo.checkoutHead(filePath)
expect(statusHandler.callCount).toBe(1)
})
})
describe('.checkoutHeadForEditor(editor)', () => {
let filePath
let editor
beforeEach(async () => {
spyOn(atom, 'confirm')
const workingDirPath = copyRepository()
repo = new GitRepositoryAsync(workingDirPath, {project: atom.project, config: atom.config, confirm: atom.confirm})
filePath = path.join(workingDirPath, 'a.txt')
fs.writeFileSync(filePath, 'ch ch changes')
editor = await atom.workspace.open(filePath)
})
it('displays a confirmation dialog by default', async () => {
atom.confirm.andCallFake(({buttons}) => buttons.OK())
atom.config.set('editor.confirmCheckoutHeadRevision', true)
await repo.checkoutHeadForEditor(editor)
expect(fs.readFileSync(filePath, 'utf8')).toBe('')
})
it('does not display a dialog when confirmation is disabled', async () => {
atom.config.set('editor.confirmCheckoutHeadRevision', false)
await repo.checkoutHeadForEditor(editor)
expect(fs.readFileSync(filePath, 'utf8')).toBe('')
expect(atom.confirm).not.toHaveBeenCalled()
})
})
describe('.destroy()', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('throws an exception when any method is called after it is called', async () => {
repo.destroy()
let error = null
try {
await repo.getShortHead()
} catch (e) {
error = e
}
expect(error.name).toBe(GitRepositoryAsync.DestroyedErrorName)
repo = null
})
})
describe('.getPathStatus(path)', () => {
let filePath
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
filePath = path.join(workingDirectory, 'file.txt')
})
it('trigger a status-changed event when the new status differs from the last cached one', async () => {
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatus(statusHandler)
fs.writeFileSync(filePath, '')
await repo.getPathStatus(filePath)
expect(statusHandler.callCount).toBe(1)
const status = GitRepositoryAsync.Git.Status.STATUS.WT_MODIFIED
expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: status})
fs.writeFileSync(filePath, 'abc')
await repo.getPathStatus(filePath)
expect(statusHandler.callCount).toBe(1)
})
})
describe('.getDirectoryStatus(path)', () => {
let directoryPath, filePath
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
directoryPath = path.join(workingDirectory, 'dir')
filePath = path.join(directoryPath, 'b.txt')
})
it('gets the status based on the files inside the directory', async () => {
await repo.checkoutHead(filePath)
let result = await repo.getDirectoryStatus(directoryPath)
expect(repo.isStatusModified(result)).toBe(false)
fs.writeFileSync(filePath, 'abc')
result = await repo.getDirectoryStatus(directoryPath)
expect(repo.isStatusModified(result)).toBe(true)
})
})
describe('.refreshStatus()', () => {
let newPath, modifiedPath, cleanPath, workingDirectory
beforeEach(() => {
workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
modifiedPath = path.join(workingDirectory, 'file.txt')
newPath = path.join(workingDirectory, 'untracked.txt')
cleanPath = path.join(workingDirectory, 'other.txt')
fs.writeFileSync(cleanPath, 'Full of text')
fs.writeFileSync(newPath, '')
fs.writeFileSync(modifiedPath, 'making this path modified')
newPath = fs.absolute(newPath) // specs could be running under symbol path.
})
it('returns status information for all new and modified files', async () => {
await repo.refreshStatus()
expect(await repo.getCachedPathStatus(cleanPath)).toBeUndefined()
expect(repo.isStatusNew(await repo.getCachedPathStatus(newPath))).toBe(true)
expect(repo.isStatusModified(await repo.getCachedPathStatus(modifiedPath))).toBe(true)
})
describe('in a repository with submodules', () => {
beforeEach(() => {
workingDirectory = copySubmoduleRepository()
repo = GitRepositoryAsync.open(workingDirectory)
modifiedPath = path.join(workingDirectory, 'jstips', 'README.md')
newPath = path.join(workingDirectory, 'You-Dont-Need-jQuery', 'untracked.txt')
cleanPath = path.join(workingDirectory, 'jstips', 'CONTRIBUTING.md')
fs.writeFileSync(newPath, '')
fs.writeFileSync(modifiedPath, 'making this path modified')
newPath = fs.absolute(newPath) // specs could be running under symbol path.
})
it('returns status information for all new and modified files', async () => {
await repo.refreshStatus()
expect(await repo.getCachedPathStatus(cleanPath)).toBeUndefined()
expect(repo.isStatusNew(await repo.getCachedPathStatus(newPath))).toBe(true)
expect(repo.isStatusModified(await repo.getCachedPathStatus(modifiedPath))).toBe(true)
})
})
it('caches the proper statuses when a subdir is open', async () => {
const subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
const filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, '')
atom.project.setPaths([subDir])
await atom.workspace.open('b.txt')
const repo = atom.project.getRepositories()[0].async
await repo.refreshStatus()
const status = await repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe(false)
expect(repo.isStatusNew(status)).toBe(false)
})
it('caches the proper statuses when multiple project are open', async () => {
const otherWorkingDirectory = copyRepository()
atom.project.setPaths([workingDirectory, otherWorkingDirectory])
await atom.workspace.open('b.txt')
const repo = atom.project.getRepositories()[0].async
await repo.refreshStatus()
const subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
const filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, 'some content!')
const status = await repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe(true)
expect(repo.isStatusNew(status)).toBe(false)
})
it('emits did-change-statuses if the status changes', async () => {
const someNewPath = path.join(workingDirectory, 'MyNewJSFramework.md')
fs.writeFileSync(someNewPath, '')
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
it('emits did-change-statuses if the branch changes', async () => {
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
repo._refreshBranch = jasmine.createSpy('_refreshBranch').andCallFake(() => {
return Promise.resolve(true)
})
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
it('emits did-change-statuses if the ahead/behind changes', async () => {
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
repo._refreshAheadBehindCount = jasmine.createSpy('_refreshAheadBehindCount').andCallFake(() => {
return Promise.resolve(true)
})
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
})
describe('.isProjectAtRoot()', () => {
it('returns true when the repository is at the root', async () => {
const workingDirectory = copyRepository()
atom.project.setPaths([workingDirectory])
const repo = atom.project.getRepositories()[0].async
const atRoot = await repo.isProjectAtRoot()
expect(atRoot).toBe(true)
})
it("returns false when the repository wasn't created with a project", async () => {
const workingDirectory = copyRepository()
const repo = GitRepositoryAsync.open(workingDirectory)
const atRoot = await repo.isProjectAtRoot()
expect(atRoot).toBe(false)
})
})
describe('buffer events', () => {
let repo
beforeEach(() => {
const workingDirectory = copyRepository()
atom.project.setPaths([workingDirectory])
// When the path is added to the project, the repository is refreshed. We
// need to wait for that to complete before the tests continue so that
// we're in a known state.
repo = atom.project.getRepositories()[0].async
waitsForPromise(() => repo.refreshStatus())
})
it('emits a status-changed event when a buffer is saved', async () => {
const editor = await atom.workspace.open('other.txt')
editor.insertNewline()
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatus(statusHandler)
editor.save()
waitsFor('the onDidChangeStatus handler to be called', () => statusHandler.callCount > 0)
runs(() => {
expect(statusHandler.callCount).toBeGreaterThan(0)
expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})
})
})
it('emits a status-changed event when a buffer is reloaded', async () => {
const editor = await atom.workspace.open('other.txt')
fs.writeFileSync(editor.getPath(), 'changed')
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatus(statusHandler)
editor.getBuffer().reload()
waitsFor('the onDidChangeStatus handler to be called', () => statusHandler.callCount > 0)
runs(() => {
expect(statusHandler.callCount).toBeGreaterThan(0)
expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})
})
})
it("emits a status-changed event when a buffer's path changes", async () => {
const editor = await atom.workspace.open('other.txt')
fs.writeFileSync(editor.getPath(), 'changed')
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatus(statusHandler)
editor.getBuffer().emitter.emit('did-change-path')
waitsFor('the onDidChangeStatus handler to be called', () => statusHandler.callCount > 0)
runs(() => {
expect(statusHandler.callCount).toBeGreaterThan(0)
expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})
const pathHandler = jasmine.createSpy('pathHandler')
const buffer = editor.getBuffer()
buffer.onDidChangePath(pathHandler)
buffer.emitter.emit('did-change-path')
waitsFor('the onDidChangePath handler to be called', () => pathHandler.callCount > 0)
runs(() => expect(pathHandler.callCount).toBeGreaterThan(0))
})
})
it('stops listening to the buffer when the repository is destroyed (regression)', async () => {
const editor = await atom.workspace.open('other.txt')
const repo = atom.project.getRepositories()[0]
repo.destroy()
expect(() => editor.save()).not.toThrow()
})
})
describe('when a project is deserialized', () => {
let project2
beforeEach(() => {
atom.project.setPaths([copyRepository()])
// See the comment in the 'buffer events' beforeEach for why we need to do
// this.
const repository = atom.project.getRepositories()[0].async
waitsForPromise(() => repository.refreshStatus())
})
afterEach(() => {
if (project2) project2.destroy()
})
it('subscribes to all the serialized buffers in the project', async () => {
await atom.workspace.open('file.txt')
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm, applicationDelegate: atom.applicationDelegate})
project2.deserialize(atom.project.serialize({isUnloading: true}))
const repo = project2.getRepositories()[0].async
waitsForPromise(() => repo.refreshStatus())
runs(() => {
const buffer = project2.getBuffers()[0]
waitsFor(() => buffer.loaded)
runs(() => {
buffer.append('changes')
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatus(statusHandler)
buffer.save()
waitsFor(() => statusHandler.callCount > 0)
runs(() => {
expect(statusHandler.callCount).toBeGreaterThan(0)
expect(statusHandler).toHaveBeenCalledWith({path: buffer.getPath(), pathStatus: 256})
})
})
})
})
})
describe('GitRepositoryAsync::relativize(filePath, workdir)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
// This is a change in implementation from the git-utils version
it('just returns path if workdir is not provided', () => {
const _path = '/foo/bar/baz.txt'
const relPath = repo.relativize(_path)
expect(_path).toEqual(relPath)
})
it('relativizes a repo path', () => {
const workdir = '/tmp/foo/bar/baz/'
const relativizedPath = repo.relativize(`${workdir}a/b.txt`, workdir)
expect(relativizedPath).toBe('a/b.txt')
})
it("doesn't require workdir to end in a slash", () => {
const workdir = '/tmp/foo/bar/baz'
const relativizedPath = repo.relativize(`${workdir}/a/b.txt`, workdir)
expect(relativizedPath).toBe('a/b.txt')
})
it('preserves file case', () => {
repo.isCaseInsensitive = true
const workdir = '/tmp/foo/bar/baz/'
const relativizedPath = repo.relativize(`${workdir}a/README.txt`, workdir)
expect(relativizedPath).toBe('a/README.txt')
})
})
describe('.getShortHead(path)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the human-readable branch name', async () => {
const head = await repo.getShortHead()
expect(head).toBe('master')
})
describe('in a submodule', () => {
beforeEach(() => {
const workingDirectory = copySubmoduleRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the human-readable branch name', async () => {
await repo.refreshStatus()
const head = await repo.getShortHead('jstips')
expect(head).toBe('test')
})
})
})
describe('.isSubmodule(path)', () => {
beforeEach(() => {
const workingDirectory = copySubmoduleRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it("returns false for a path that isn't a submodule", async () => {
const isSubmodule = await repo.isSubmodule('README')
expect(isSubmodule).toBe(false)
})
it('returns true for a path that is a submodule', async () => {
const isSubmodule = await repo.isSubmodule('jstips')
expect(isSubmodule).toBe(true)
})
})
describe('.getAheadBehindCount(reference, path)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns 0, 0 for a branch with no upstream', async () => {
const {ahead, behind} = await repo.getAheadBehindCount('master')
expect(ahead).toBe(0)
expect(behind).toBe(0)
})
})
describe('.getCachedUpstreamAheadBehindCount(path)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns 0, 0 for a branch with no upstream', async () => {
await repo.refreshStatus()
const {ahead, behind} = await repo.getCachedUpstreamAheadBehindCount()
expect(ahead).toBe(0)
expect(behind).toBe(0)
})
describe('in a submodule', () => {
beforeEach(() => {
const workingDirectory = copySubmoduleRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns 1, 0 for a branch which is ahead by 1', async () => {
await repo.refreshStatus()
const {ahead, behind} = await repo.getCachedUpstreamAheadBehindCount('You-Dont-Need-jQuery')
expect(ahead).toBe(1)
expect(behind).toBe(0)
})
})
})
describe('.getDiffStats(path)', () => {
let workingDirectory
beforeEach(() => {
workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the diff stat', async () => {
const filePath = path.join(workingDirectory, 'a.txt')
fs.writeFileSync(filePath, 'change')
const {added, deleted} = await repo.getDiffStats('a.txt')
expect(added).toBe(1)
expect(deleted).toBe(0)
})
})
describe('.hasBranch(branch)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('resolves true when the branch exists', async () => {
const hasBranch = await repo.hasBranch('master')
expect(hasBranch).toBe(true)
})
it("resolves false when the branch doesn't exist", async () => {
const hasBranch = await repo.hasBranch('trolleybus')
expect(hasBranch).toBe(false)
})
})
describe('.getReferences(path)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the heads, remotes, and tags', async () => {
const {heads, remotes, tags} = await repo.getReferences()
expect(heads.length).toBe(1)
expect(remotes.length).toBe(0)
expect(tags.length).toBe(0)
})
})
describe('.getReferenceTarget(reference, path)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the SHA target', async () => {
const SHA = await repo.getReferenceTarget('refs/heads/master')
expect(SHA).toBe('8a9c86f1cb1f14b8f436eb91f4b052c8802ca99e')
})
})
describe('.getConfigValue(key, path)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('looks up the value for the key', async () => {
const bare = await repo.getConfigValue('core.bare')
expect(bare).toBe('false')
})
it("resolves to null if there's no value", async () => {
const value = await repo.getConfigValue('my.special.key')
expect(value).toBeNull()
})
})
describe('.checkoutReference(reference, create)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('can create new branches', () => {
let success = false
let threw = false
waitsForPromise(() => repo.checkoutReference('my-b', true)
.then(_ => success = true)
.catch(_ => threw = true))
runs(() => {
expect(success).toBe(true)
expect(threw).toBe(false)
})
})
})
describe('.getLineDiffs(path, text)', () => {
beforeEach(() => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the old and new lines of the diff', async () => {
const [{oldStart, newStart, oldLines, newLines}] = await repo.getLineDiffs('a.txt', 'hi there')
expect(oldStart).toBe(0)
expect(oldLines).toBe(0)
expect(newStart).toBe(1)
expect(newLines).toBe(1)
})
})
describe('GitRepositoryAsync::relativizeToWorkingDirectory(_path)', () => {
let workingDirectory
beforeEach(() => {
workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
})
it('relativizes the given path to the working directory of the repository', async () => {
let absolutePath = path.join(workingDirectory, 'a.txt')
expect(await repo.relativizeToWorkingDirectory(absolutePath)).toBe('a.txt')
absolutePath = path.join(workingDirectory, 'a/b/c.txt')
expect(await repo.relativizeToWorkingDirectory(absolutePath)).toBe('a/b/c.txt')
expect(await repo.relativizeToWorkingDirectory('a.txt')).toBe('a.txt')
expect(await repo.relativizeToWorkingDirectory('/not/in/workdir')).toBe('/not/in/workdir')
expect(await repo.relativizeToWorkingDirectory(null)).toBe(null)
expect(await repo.relativizeToWorkingDirectory()).toBe(undefined)
expect(await repo.relativizeToWorkingDirectory('')).toBe('')
expect(await repo.relativizeToWorkingDirectory(workingDirectory)).toBe('')
})
describe('when the opened path is a symlink', () => {
it('relativizes against both the linked path and real path', async () => {
// Symlinks require admin privs on windows so we just skip this there,
// done in git-utils as well
if (process.platform === 'win32') {
return
}
const linkDirectory = path.join(temp.mkdirSync('atom-working-dir-symlink'), 'link')
fs.symlinkSync(workingDirectory, linkDirectory)
const linkedRepo = GitRepositoryAsync.open(linkDirectory)
expect(await linkedRepo.relativizeToWorkingDirectory(path.join(workingDirectory, 'test1'))).toBe('test1')
expect(await linkedRepo.relativizeToWorkingDirectory(path.join(linkDirectory, 'test2'))).toBe('test2')
expect(await linkedRepo.relativizeToWorkingDirectory(path.join(linkDirectory, 'test2/test3'))).toBe('test2/test3')
expect(await linkedRepo.relativizeToWorkingDirectory('test2/test3')).toBe('test2/test3')
})
it('handles case insensitive filesystems', async () => {
repo.isCaseInsensitive = true
expect(await repo.relativizeToWorkingDirectory(path.join(workingDirectory.toUpperCase(), 'a.txt'))).toBe('a.txt')
expect(await repo.relativizeToWorkingDirectory(path.join(workingDirectory.toUpperCase(), 'a/b/c.txt'))).toBe('a/b/c.txt')
})
})
})
describe('.getOriginURL()', () => {
beforeEach(() => {
const workingDirectory = copyRepository('repo-with-submodules')
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns the origin URL', async () => {
const url = await repo.getOriginURL()
expect(url).toBe('git@github.com:atom/some-repo-i-guess.git')
})
})
describe('.getUpstreamBranch()', () => {
it('returns null when there is no upstream branch', async () => {
const workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
const upstream = await repo.getUpstreamBranch()
expect(upstream).toBe(null)
})
it('returns the upstream branch', async () => {
const workingDirectory = copyRepository('repo-with-submodules')
repo = GitRepositoryAsync.open(workingDirectory)
const upstream = await repo.getUpstreamBranch()
expect(upstream).toBe('refs/remotes/origin/master')
})
})
})

View File

@ -25,16 +25,6 @@ describe "GitRepository", ->
it "returns null when no repository is found", ->
expect(GitRepository.open(path.join(temp.dir, 'nogit.txt'))).toBeNull()
describe ".async", ->
it "returns a GitRepositoryAsync for the same repo", ->
repoPath = path.join(__dirname, 'fixtures', 'git', 'master.git')
repo = new GitRepository(repoPath)
onSuccess = jasmine.createSpy('onSuccess')
waitsForPromise ->
repo.async.getPath().then(onSuccess)
runs ->
expect(onSuccess.mostRecentCall.args[0]).toEqualPath(repoPath)
describe "new GitRepository(path)", ->
it "throws an exception when no repository is found", ->
expect(-> new GitRepository(path.join(temp.dir, 'nogit.txt'))).toThrow()

View File

@ -1,558 +0,0 @@
'use babel'
import {Repository} from 'ohnogit'
import {CompositeDisposable, Disposable} from 'event-kit'
// For the most part, this class behaves the same as `GitRepository`, with a few
// notable differences:
// * Errors are generally propagated out to the caller instead of being
// swallowed within `GitRepositoryAsync`.
// * Methods accepting a path shouldn't be given a null path, unless it is
// specifically allowed as noted in the method's documentation.
export default class GitRepositoryAsync {
static open (path, options = {}) {
// QUESTION: Should this wrap Git.Repository and reject with a nicer message?
return new GitRepositoryAsync(path, options)
}
static get Git () {
return Repository.Git
}
// The name of the error thrown when an action is attempted on a destroyed
// repository.
static get DestroyedErrorName () {
return Repository.DestroyedErrorName
}
constructor (_path, options = {}) {
this.repo = Repository.open(_path, options)
this.subscriptions = new CompositeDisposable()
let {refreshOnWindowFocus = true} = options
if (refreshOnWindowFocus) {
const onWindowFocus = () => this.refreshStatus()
window.addEventListener('focus', onWindowFocus)
this.subscriptions.add(new Disposable(() => window.removeEventListener('focus', onWindowFocus)))
}
const {project, subscribeToBuffers} = options
this.project = project
if (this.project && subscribeToBuffers) {
this.project.getBuffers().forEach(buffer => this.subscribeToBuffer(buffer))
this.subscriptions.add(this.project.onDidAddBuffer(buffer => this.subscribeToBuffer(buffer)))
}
}
// This exists to provide backwards compatibility.
get _refreshingPromise () {
return this.repo._refreshingPromise
}
get openedPath () {
return this.repo.openedPath
}
// Public: Destroy this {GitRepositoryAsync} object.
//
// This destroys any tasks and subscriptions and releases the underlying
// libgit2 repository handle. This method is idempotent.
destroy () {
this.repo.destroy()
if (this.subscriptions) {
this.subscriptions.dispose()
this.subscriptions = null
}
}
// Event subscription
// ==================
// Public: Invoke the given callback when this GitRepositoryAsync's destroy()
// method is invoked.
//
// * `callback` {Function}
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidDestroy (callback) {
return this.repo.onDidDestroy(callback)
}
// Public: Invoke the given callback when a specific file's status has
// changed. When a file is updated, reloaded, etc, and the status changes, this
// will be fired.
//
// * `callback` {Function}
// * `event` {Object}
// * `path` {String} the old parameters the decoration used to have
// * `pathStatus` {Number} representing the status. This value can be passed to
// {::isStatusModified} or {::isStatusNew} to get more information.
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeStatus (callback) {
return this.repo.onDidChangeStatus(callback)
}
// Public: Invoke the given callback when a multiple files' statuses have
// changed. For example, on window focus, the status of all the paths in the
// repo is checked. If any of them have changed, this will be fired. Call
// {::getPathStatus(path)} to get the status for your path of choice.
//
// * `callback` {Function}
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeStatuses (callback) {
return this.repo.onDidChangeStatuses(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.repo.getPath()
}
// Public: Returns a {Promise} which resolves to the {String} working
// directory path of the repository.
getWorkingDirectory (_path) {
return this.repo.getWorkingDirectory()
}
// Public: Returns a {Promise} that resolves to true if at the root, false if
// in a subfolder of the repository.
isProjectAtRoot () {
if (!this.project) return Promise.resolve(false)
if (!this.projectAtRoot) {
this.projectAtRoot = this.getWorkingDirectory()
.then(wd => this.project.relativize(wd) === '')
}
return this.projectAtRoot
}
// Public: Makes a path relative to the repository's working directory.
//
// * `path` The {String} path to relativize.
//
// Returns a {Promise} which resolves to the relative {String} path.
relativizeToWorkingDirectory (_path) {
return this.repo.relativizeToWorkingDirectory(_path)
}
// Public: Makes a path relative to the repository's working directory.
//
// * `path` The {String} path to relativize.
// * `workingDirectory` The {String} working directory path.
//
// Returns the relative {String} path.
relativize (_path, workingDirectory) {
return this.repo.relativize(_path, workingDirectory)
}
// Public: Returns a {Promise} which resolves to whether the given branch
// exists.
hasBranch (branch) {
return this.repo.hasBranch(branch)
}
// 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 {Promise} which resolves to a {String}.
getShortHead (_path) {
return this.repo.getShortHead(_path)
}
// 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.repo.isSubmodule(_path)
}
// 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.
//
// Returns a {Promise} which resolves to an {Object} with the following keys:
// * `ahead` The {Number} of commits ahead.
// * `behind` The {Number} of commits behind.
getAheadBehindCount (reference, _path) {
return this.repo.getAheadBehindCount(reference, _path)
}
// 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 a {Promise} which resolves to an {Object} with the following keys:
// * `ahead` The {Number} of commits ahead.
// * `behind` The {Number} of commits behind.
getCachedUpstreamAheadBehindCount (_path) {
return this.repo.getCachedUpstreamAheadBehindCount(_path)
}
// 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.
//
// Returns a {Promise} which resolves to the {String} git configuration value
// specified by the key.
getConfigValue (key, _path) {
return this.repo.getConfigValue(key, _path)
}
// Public: Get the URL for the 'origin' remote.
//
// * `path` (optional) {String} path in the repository to get this information
// for, only needed if the repository has submodules.
//
// Returns a {Promise} which resolves to the {String} origin url of the
// repository.
getOriginURL (_path) {
return this.repo.getOriginURL(_path)
}
// 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 {Promise} which resolves to a {String} branch name such as
// `refs/remotes/origin/master`.
getUpstreamBranch (_path) {
return this.repo.getUpstreamBranch(_path)
}
// 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 a {Promise} which resolves to 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) {
return this.repo.getReferences(_path)
}
// Public: Get the 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.
//
// Returns a {Promise} which resolves to the current {String} SHA for the
// given reference.
getReferenceTarget (reference, _path) {
return this.repo.getReferenceTarget(reference, _path)
}
// Reading Status
// ==============
// Public: Resolves true if the given path is modified.
//
// * `path` The {String} path to check.
//
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
// is modified.
isPathModified (_path) {
return this.repo.isPathModified(_path)
}
// Public: Resolves true if the given path is new.
//
// * `path` The {String} path to check.
//
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
// is new.
isPathNew (_path) {
return this.repo.isPathNew(_path)
}
// Public: Is the given path ignored?
//
// * `path` The {String} path to check.
//
// Returns a {Promise} which resolves to a {Boolean} that's true if the `path`
// is ignored.
isPathIgnored (_path) {
return this.repo.isPathIgnored(_path)
}
// 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) {
return this.repo.getDirectoryStatus(directoryPath)
}
// Refresh the status bit for the given path.
//
// Note that if the status of the path has changed, this will emit a
// 'did-change-status' event.
//
// * `path` The {String} path whose status should be refreshed.
//
// Returns a {Promise} which resolves to a {Number} which is the refreshed
// status bit for the path.
refreshStatusForPath (_path) {
return this.repo.refreshStatusForPath(_path)
}
// Returns a Promise that resolves to the status bit of a given path if it has
// one, otherwise 'current'.
getPathStatus (_path) {
return this.refreshStatusForPath(_path)
}
// Public: Get the cached status for the given path.
//
// * `path` A {String} path in the repository, relative or absolute.
//
// Returns a {Promise} which resolves to a status {Number} or null if the
// path is not in the cache.
getCachedPathStatus (_path) {
return this.repo.getCachedPathStatus(_path)
}
// Public: Get the cached statuses for the repository.
//
// Returns an {Object} of {Number} statuses, keyed by {String} working
// directory-relative file names.
getCachedPathStatuses () {
return this.repo.pathStatusCache
}
// Public: Returns true if the given status indicates modification.
//
// * `statusBit` A {Number} representing the status.
//
// Returns a {Boolean} that's true if the `statusBit` indicates modification.
isStatusModified (statusBit) {
return this.repo.isStatusModified(statusBit)
}
// Public: Returns true if the given status indicates a new path.
//
// * `statusBit` A {Number} representing the status.
//
// Returns a {Boolean} that's true if the `statusBit` indicates a new path.
isStatusNew (statusBit) {
return this.repo.isStatusNew(statusBit)
}
// Public: Returns true if the given status indicates the path is staged.
//
// * `statusBit` A {Number} representing the status.
//
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
// staged.
isStatusStaged (statusBit) {
return this.repo.isStatusStaged(statusBit)
}
// Public: Returns true if the given status indicates the path is ignored.
//
// * `statusBit` A {Number} representing the status.
//
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
// ignored.
isStatusIgnored (statusBit) {
return this.repo.isStatusIgnored(statusBit)
}
// Public: Returns true if the given status indicates the path is deleted.
//
// * `statusBit` A {Number} representing the status.
//
// Returns a {Boolean} that's true if the `statusBit` indicates the path is
// deleted.
isStatusDeleted (statusBit) {
return this.repo.isStatusDeleted(statusBit)
}
// Retrieving Diffs
// ================
// Public: Retrieves the number of lines added and removed to a path.
//
// This compares the working directory contents of the path to the `HEAD`
// version.
//
// * `path` The {String} path to check.
//
// Returns a {Promise} which resolves to an {Object} with the following keys:
// * `added` The {Number} of added lines.
// * `deleted` The {Number} of deleted lines.
getDiffStats (_path) {
return this.repo.getDiffStats(_path)
}
// Public: Retrieves the line diffs comparing the `HEAD` version of the given
// path and the given text.
//
// * `path` The {String} path relative to the repository.
// * `text` The {String} to compare against the `HEAD` contents
//
// Returns an {Array} of hunk {Object}s with the following keys:
// * `oldStart` The line {Number} of the old hunk.
// * `newStart` The line {Number} of the new hunk.
// * `oldLines` The {Number} of lines in the old hunk.
// * `newLines` The {Number} of lines in the new hunk
getLineDiffs (_path, text) {
return this.repo.getLineDiffs(_path, text)
}
// 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 -- <path>
// git checkout HEAD -- <path>
// ```
//
// * `path` The {String} path to checkout.
//
// Returns a {Promise} that resolves or rejects depending on whether the
// method was successful.
checkoutHead (_path) {
return this.repo.checkoutHead(_path)
}
// Public: Checks out a branch in your repository.
//
// * `reference` The {String} reference to checkout.
// * `create` A {Boolean} value which, if true creates the new reference if
// it doesn't exist.
//
// Returns a {Promise} that resolves if the method was successful.
checkoutReference (reference, create) {
return this.repo.checkoutReference(reference, create)
}
// Private
// =======
checkoutHeadForEditor (editor) {
const filePath = editor.getPath()
if (!filePath) {
return Promise.reject()
}
if (editor.buffer.isModified()) {
editor.buffer.reload()
}
return this.checkoutHead(filePath)
}
// Refreshes the git status.
//
// Returns a {Promise} which will resolve to {null} when refresh is complete.
refreshStatus () {
let projectPathsPromises = [Promise.resolve('')]
if (this.project) {
projectPathsPromises = this.project.getPaths()
.map(p => this.relativizeToWorkingDirectory(p))
}
return Promise.all(projectPathsPromises)
.then(paths => paths.map(p => p.length > 0 ? p + '/**' : '*'))
.then(pathspecs => this.repo.refreshStatus(pathspecs))
}
// Get the NodeGit repository for the given path.
//
// * `path` The optional {String} path within the repository. This is only
// needed if you want to get the repository for that path if it is a
// submodule.
//
// Returns a {Promise} which resolves to the {NodeGit.Repository}.
getRepo (_path) {
return this.repo.getRepo(_path)
}
// Open a new instance of the underlying {NodeGit.Repository}.
//
// By opening multiple connections to the same underlying repository, users
// can safely access the same repository concurrently.
//
// Returns the new {NodeGit.Repository}.
openRepository () {
return this.repo.openRepository()
}
// Section: Private
// ================
// Has the repository been destroyed?
//
// Returns a {Boolean}.
_isDestroyed () {
return this.repo._isDestroyed()
}
// Subscribe to events on the given buffer.
subscribeToBuffer (buffer) {
const bufferSubscriptions = new CompositeDisposable()
const refreshStatusForBuffer = () => {
const _path = buffer.getPath()
if (_path) {
this.refreshStatusForPath(_path)
}
}
bufferSubscriptions.add(
buffer.onDidSave(refreshStatusForBuffer),
buffer.onDidReload(refreshStatusForBuffer),
buffer.onDidChangePath(refreshStatusForBuffer),
buffer.onDidDestroy(() => {
bufferSubscriptions.dispose()
this.subscriptions.remove(bufferSubscriptions)
})
)
this.subscriptions.add(bufferSubscriptions)
}
}

View File

@ -3,7 +3,6 @@
_ = require 'underscore-plus'
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
fs = require 'fs-plus'
GitRepositoryAsync = require './git-repository-async'
GitUtils = require 'git-utils'
Task = require './task'
@ -76,13 +75,6 @@ class GitRepository
unless @repo?
throw new Error("No Git repository found searching path: #{path}")
asyncOptions = _.clone(options)
# GitRepository itself will handle these cases by manually calling through
# to the async repo.
asyncOptions.refreshOnWindowFocus = false
asyncOptions.subscribeToBuffers = false
@async = GitRepositoryAsync.open(path, asyncOptions)
@statuses = {}
@upstream = {ahead: 0, behind: 0}
for submodulePath, submoduleRepo of @repo.submodules