mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 23:48:05 +03:00
Merge branch 'mb-remove-nodegit' into as-ns-use-apm-with-npm3-and-node-4
# Conflicts: # package.json
This commit is contained in:
commit
ab2ede30da
@ -11,6 +11,7 @@ AWS = require 'aws-sdk'
|
||||
grunt = null
|
||||
|
||||
token = process.env.ATOM_ACCESS_TOKEN
|
||||
repo = process.env.ATOM_REPO ? 'atom/atom'
|
||||
defaultHeaders =
|
||||
Authorization: "token #{token}"
|
||||
'User-Agent': 'Atom'
|
||||
@ -119,7 +120,8 @@ logError = (message, error, details) ->
|
||||
zipAssets = (buildDir, assets, callback) ->
|
||||
zip = (directory, sourcePath, assetName, callback) ->
|
||||
if process.platform is 'win32'
|
||||
zipCommand = "C:/psmodules/7z.exe a -r #{assetName} \"#{sourcePath}\""
|
||||
sevenZipPath = if process.env.JANKY_SHA1? then "C:/psmodules/" else ""
|
||||
zipCommand = "#{sevenZipPath}7z.exe a -r \"#{assetName}\" \"#{sourcePath}\""
|
||||
else
|
||||
zipCommand = "zip -r --symlinks '#{assetName}' '#{sourcePath}'"
|
||||
options = {cwd: directory, maxBuffer: Infinity}
|
||||
@ -134,10 +136,10 @@ zipAssets = (buildDir, assets, callback) ->
|
||||
async.parallel(tasks, callback)
|
||||
|
||||
getAtomDraftRelease = (isPrerelease, branchName, callback) ->
|
||||
atomRepo = new GitHub({repo: 'atom/atom', token})
|
||||
atomRepo = new GitHub({repo: repo, token})
|
||||
atomRepo.getReleases {prerelease: isPrerelease}, (error, releases=[]) ->
|
||||
if error?
|
||||
logError('Fetching atom/atom releases failed', error, releases)
|
||||
logError("Fetching #{repo} #{if isPrerelease then "pre" else "" }releases failed", error, releases)
|
||||
callback(error)
|
||||
else
|
||||
[firstDraft] = releases.filter ({draft}) -> draft
|
||||
@ -160,7 +162,7 @@ getAtomDraftRelease = (isPrerelease, branchName, callback) ->
|
||||
createAtomDraftRelease = (isPrerelease, branchName, callback) ->
|
||||
{version} = require('../../package.json')
|
||||
options =
|
||||
uri: 'https://api.github.com/repos/atom/atom/releases'
|
||||
uri: "https://api.github.com/repos/#{repo}/releases"
|
||||
method: 'POST'
|
||||
headers: defaultHeaders
|
||||
json:
|
||||
@ -177,7 +179,7 @@ createAtomDraftRelease = (isPrerelease, branchName, callback) ->
|
||||
|
||||
request options, (error, response, body='') ->
|
||||
if error? or response.statusCode isnt 201
|
||||
logError("Creating atom/atom draft release failed", error, body)
|
||||
logError("Creating #{repo} draft release failed", error, body)
|
||||
callback(error ? new Error(response.statusCode))
|
||||
else
|
||||
callback(null, body)
|
||||
|
@ -25,7 +25,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
|
||||
### Fedora / CentOS / RHEL
|
||||
|
||||
* `sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools`
|
||||
* Instructions for [Node.js](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager#enterprise-linux-and-fedora).
|
||||
* Instructions for [Node.js](https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora).
|
||||
|
||||
### Arch
|
||||
|
||||
|
@ -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
|
||||
|
25
package.json
25
package.json
@ -16,7 +16,7 @@
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "6.3.2",
|
||||
"atom-ui": "0.3.3",
|
||||
"atom-ui": "0.4.1",
|
||||
"babel-core": "^5.8.21",
|
||||
"cached-run-in-this-context": "0.4.1",
|
||||
"chai": "3.5.0",
|
||||
@ -42,7 +42,6 @@
|
||||
"mocha": "2.5.1",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"nslog": "^3",
|
||||
"ohnogit": "0.0.14",
|
||||
"oniguruma": "^5",
|
||||
"pathwatcher": "~6.5",
|
||||
"property-accessors": "^1.1.3",
|
||||
@ -76,12 +75,12 @@
|
||||
"one-light-syntax": "1.3.0",
|
||||
"solarized-dark-syntax": "1.0.2",
|
||||
"solarized-light-syntax": "1.0.2",
|
||||
"about": "1.5.2",
|
||||
"about": "1.5.3",
|
||||
"archive-view": "0.61.1",
|
||||
"autocomplete-atom-api": "0.10.0",
|
||||
"autocomplete-css": "0.11.2",
|
||||
"autocomplete-html": "0.7.2",
|
||||
"autocomplete-plus": "2.31.0",
|
||||
"autocomplete-plus": "2.31.1",
|
||||
"autocomplete-snippets": "1.11.0",
|
||||
"autoflow": "0.27.0",
|
||||
"autosave": "0.23.1",
|
||||
@ -93,12 +92,12 @@
|
||||
"dev-live-reload": "0.47.0",
|
||||
"encoding-selector": "0.22.0",
|
||||
"exception-reporting": "0.38.1",
|
||||
"find-and-replace": "0.200.0",
|
||||
"fuzzy-finder": "1.2.0",
|
||||
"git-diff": "1.0.1",
|
||||
"find-and-replace": "0.201.0",
|
||||
"fuzzy-finder": "1.3.0-sync-git",
|
||||
"git-diff": "1.1.0-sync-git",
|
||||
"go-to-line": "0.31.0",
|
||||
"grammar-selector": "0.48.1",
|
||||
"image-view": "0.58.1",
|
||||
"image-view": "0.58.2",
|
||||
"incompatible-packages": "0.26.1",
|
||||
"keybinding-resolver": "0.35.0",
|
||||
"line-ending-selector": "0.5.0",
|
||||
@ -106,17 +105,17 @@
|
||||
"markdown-preview": "0.158.0",
|
||||
"metrics": "0.53.1",
|
||||
"notifications": "0.65.0",
|
||||
"open-on-github": "1.1.0",
|
||||
"open-on-github": "1.2.0-sync-git",
|
||||
"package-generator": "1.0.0",
|
||||
"settings-view": "0.238.1",
|
||||
"settings-view": "0.240.1",
|
||||
"snippets": "1.0.2",
|
||||
"spell-check": "0.67.1",
|
||||
"status-bar": "1.3.1",
|
||||
"styleguide": "0.46.0",
|
||||
"status-bar": "1.4.0-sync-git",
|
||||
"styleguide": "0.47.0",
|
||||
"symbols-view": "0.113.0",
|
||||
"tabs": "0.99.0",
|
||||
"timecop": "0.33.1",
|
||||
"tree-view": "0.208.0",
|
||||
"tree-view": "0.208.1",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.34.0",
|
||||
"whitespace": "0.32.2",
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
describe('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')
|
||||
})
|
||||
})
|
||||
})
|
@ -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()
|
||||
@ -259,36 +249,6 @@ describe "GitRepository", ->
|
||||
expect(repo.isStatusModified(status)).toBe false
|
||||
expect(repo.isStatusNew(status)).toBe false
|
||||
|
||||
it 'caches the proper statuses when multiple project are open', ->
|
||||
otherWorkingDirectory = copyRepository()
|
||||
|
||||
atom.project.setPaths([workingDirectory, otherWorkingDirectory])
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('b.txt')
|
||||
|
||||
statusHandler = null
|
||||
runs ->
|
||||
repo = atom.project.getRepositories()[0]
|
||||
|
||||
statusHandler = jasmine.createSpy('statusHandler')
|
||||
repo.onDidChangeStatuses statusHandler
|
||||
repo.refreshStatus()
|
||||
|
||||
waitsFor ->
|
||||
statusHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
subDir = path.join(workingDirectory, 'dir')
|
||||
fs.mkdirSync(subDir)
|
||||
|
||||
filePath = path.join(subDir, 'b.txt')
|
||||
fs.writeFileSync(filePath, '')
|
||||
|
||||
status = repo.getCachedPathStatus(filePath)
|
||||
expect(repo.isStatusModified(status)).toBe true
|
||||
expect(repo.isStatusNew(status)).toBe false
|
||||
|
||||
it 'caches statuses that were looked up synchronously', ->
|
||||
originalContent = 'undefined'
|
||||
fs.writeFileSync(modifiedPath, 'making this path modified')
|
||||
|
@ -224,11 +224,12 @@ class AtomEnvironment extends Model
|
||||
|
||||
@observeAutoHideMenuBar()
|
||||
|
||||
checkPortableHomeWritable = ->
|
||||
checkPortableHomeWritable = =>
|
||||
responseChannel = "check-portable-home-writable-response"
|
||||
ipcRenderer.on responseChannel, (event, response) ->
|
||||
ipcRenderer.removeAllListeners(responseChannel)
|
||||
atom.notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable
|
||||
@notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable
|
||||
@disposables.add new Disposable -> ipcRenderer.removeAllListeners(responseChannel)
|
||||
ipcRenderer.send('check-portable-home-writable', responseChannel)
|
||||
|
||||
checkPortableHomeWritable()
|
||||
|
@ -72,7 +72,10 @@ function needsPatching (options = { platform: process.platform, env: process.env
|
||||
// underlying functionality.
|
||||
function clone (to, from) {
|
||||
for (var key in to) {
|
||||
delete to[key]
|
||||
// Don't erase NODE_ENV. Fixes #12024
|
||||
if (key !== 'NODE_ENV') {
|
||||
delete to[key]
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(to, from)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -77,7 +77,7 @@ class GitRepositoryProvider
|
||||
unless repo
|
||||
repo = GitRepository.open(gitDirPath, {@project, @config})
|
||||
return null unless repo
|
||||
repo.async.onDidDestroy(=> delete @pathToRepository[gitDirPath])
|
||||
repo.onDidDestroy(=> delete @pathToRepository[gitDirPath])
|
||||
@pathToRepository[gitDirPath] = repo
|
||||
repo.refreshIndex()
|
||||
repo.refreshStatus()
|
||||
|
@ -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,19 +75,11 @@ 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
|
||||
submoduleRepo.upstream = {ahead: 0, behind: 0}
|
||||
|
||||
@statusesByPath = {}
|
||||
|
||||
{@project, @config, refreshOnWindowFocus} = options
|
||||
|
||||
refreshOnWindowFocus ?= true
|
||||
@ -126,10 +117,6 @@ class GitRepository
|
||||
@subscriptions.dispose()
|
||||
@subscriptions = null
|
||||
|
||||
if @async?
|
||||
@async.destroy()
|
||||
@async = null
|
||||
|
||||
# Public: Returns a {Boolean} indicating if this repository has been destroyed.
|
||||
isDestroyed: ->
|
||||
not @repo?
|
||||
@ -322,7 +309,7 @@ class GitRepository
|
||||
getDirectoryStatus: (directoryPath) ->
|
||||
directoryPath = "#{@relativize(directoryPath)}/"
|
||||
directoryStatus = 0
|
||||
for path, status of Object.assign({}, @async.getCachedPathStatuses(), @statusesByPath)
|
||||
for path, status of @statuses
|
||||
directoryStatus |= status if path.indexOf(directoryPath) is 0
|
||||
directoryStatus
|
||||
|
||||
@ -335,24 +322,13 @@ class GitRepository
|
||||
getPathStatus: (path) ->
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
|
||||
# This is a bit particular. If a package calls `getPathStatus` like this:
|
||||
# - change the file
|
||||
# - getPathStatus
|
||||
# - change the file
|
||||
# - getPathStatus
|
||||
# We need to preserve the guarantee that each call to `getPathStatus` will
|
||||
# synchronously emit 'did-change-status'. So we need to keep a cache of the
|
||||
# statuses found from this call.
|
||||
currentPathStatus = @getCachedRelativePathStatus(relativePath) ? 0
|
||||
|
||||
# Trigger events emitted on the async repo as well
|
||||
@async.refreshStatusForPath(path)
|
||||
|
||||
currentPathStatus = @statuses[relativePath] ? 0
|
||||
pathStatus = repo.getStatus(repo.relativize(path)) ? 0
|
||||
pathStatus = 0 if repo.isStatusIgnored(pathStatus)
|
||||
@statusesByPath[relativePath] = pathStatus
|
||||
|
||||
if pathStatus > 0
|
||||
@statuses[relativePath] = pathStatus
|
||||
else
|
||||
delete @statuses[relativePath]
|
||||
if currentPathStatus isnt pathStatus
|
||||
@emitter.emit 'did-change-status', {path, pathStatus}
|
||||
|
||||
@ -364,11 +340,7 @@ class GitRepository
|
||||
#
|
||||
# Returns a status {Number} or null if the path is not in the cache.
|
||||
getCachedPathStatus: (path) ->
|
||||
relativePath = @relativize(path)
|
||||
@getCachedRelativePathStatus(relativePath)
|
||||
|
||||
getCachedRelativePathStatus: (relativePath) ->
|
||||
@statusesByPath[relativePath] ? @async.getCachedPathStatuses()[relativePath]
|
||||
@statuses[@relativize(path)]
|
||||
|
||||
# Public: Returns true if the given status indicates modification.
|
||||
#
|
||||
@ -492,42 +464,29 @@ class GitRepository
|
||||
|
||||
# Refreshes the current git status in an outside process and asynchronously
|
||||
# updates the relevant properties.
|
||||
#
|
||||
# Returns a promise that resolves when the repository has been refreshed.
|
||||
refreshStatus: ->
|
||||
statusesChanged = false
|
||||
@handlerPath ?= require.resolve('./repository-status-handler')
|
||||
|
||||
# Listen for `did-change-statuses` so we know if something changed. But we
|
||||
# need to wait to propagate it until after we've set the branch and cleared
|
||||
# the `statusesByPath` cache. So just set a flag, and we'll emit the event
|
||||
# after refresh is done.
|
||||
subscription = @async.onDidChangeStatuses ->
|
||||
subscription?.dispose()
|
||||
subscription = null
|
||||
relativeProjectPaths = @project?.getPaths()
|
||||
.map (path) => @relativize(path)
|
||||
.filter (path) -> path.length > 0
|
||||
|
||||
statusesChanged = true
|
||||
@statusTask?.terminate()
|
||||
new Promise (resolve) =>
|
||||
@statusTask = Task.once @handlerPath, @getPath(), relativeProjectPaths, ({statuses, upstream, branch, submodules}) =>
|
||||
statusesUnchanged = _.isEqual(statuses, @statuses) and
|
||||
_.isEqual(upstream, @upstream) and
|
||||
_.isEqual(branch, @branch) and
|
||||
_.isEqual(submodules, @submodules)
|
||||
|
||||
asyncRefresh = @async.refreshStatus().then =>
|
||||
subscription?.dispose()
|
||||
subscription = null
|
||||
|
||||
@branch = @async?.branch
|
||||
@statusesByPath = {}
|
||||
|
||||
if statusesChanged
|
||||
@emitter.emit 'did-change-statuses'
|
||||
|
||||
syncRefresh = new Promise (resolve, reject) =>
|
||||
@handlerPath ?= require.resolve('./repository-status-handler')
|
||||
|
||||
@statusTask?.terminate()
|
||||
@statusTask = Task.once @handlerPath, @getPath(), ({upstream, submodules}) =>
|
||||
@statuses = statuses
|
||||
@upstream = upstream
|
||||
@branch = branch
|
||||
@submodules = submodules
|
||||
|
||||
for submodulePath, submoduleRepo of @getRepo().submodules
|
||||
submoduleRepo.upstream = submodules[submodulePath]?.upstream ? {ahead: 0, behind: 0}
|
||||
|
||||
unless statusesUnchanged
|
||||
@emitter.emit 'did-change-statuses'
|
||||
resolve()
|
||||
|
||||
return Promise.all([asyncRefresh, syncRefresh])
|
||||
|
@ -33,8 +33,20 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-success`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
@ -46,8 +58,20 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-info`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
@ -59,8 +83,20 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-warning`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
@ -72,12 +108,26 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-error`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
# in the notification header. Defaults to `'flame'`.
|
||||
# * `stack` (optional) A preformatted {String} with stack trace information
|
||||
# describing the location of the error.
|
||||
addError: (message, options) ->
|
||||
@addNotification(new Notification('error', message, options))
|
||||
|
||||
@ -85,12 +135,26 @@ class NotificationManager
|
||||
#
|
||||
# * `message` A {String} message
|
||||
# * `options` (optional) An options {Object} with the following keys:
|
||||
# * `detail` (optional) A {String} with additional details about the
|
||||
# notification.
|
||||
# * `buttons` (optional) An {Array} of {Object} where each {Object} has the
|
||||
# following options:
|
||||
# * `className` (optional) {String} a class name to add to the button's
|
||||
# default class name (`btn btn-error`).
|
||||
# * `onDidClick` (optional) {Function} callback to call when the button
|
||||
# has been clicked. The context will be set to the
|
||||
# {NotificationElement} instance.
|
||||
# * `text` {String} inner text for the button
|
||||
# * `description` (optional) A Markdown {String} containing a longer
|
||||
# description about the notification. By default, this **will not**
|
||||
# preserve newlines and whitespace when it is rendered.
|
||||
# * `detail` (optional) A plain-text {String} containing additional details
|
||||
# about the notification. By default, this **will** preserve newlines
|
||||
# and whitespace when it is rendered.
|
||||
# * `dismissable` (optional) A {Boolean} indicating whether this
|
||||
# notification can be dismissed by the user. Defaults to `false`.
|
||||
# * `icon` (optional) A {String} name of an icon from Octicons to display
|
||||
# in the notification header. Defaults to `'bug'`.
|
||||
# * `stack` (optional) A preformatted {String} with stack trace information
|
||||
# describing the location of the error.
|
||||
addFatalError: (message, options) ->
|
||||
@addNotification(new Notification('fatal', message, options))
|
||||
|
||||
|
@ -426,8 +426,8 @@ class Package
|
||||
return @mainModule if @mainModuleRequired
|
||||
unless @isCompatible()
|
||||
console.warn """
|
||||
Failed to require the main module of '#{@name}' because it requires an incompatible native module.
|
||||
Run `apm rebuild` in the package directory to resolve.
|
||||
Failed to require the main module of '#{@name}' because it requires one or more incompatible native modules (#{_.map(@incompatibleModules, 'name').join(', ')}).
|
||||
Run `apm rebuild` in the package directory and restart Atom to resolve.
|
||||
"""
|
||||
return
|
||||
mainModulePath = @getMainModulePath()
|
||||
|
@ -5,15 +5,32 @@ module.exports = (repoPath, paths = []) ->
|
||||
repo = Git.open(repoPath)
|
||||
|
||||
upstream = {}
|
||||
statuses = {}
|
||||
submodules = {}
|
||||
branch = null
|
||||
|
||||
if repo?
|
||||
# Statuses in main repo
|
||||
workingDirectoryPath = repo.getWorkingDirectory()
|
||||
repoStatus = (if paths.length > 0 then repo.getStatusForPaths(paths) else repo.getStatus())
|
||||
for filePath, status of repoStatus
|
||||
statuses[filePath] = status
|
||||
|
||||
# Statuses in submodules
|
||||
for submodulePath, submoduleRepo of repo.submodules
|
||||
submodules[submodulePath] =
|
||||
branch: submoduleRepo.getHead()
|
||||
upstream: submoduleRepo.getAheadBehindCount()
|
||||
|
||||
workingDirectoryPath = submoduleRepo.getWorkingDirectory()
|
||||
for filePath, status of submoduleRepo.getStatus()
|
||||
absolutePath = path.join(workingDirectoryPath, filePath)
|
||||
# Make path relative to parent repository
|
||||
relativePath = repo.relativize(absolutePath)
|
||||
statuses[relativePath] = status
|
||||
|
||||
upstream = repo.getAheadBehindCount()
|
||||
branch = repo.getHead()
|
||||
repo.release()
|
||||
|
||||
{upstream, submodules}
|
||||
{statuses, upstream, branch, submodules}
|
||||
|
@ -15,6 +15,9 @@ AnyConstructor = Symbol('any-constructor')
|
||||
# application logic and is the primary point of API interaction. The view
|
||||
# just handles presentation.
|
||||
#
|
||||
# Note: Models can be any object, but must implement a `getTitle()` function
|
||||
# if they are to be displayed in a {Pane}
|
||||
#
|
||||
# View providers inform the workspace how your model objects should be
|
||||
# presented in the DOM. A view provider must always return a DOM node, which
|
||||
# makes [HTML 5 custom elements](http://www.html5rocks.com/en/tutorials/webcomponents/customelements/)
|
||||
|
@ -589,7 +589,11 @@ class Workspace extends Model
|
||||
|
||||
# Public: Register an opener for a uri.
|
||||
#
|
||||
# An {TextEditor} will be used if no openers return a value.
|
||||
# When a URI is opened via {Workspace::open}, Atom loops through its registered
|
||||
# opener functions until one returns a value for the given uri.
|
||||
# Openers are expected to return an object that inherits from HTMLElement or
|
||||
# a model which has an associated view in the {ViewRegistry}.
|
||||
# A {TextEditor} will be used if no opener returns a value.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
@ -1093,7 +1097,7 @@ class Workspace extends Model
|
||||
checkoutHead = =>
|
||||
@project.repositoryForDirectory(new Directory(editor.getDirectoryPath()))
|
||||
.then (repository) ->
|
||||
repository?.async.checkoutHeadForEditor(editor)
|
||||
repository?.checkoutHeadForEditor(editor)
|
||||
|
||||
if @config.get('editor.confirmCheckoutHeadRevision')
|
||||
@applicationDelegate.confirm
|
||||
|
@ -8,6 +8,6 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
background: rgba(150, 150, 150, .33);
|
||||
}
|
||||
|
@ -5,13 +5,13 @@
|
||||
// editor resource with a tab.
|
||||
atom-pane-container {
|
||||
position: relative;
|
||||
display: -webkit-flex;
|
||||
-webkit-flex: 1;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
atom-pane-axis {
|
||||
display: -webkit-flex;
|
||||
-webkit-flex: 1;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
& > atom-pane-resize-handle {
|
||||
@ -21,7 +21,7 @@ atom-pane-container {
|
||||
}
|
||||
|
||||
atom-pane-axis.vertical {
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
|
||||
& > atom-pane-resize-handle {
|
||||
width: 100%;
|
||||
@ -33,7 +33,7 @@ atom-pane-container {
|
||||
}
|
||||
|
||||
atom-pane-axis.horizontal {
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
|
||||
& > atom-pane-resize-handle {
|
||||
width: 8px;
|
||||
@ -46,15 +46,15 @@ atom-pane-container {
|
||||
|
||||
atom-pane {
|
||||
position: relative;
|
||||
display: -webkit-flex;
|
||||
-webkit-flex: 1;
|
||||
-webkit-flex-direction: column;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
min-width: 0;
|
||||
|
||||
.item-views {
|
||||
-webkit-flex: 1;
|
||||
display: -webkit-flex;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
|
@ -23,13 +23,13 @@ atom-text-editor {
|
||||
.define-selection-flash-color-if-not-defined() { @syntax-selection-flash-color: rgba(100, 255, 100, 0.7); }
|
||||
.define-selection-flash-color-if-not-defined();
|
||||
|
||||
@-webkit-keyframes flash {
|
||||
@keyframes flash {
|
||||
from { background-color: @syntax-selection-flash-color; }
|
||||
to { background-color: null; }
|
||||
}
|
||||
|
||||
atom-text-editor .flash.selection .region {
|
||||
-webkit-animation-name: flash;
|
||||
-webkit-animation-duration: .5s;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
animation-name: flash;
|
||||
animation-duration: .5s;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
|
@ -22,13 +22,13 @@ atom-overlay {
|
||||
|
||||
// TODO: Remove the following styles when the editor shadow DOM can no longer be disabled
|
||||
atom-text-editor {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
.editor-contents {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
cursor: text;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
@ -96,7 +96,7 @@ atom-text-editor {
|
||||
z-index: 0;
|
||||
|
||||
overflow: hidden;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
.editor-contents--private {
|
||||
width: 100%;
|
||||
cursor: text;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
@ -79,7 +79,7 @@
|
||||
z-index: 0;
|
||||
|
||||
overflow: hidden;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
|
@ -83,3 +83,4 @@
|
||||
// Other
|
||||
|
||||
@font-family: 'BlinkMacSystemFont', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif;
|
||||
@use-custom-controls: true; // false uses native controls
|
||||
|
@ -17,6 +17,7 @@ atom-workspace {
|
||||
atom-workspace-axis.horizontal {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
atom-workspace-axis.vertical {
|
||||
|
Loading…
Reference in New Issue
Block a user