Merge branch 'mb-remove-nodegit' into as-ns-use-apm-with-npm3-and-node-4

# Conflicts:
#	package.json
This commit is contained in:
Antonio Scandurra 2016-07-14 16:49:05 +02:00
commit ab2ede30da
23 changed files with 177 additions and 1640 deletions

View File

@ -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)

View File

@ -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

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

@ -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",

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
}
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')
})
})
})

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()
@ -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')

View File

@ -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()

View File

@ -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)

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

@ -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()

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,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])

View File

@ -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))

View File

@ -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()

View File

@ -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}

View File

@ -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/)

View File

@ -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

View File

@ -8,6 +8,6 @@
}
::-webkit-scrollbar-thumb {
-webkit-border-radius: 2px;
border-radius: 2px;
background: rgba(150, 150, 150, .33);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -83,3 +83,4 @@
// Other
@font-family: 'BlinkMacSystemFont', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif;
@use-custom-controls: true; // false uses native controls

View File

@ -17,6 +17,7 @@ atom-workspace {
atom-workspace-axis.horizontal {
display: flex;
flex: 1;
min-width: 0;
}
atom-workspace-axis.vertical {