diff --git a/build/tasks/publish-build-task.coffee b/build/tasks/publish-build-task.coffee index 0a18c9c23..2802f827d 100644 --- a/build/tasks/publish-build-task.coffee +++ b/build/tasks/publish-build-task.coffee @@ -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) diff --git a/docs/build-instructions/linux.md b/docs/build-instructions/linux.md index 126604c49..f737a6cda 100644 --- a/docs/build-instructions/linux.md +++ b/docs/build-instructions/linux.md @@ -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 diff --git a/exports/atom.coffee b/exports/atom.coffee index 4953d3756..81d1726b8 100644 --- a/exports/atom.coffee +++ b/exports/atom.coffee @@ -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 diff --git a/package.json b/package.json index e2f9a07ba..1829a077b 100644 --- a/package.json +++ b/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", diff --git a/spec/git-repository-async-spec.js b/spec/git-repository-async-spec.js deleted file mode 100644 index 8b824bab3..000000000 --- a/spec/git-repository-async-spec.js +++ /dev/null @@ -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') - }) - }) -}) diff --git a/spec/git-spec.coffee b/spec/git-spec.coffee index a9de506a2..ef87d83ee 100644 --- a/spec/git-spec.coffee +++ b/spec/git-spec.coffee @@ -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') diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 5247ceb97..7c81eaeee 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -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() diff --git a/src/environment-helpers.js b/src/environment-helpers.js index 5a9ef8e3a..f4224fbe4 100644 --- a/src/environment-helpers.js +++ b/src/environment-helpers.js @@ -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) diff --git a/src/git-repository-async.js b/src/git-repository-async.js deleted file mode 100644 index 66b73ba77..000000000 --- a/src/git-repository-async.js +++ /dev/null @@ -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 -- - // git checkout HEAD -- - // ``` - // - // * `path` The {String} path to checkout. - // - // Returns a {Promise} that resolves or rejects depending on whether the - // method was successful. - checkoutHead (_path) { - return this.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) - } -} diff --git a/src/git-repository-provider.coffee b/src/git-repository-provider.coffee index 463e2bda2..593324d0c 100644 --- a/src/git-repository-provider.coffee +++ b/src/git-repository-provider.coffee @@ -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() diff --git a/src/git-repository.coffee b/src/git-repository.coffee index f6bacb760..85600bba7 100644 --- a/src/git-repository.coffee +++ b/src/git-repository.coffee @@ -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]) diff --git a/src/notification-manager.coffee b/src/notification-manager.coffee index 3d8b1895c..1cb144bdc 100644 --- a/src/notification-manager.coffee +++ b/src/notification-manager.coffee @@ -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)) diff --git a/src/package.coffee b/src/package.coffee index 94e759947..b27e3ce0e 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -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() diff --git a/src/repository-status-handler.coffee b/src/repository-status-handler.coffee index adae7bc4f..2fda9a335 100644 --- a/src/repository-status-handler.coffee +++ b/src/repository-status-handler.coffee @@ -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} diff --git a/src/view-registry.coffee b/src/view-registry.coffee index 5fbfba729..f5f8651df 100644 --- a/src/view-registry.coffee +++ b/src/view-registry.coffee @@ -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/) diff --git a/src/workspace.coffee b/src/workspace.coffee index c2ed18705..8d0ff38fd 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -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 diff --git a/static/linux.less b/static/linux.less index 183353821..7a48e7fcf 100644 --- a/static/linux.less +++ b/static/linux.less @@ -8,6 +8,6 @@ } ::-webkit-scrollbar-thumb { - -webkit-border-radius: 2px; + border-radius: 2px; background: rgba(150, 150, 150, .33); } diff --git a/static/panes.less b/static/panes.less index 418027772..a49e11fd6 100644 --- a/static/panes.less +++ b/static/panes.less @@ -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; diff --git a/static/syntax.less b/static/syntax.less index 657947793..601d09fd7 100644 --- a/static/syntax.less +++ b/static/syntax.less @@ -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; } diff --git a/static/text-editor-light.less b/static/text-editor-light.less index f5429fd7f..bc699e698 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -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; } diff --git a/static/text-editor-shadow.less b/static/text-editor-shadow.less index e481d11b8..f49377ad6 100644 --- a/static/text-editor-shadow.less +++ b/static/text-editor-shadow.less @@ -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; } diff --git a/static/variables/ui-variables.less b/static/variables/ui-variables.less index 0a549954d..dd0561932 100644 --- a/static/variables/ui-variables.less +++ b/static/variables/ui-variables.less @@ -83,3 +83,4 @@ // Other @font-family: 'BlinkMacSystemFont', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif; +@use-custom-controls: true; // false uses native controls diff --git a/static/workspace-view.less b/static/workspace-view.less index 372c89814..6d272a018 100644 --- a/static/workspace-view.less +++ b/static/workspace-view.less @@ -17,6 +17,7 @@ atom-workspace { atom-workspace-axis.horizontal { display: flex; flex: 1; + min-width: 0; } atom-workspace-axis.vertical {