├── .gitignore ├── .npmignore ├── commit.gif ├── .gitmodules ├── lib ├── views │ ├── remote-branch-list-view.coffee │ ├── status-view.coffee │ ├── output-view.coffee │ ├── pull-branch-list-view.coffee │ ├── select-stage-hunk-file-view.coffee │ ├── delete-branch-view.coffee │ ├── repo-list-view.coffee │ ├── projects-list-view.coffee │ ├── tag-list-view.coffee │ ├── cherry-pick-select-branch-view.coffee │ ├── merge-list-view.coffee │ ├── branch-list-view.coffee │ ├── select-unstage-files-view.coffee │ ├── select-stage-files-view.coffee │ ├── tag-create-view.coffee │ ├── tag-view.coffee │ ├── cherry-pick-select-commits-view.coffee │ ├── remove-list-view.coffee │ ├── status-list-view.coffee │ ├── remote-list-view.coffee │ ├── select-stage-hunks-view.coffee │ ├── git-palette-view.coffee │ ├── log-list-view.coffee │ └── select-list-multiple-view.coffee ├── models │ ├── git-status.coffee │ ├── git-add-all-and-commit.coffee │ ├── git-unstage-files.coffee │ ├── git-merge.coffee │ ├── git-add.coffee │ ├── git-stage-hunk.coffee │ ├── git-add-all-commit-and-push.coffee │ ├── git-add-and-commit.coffee │ ├── git-stage-files.coffee │ ├── git-fetch.coffee │ ├── git-push.coffee │ ├── git-fetch-prune.coffee │ ├── git-delete-local-branch.coffee │ ├── git-delete-remote-branch.coffee │ ├── git-checkout-all-files.coffee │ ├── git-diff-all.coffee │ ├── git-pull.coffee │ ├── git-tags.coffee │ ├── git-stash-pop.coffee │ ├── git-stash-drop.coffee │ ├── git-commit-amend.coffee │ ├── git-stash-save.coffee │ ├── git-stash-apply.coffee │ ├── git-cherry-pick.coffee │ ├── git-checkout-current-file.coffee │ ├── git-init.coffee │ ├── git-log.coffee │ ├── git-remove.coffee │ ├── git-run.coffee │ ├── git-branch.coffee │ ├── git-diff.coffee │ ├── git-show.coffee │ ├── fuzzy.coffee │ └── git-commit.coffee ├── notifier.coffee ├── git-plus-commands.coffee ├── git.coffee └── git-plus.coffee ├── spec └── git-spec.coffee ├── LICENSE.md ├── keymaps └── git-plus.cson ├── menus └── git-plus.cson ├── styles └── git-plus.less ├── package.json ├── grammars └── diff.cson ├── README.md └── Changelog.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | doc 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | doc 5 | -------------------------------------------------------------------------------- /commit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darobin/git-plus/master/commit.gif -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test-submodule"] 2 | path = spec/foo 3 | url = git@github.com:akonwi/foo 4 | -------------------------------------------------------------------------------- /lib/views/remote-branch-list-view.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | BranchListView = require '../views/branch-list-view' 3 | 4 | module.exports = 5 | class RemoteBranchListView extends BranchListView 6 | args: ['checkout', '-t'] 7 | -------------------------------------------------------------------------------- /lib/models/git-status.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | StatusListView = require '../views/status-list-view' 3 | 4 | gitStatus = (repo) -> 5 | git.status repo, (data) -> new StatusListView(repo, data) 6 | 7 | module.exports = gitStatus 8 | -------------------------------------------------------------------------------- /lib/models/git-add-all-and-commit.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | GitCommit = require './git-commit' 3 | 4 | gitAddAllAndCommit = (repo) -> 5 | git.add repo, 6 | exit: -> new GitCommit(repo) 7 | 8 | module.exports = gitAddAllAndCommit 9 | -------------------------------------------------------------------------------- /lib/models/git-unstage-files.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | SelectUnstageFiles = require '../views/select-unstage-files-view' 3 | 4 | gitUnstageFiles = (repo) -> 5 | git.stagedFiles repo, (data) -> new SelectUnstageFiles(repo, data) 6 | 7 | module.exports = gitUnstageFiles 8 | -------------------------------------------------------------------------------- /lib/models/git-merge.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | MergeListView = require '../views/merge-list-view' 3 | 4 | module.exports = (repo) -> 5 | git.cmd 6 | args: ['branch'] 7 | cwd: repo.getWorkingDirectory() 8 | stdout: (data) -> 9 | new MergeListView(repo, data) 10 | -------------------------------------------------------------------------------- /lib/models/git-add.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | 3 | gitAdd = (repo, {addAll}={}) -> 4 | if not addAll 5 | file = repo.relativize(atom.workspace.getActiveTextEditor()?.getPath()) 6 | else 7 | file = null 8 | 9 | git.add(repo, file: file) 10 | 11 | module.exports = gitAdd 12 | -------------------------------------------------------------------------------- /lib/models/git-stage-hunk.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | SelectStageHunkFile = require '../views/select-stage-hunk-file-view' 3 | 4 | gitStageHunk = (repo) -> 5 | git.unstagedFiles(repo, null, 6 | (data) -> new SelectStageHunkFile(repo, data) 7 | ) 8 | 9 | module.exports = gitStageHunk 10 | -------------------------------------------------------------------------------- /lib/models/git-add-all-commit-and-push.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | GitCommit = require './git-commit' 3 | 4 | gitAddAllCommitAndPush = (repo) -> 5 | git.add repo, 6 | file: null, 7 | exit: -> 8 | new GitCommit(repo, andPush: true) 9 | 10 | module.exports = gitAddAllCommitAndPush 11 | -------------------------------------------------------------------------------- /lib/models/git-add-and-commit.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | GitCommit = require './git-commit' 3 | 4 | gitAddAndCommit = (repo) -> 5 | git.add repo, 6 | file: repo.relativize(atom.workspace.getActiveTextEditor()?.getPath()) 7 | exit: -> new GitCommit(repo) 8 | 9 | module.exports = gitAddAndCommit 10 | -------------------------------------------------------------------------------- /lib/models/git-stage-files.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | SelectStageFiles = require '../views/select-stage-files-view' 3 | 4 | gitStageFiles = (repo) -> 5 | git.unstagedFiles(repo, 6 | showUntracked: true, 7 | (data) -> new SelectStageFiles(repo, data) 8 | ) 9 | 10 | module.exports = gitStageFiles 11 | -------------------------------------------------------------------------------- /lib/models/git-fetch.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | ListView = require '../views/remote-list-view' 3 | 4 | gitFetch = (repo) -> 5 | git.cmd 6 | args: ['remote'] 7 | cwd: repo.getWorkingDirectory() 8 | stdout: (data) -> new ListView(repo, data.toString(), mode: 'fetch') 9 | 10 | module.exports = gitFetch 11 | -------------------------------------------------------------------------------- /lib/models/git-push.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | RemoteListView = require '../views/remote-list-view' 3 | 4 | gitPush = (repo) -> 5 | git.cmd 6 | args: ['remote'] 7 | cwd: repo.getWorkingDirectory() 8 | stdout: (data) -> new RemoteListView(repo, data, mode: 'push') 9 | 10 | module.exports = gitPush 11 | -------------------------------------------------------------------------------- /lib/models/git-fetch-prune.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | ListView = require '../views/remote-list-view' 3 | 4 | gitFetch = (repo) -> 5 | git.cmd 6 | args: ['remote'] 7 | cwd: repo.getWorkingDirectory() 8 | stdout: (data) -> new ListView(repo, data.toString(), mode: 'fetch-prune') 9 | 10 | module.exports = gitFetch 11 | -------------------------------------------------------------------------------- /lib/models/git-delete-local-branch.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | ListView = require '../views/delete-branch-view' 3 | 4 | gitDeleteLocalBranch = (repo) -> 5 | git.cmd 6 | args: ['branch'] 7 | cwd: repo.getWorkingDirectory() 8 | stdout: (data) -> new ListView(repo, data.toString()) 9 | 10 | module.exports = gitDeleteLocalBranch 11 | -------------------------------------------------------------------------------- /lib/models/git-delete-remote-branch.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | ListView = require '../views/delete-branch-view' 3 | 4 | gitDeleteRemoteBranch = (repo) -> 5 | git.cmd 6 | args: ['branch', '-r'] 7 | cwd: repo.getWorkingDirectory() 8 | stdout: (data) -> 9 | new ListView(repo, data.toString(), isRemote: true) 10 | 11 | module.exports = gitDeleteRemoteBranch 12 | -------------------------------------------------------------------------------- /lib/models/git-checkout-all-files.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | notifier = require '../notifier' 3 | 4 | gitCheckoutAllFiles = (repo) -> 5 | git.cmd 6 | args: ['checkout', '-f'] 7 | cwd: repo.getWorkingDirectory() 8 | stdout: (data) -> 9 | notifier.addSuccess "File changes checked out successfully!" 10 | git.refresh() 11 | 12 | module.exports = gitCheckoutAllFiles 13 | -------------------------------------------------------------------------------- /lib/models/git-diff-all.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | GitDiff = require './git-diff' 3 | 4 | gitStat = (repo) -> 5 | args = ['diff', '--stat'] 6 | args.push 'HEAD' if atom.config.get 'git-plus.includeStagedDiff' 7 | git.cmd 8 | args: args 9 | cwd: repo.getWorkingDirectory() 10 | stdout: (data) -> GitDiff(repo, diffStat: data, file: '.') 11 | 12 | module.exports = gitStat 13 | -------------------------------------------------------------------------------- /lib/models/git-pull.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | RemoteListView = require '../views/remote-list-view' 3 | 4 | gitPull = (repo, {rebase}={}) -> 5 | extraArgs = ['--rebase'] if rebase 6 | 7 | git.cmd 8 | args: ['remote'] 9 | cwd: repo.getWorkingDirectory() 10 | stdout: (data) -> new RemoteListView(repo, data, mode: 'pull', extraArgs: extraArgs) 11 | 12 | module.exports = gitPull 13 | -------------------------------------------------------------------------------- /lib/models/git-tags.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | TagListView = require '../views/tag-list-view' 3 | 4 | gitTags = (repo) -> 5 | @TagListView = null 6 | git.cmd 7 | args: ['tag', '-ln'] 8 | cwd: repo.getWorkingDirectory() 9 | stdout: (data) -> @TagListView = new TagListView(repo, data), 10 | exit: -> new TagListView(repo) if not @TagListView? 11 | 12 | module.exports = gitTags 13 | -------------------------------------------------------------------------------- /lib/notifier.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | title: 'Git-Plus' 3 | addInfo: (message, {dismissable}={}) -> 4 | atom.notifications.addInfo(@title, detail: message, dismissable: dismissable) 5 | addSuccess: (message, {dismissable}={}) -> 6 | atom.notifications.addSuccess(@title, detail: message, dismissable: dismissable) 7 | addError: (message, {dismissable}={}) -> 8 | atom.notifications.addError(@title, detail: message, dismissable: dismissable) 9 | -------------------------------------------------------------------------------- /lib/models/git-stash-pop.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | notifier = require '../notifier' 3 | 4 | gitStashPop = (repo) -> 5 | git.cmd 6 | args: ['stash', 'pop'] 7 | cwd: repo.getWorkingDirectory() 8 | options: { 9 | env: process.env.NODE_ENV 10 | } 11 | stdout: (data) -> 12 | notifier.addSuccess(data) if data.toString().length > 0 13 | stderr: (data) -> 14 | notifier.addError(data) 15 | 16 | module.exports = gitStashPop 17 | -------------------------------------------------------------------------------- /lib/models/git-stash-drop.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | notifier = require '../notifier' 3 | 4 | gitStashDrop = (repo) -> 5 | git.cmd 6 | args: ['stash', 'drop'] 7 | cwd: repo.getWorkingDirectory() 8 | options: { 9 | env: process.env.NODE_ENV 10 | } 11 | stdout: (data) -> 12 | notifier.addSuccess(data) if data.toString().length > 0 13 | stderr: (data) -> 14 | notifier.addError(data) 15 | 16 | module.exports = gitStashDrop 17 | -------------------------------------------------------------------------------- /lib/models/git-commit-amend.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | GitCommit = require './git-commit' 3 | 4 | gitCommitAmend = (repo) -> 5 | git.cmd 6 | args: ['log', '-1', '--format=%B'] 7 | cwd: repo.getWorkingDirectory() 8 | stdout: (amend) -> 9 | git.cmd 10 | args: ['reset', '--soft', 'HEAD^'] 11 | cwd: repo.getWorkingDirectory() 12 | exit: -> new GitCommit(repo, amend: "#{amend?.trim()}\n") 13 | 14 | module.exports = gitCommitAmend 15 | -------------------------------------------------------------------------------- /lib/models/git-stash-save.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | notifier = require '../notifier' 3 | 4 | gitStashSave = (repo) -> 5 | notification = notifier.addInfo('Saving...', dismissable: true) 6 | git.cmd 7 | args: ['stash', 'save'] 8 | cwd: repo.getWorkingDirectory() 9 | options: { 10 | env: process.env.NODE_ENV 11 | } 12 | stdout: (data) -> 13 | notification.dismiss() 14 | notifier.addSuccess(data) 15 | 16 | module.exports = gitStashSave 17 | -------------------------------------------------------------------------------- /lib/models/git-stash-apply.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | notifier = require '../notifier' 3 | 4 | gitStashApply = (repo) -> 5 | git.cmd 6 | args: ['stash', 'apply'] 7 | cwd: repo.getWorkingDirectory() 8 | options: { 9 | env: process.env.NODE_ENV 10 | } 11 | stdout: (data) -> 12 | notifier.addSuccess(data) if data.toString().length > 0 13 | stderr: (data) -> 14 | notifier.addError(data.toString()) 15 | 16 | module.exports = gitStashApply 17 | -------------------------------------------------------------------------------- /lib/models/git-cherry-pick.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | CherryPickSelectBranch = require '../views/cherry-pick-select-branch-view' 3 | 4 | gitCherryPick = (repo) -> 5 | heads = repo.getReferences().heads 6 | currentHead = repo.getShortHead() 7 | 8 | for head, i in heads 9 | heads[i] = head.replace('refs/heads/', '') 10 | 11 | heads = heads.filter (head) -> head isnt currentHead 12 | new CherryPickSelectBranch(repo, heads, currentHead) 13 | 14 | module.exports = gitCherryPick 15 | -------------------------------------------------------------------------------- /lib/views/status-view.coffee: -------------------------------------------------------------------------------- 1 | {$, View} = require 'atom-space-pen-views' 2 | 3 | module.exports = 4 | class StatusView extends View 5 | @content = (params) -> 6 | @div class: 'git-plus', => 7 | @div class: "#{params.type} message", params.message 8 | 9 | initialize: -> 10 | @panel ?= atom.workspace.addBottomPanel(item: this) 11 | setTimeout => 12 | @destroy() 13 | , atom.config.get('git-plus.messageTimeout') * 1000 14 | 15 | destroy: -> 16 | @panel?.destroy() 17 | -------------------------------------------------------------------------------- /lib/models/git-checkout-current-file.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | notifier = require '../notifier' 3 | 4 | gitCheckoutCurrentFile = (repo)-> 5 | currentFile = repo.relativize(atom.workspace.getActiveTextEditor()?.getPath()) 6 | git.cmd 7 | args: ['checkout', '--', currentFile] 8 | cwd: repo.getWorkingDirectory() 9 | stdout: (data) -> # There is no output from this command 10 | notifier.addSuccess 'File changes checked out successfully' 11 | git.refresh() 12 | 13 | module.exports = gitCheckoutCurrentFile 14 | -------------------------------------------------------------------------------- /lib/models/git-init.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | ProjectsListView = require '../views/projects-list-view' 3 | notifier = require '../notifier' 4 | 5 | gitInit = -> 6 | currentFile = atom.workspace.getActiveTextEditor()?.getPath() 7 | if not currentFile and atom.project.getPaths().length > 1 8 | promise = new ProjectsListView().result.then (path) -> init(path) 9 | else 10 | init(atom.project.getPaths()[0]) 11 | 12 | init = (path) -> 13 | git.cmd 14 | args: ['init'] 15 | cwd: path 16 | stdout: (data) -> 17 | notifier.addSuccess data 18 | atom.project.setPaths([path]) 19 | 20 | module.exports = gitInit 21 | -------------------------------------------------------------------------------- /lib/views/output-view.coffee: -------------------------------------------------------------------------------- 1 | {$, ScrollView} = require 'atom-space-pen-views' 2 | 3 | module.exports = 4 | class OutputView extends ScrollView 5 | message: '' 6 | 7 | @content: -> 8 | @div class: 'git-plus info-view', => 9 | @pre class: 'output' 10 | 11 | initialize: -> 12 | super 13 | 14 | addLine: (line) -> 15 | @message += line 16 | 17 | reset: -> 18 | @message = '' 19 | 20 | finish: -> 21 | @panel ?= atom.workspace.addBottomPanel(item: this) 22 | @find(".output").append(@message) 23 | setTimeout => 24 | @destroy() 25 | , atom.config.get('git-plus.messageTimeout') * 1000 26 | 27 | destroy: -> 28 | @panel?.destroy() 29 | -------------------------------------------------------------------------------- /lib/models/git-log.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | LogListView = require '../views/log-list-view' 3 | ViewUriLog = 'atom://git-plus:log' 4 | 5 | amountOfCommitsToShow = -> 6 | atom.config.get('git-plus.amountOfCommitsToShow') 7 | 8 | gitLog = (repo, {onlyCurrentFile}={}) -> 9 | currentFile = repo.relativize(atom.workspace.getActiveTextEditor()?.getPath()) 10 | # opener doesn't get overwritten with a new instance of LogListView 11 | atom.workspace.addOpener (filePath) -> 12 | return new LogListView if filePath is ViewUriLog 13 | 14 | atom.workspace.open(ViewUriLog).done (view) -> 15 | if view instanceof LogListView 16 | view.setRepo repo 17 | if onlyCurrentFile 18 | view.currentFileLog(onlyCurrentFile, currentFile) 19 | else 20 | view.branchLog() 21 | 22 | module.exports = gitLog 23 | -------------------------------------------------------------------------------- /lib/views/pull-branch-list-view.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | OutputView = require './output-view' 3 | BranchListView = require './branch-list-view' 4 | 5 | module.exports = 6 | # Extension of BranchListView 7 | # Takes the name of the remote to pull from 8 | class PullBranchListView extends BranchListView 9 | initialize: (@repo, @data, @remote, @extraArgs) -> 10 | super 11 | 12 | confirmed: ({name}) -> 13 | @pull name.substring(name.indexOf('/') + 1) 14 | @cancel() 15 | 16 | pull: (remoteBranch='') -> 17 | view = new OutputView() 18 | git.cmd 19 | args: ['pull'].concat(@extraArgs, @remote, remoteBranch) 20 | cwd: @repo.getWorkingDirectory() 21 | stdout: (data) -> view.addLine(data.toString()) 22 | stderr: (data) -> view.addLine(data.toString()) 23 | exit: (code) => view.finish() 24 | -------------------------------------------------------------------------------- /spec/git-spec.coffee: -------------------------------------------------------------------------------- 1 | git = require '../lib/git' 2 | Path = require 'flavored-path' 3 | 4 | pathToRepoFile = Path.get "~/.atom/packages/git-plus/lib/git.coffee" 5 | pathToSubmoduleFile = Path.get "~/.atom/packages/git-plus/spec/foo/foo.txt" 6 | 7 | describe "Git-Plus git module", -> 8 | describe "git.getRepo", -> 9 | it "returns a promise", -> 10 | waitsForPromise -> 11 | git.getRepo().then (repo) -> 12 | expect(repo.getWorkingDirectory()).toContain 'git-plus' 13 | 14 | describe "git.dir", -> 15 | it "returns a promise", -> 16 | waitsForPromise -> 17 | git.dir().then (dir) -> 18 | expect(dir).toContain 'git-plus' 19 | 20 | describe "git.getSubmodule", -> 21 | it "returns undefined when there is no submodule", -> 22 | expect(git.getSubmodule(pathToRepoFile)).toBe undefined 23 | 24 | it "returns a submodule when given file is in a submodule of a project repo", -> 25 | expect(git.getSubmodule(pathToSubmoduleFile)).toBeTruthy() 26 | -------------------------------------------------------------------------------- /lib/views/select-stage-hunk-file-view.coffee: -------------------------------------------------------------------------------- 1 | {BufferedProcess} = require 'atom' 2 | {$$, SelectListView} = require 'atom-space-pen-views' 3 | SelectStageHunks = require './select-stage-hunks-view' 4 | git = require '../git' 5 | 6 | module.exports = 7 | class SelectStageHunkFile extends SelectListView 8 | 9 | initialize: (@repo, items) -> 10 | super 11 | @show() 12 | @setItems items 13 | @focusFilterEditor() 14 | 15 | getFilterKey: -> 'path' 16 | 17 | show: -> 18 | @panel ?= atom.workspace.addModalPanel(item: this) 19 | @panel.show() 20 | @storeFocusedElement() 21 | 22 | cancelled: -> @hide() 23 | 24 | hide: -> 25 | @panel?.destroy() 26 | 27 | viewForItem: (item) -> 28 | $$ -> 29 | @li => 30 | @div class: 'pull-right', => 31 | @span class: 'inline-block highlight', item.mode 32 | @span class: 'text-warning', item.path 33 | 34 | confirmed: ({path}) -> 35 | @cancel() 36 | git.diff(@repo, path, 37 | (data) => new SelectStageHunks(@repo, data) 38 | ) 39 | -------------------------------------------------------------------------------- /lib/models/git-remove.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | notifier = require '../notifier' 3 | RemoveListView = require '../views/remove-list-view' 4 | 5 | gitRemove = (repo, {showSelector}={}) -> 6 | currentFile = repo.relativize(atom.workspace.getActiveTextEditor()?.getPath()) 7 | 8 | if currentFile? and not showSelector 9 | if window.confirm 'Are you sure?' 10 | atom.workspace.getActivePaneItem().destroy() 11 | git.cmd 12 | args: ['rm', '-f', '--ignore-unmatch', currentFile] 13 | cwd: repo.getWorkingDirectory() 14 | stdout: (data) -> 15 | notifier.addSuccess("Removed #{prettify data}") 16 | else 17 | git.cmd 18 | args: ['rm', '-r', '-n', '--ignore-unmatch', '-f', '*'] 19 | cwd: repo.getWorkingDirectory() 20 | stdout: (data) -> new RemoveListView(repo, prettify(data)) 21 | 22 | # cut off rm '' around the filenames. 23 | prettify = (data) -> 24 | data = data.match(/rm ('.*')/g) 25 | if data 26 | for file, i in data 27 | data[i] = file.match(/rm '(.*)'/)[1] 28 | else 29 | data 30 | 31 | module.exports = gitRemove 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Akonwi Ngoh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/views/delete-branch-view.coffee: -------------------------------------------------------------------------------- 1 | git = require '../git' 2 | notifier = require '../notifier' 3 | BranchListView = require './branch-list-view' 4 | 5 | module.exports = 6 | # Extension of BranchListView 7 | class DeleteBranchListView extends BranchListView 8 | initialize: (@repo, @data, {@isRemote}={}) -> super 9 | 10 | confirmed: ({name}) -> 11 | if name.startsWith "*" 12 | name = name.slice(1) 13 | 14 | unless @isRemote 15 | @delete name 16 | else 17 | branch = name.substring(name.indexOf('/') + 1) 18 | remote = name.substring(0, name.indexOf('/')) 19 | @delete branch, remote 20 | 21 | @cancel() 22 | 23 | delete: (branch, remote = '') -> 24 | if remote.length is 0 25 | git.cmd 26 | args: ['branch', '-D', branch] 27 | cwd: @repo.getWorkingDirectory() 28 | stdout: (data) -> notifier.addSuccess(data.toString()) 29 | else 30 | git.cmd 31 | args: ['push', remote, '--delete', branch] 32 | cwd: @repo.getWorkingDirectory() 33 | stderr: (data) -> notifier.addSuccess(data.toString()) 34 | -------------------------------------------------------------------------------- /lib/views/repo-list-view.coffee: -------------------------------------------------------------------------------- 1 | {$$, SelectListView} = require 'atom-space-pen-views' 2 | git = require '../git' 3 | 4 | module.exports = 5 | class ListView extends SelectListView 6 | initialize: (@repos) -> 7 | super 8 | @currentPane = atom.workspace.getActivePane() 9 | @result = new Promise (resolve, reject) => 10 | @resolve = resolve 11 | @reject = reject 12 | @setup() 13 | 14 | getFilterKey: -> 'name' 15 | 16 | setup: -> 17 | @repos = @repos.map (r) -> 18 | path = r.getWorkingDirectory() 19 | return { 20 | name: path.substring(path.lastIndexOf('/')+1) 21 | repo: r 22 | } 23 | @setItems @repos 24 | @show() 25 | 26 | show: -> 27 | @filterEditorView.getModel().placeholderText = 'Which repo?' 28 | @panel ?= atom.workspace.addModalPanel(item: this) 29 | @panel.show() 30 | @focusFilterEditor() 31 | @storeFocusedElement() 32 | 33 | hide: -> @panel?.destroy() 34 | 35 | cancelled: -> @hide() 36 | 37 | viewForItem: ({name}) -> 38 | $$ -> @li(name) 39 | 40 | confirmed: ({repo}) -> 41 | @resolve repo 42 | @cancel() 43 | -------------------------------------------------------------------------------- /keymaps/git-plus.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/advanced/keymaps 10 | 11 | '.platform-darwin': 12 | 'cmd-shift-h': 'git-plus:menu' 13 | 'cmd-shift-c': 'git-plus:commit' 14 | 'cmd-shift-a s': 'git-plus:status' 15 | 'cmd-shift-a a': 'git-plus:add-all-and-commit' 16 | 'cmd-shift-a p': 'git-plus:add-all-commit-and-push' 17 | 18 | '.platform-win32, .platform-linux': 19 | 'ctrl-shift-h': 'git-plus:menu' 20 | 'ctrl-shift-x': 'git-plus:commit' 21 | 'ctrl-shift-a s': 'git-plus:status' 22 | 'ctrl-shift-a a': 'git-plus:add-all-and-commit' 23 | 'ctrl-shift-a p': 'git-plus:add-all-commit-and-push' 24 | 25 | '.platform-darwin atom-text-editor': 26 | 'cmd-shift-a': 'git-plus:add' 27 | 'cmd-shift-a c': 'git-plus:add-and-commit' 28 | 29 | '.platform-win32 atom-text-editor, .platform-linux atom-text-editor': 30 | 'ctrl-shift-a': 'git-plus:add' 31 | 'ctrl-shift-a c': 'git-plus:add-and-commit' 32 | -------------------------------------------------------------------------------- /menus/git-plus.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details 2 | 'context-menu': 3 | 'atom-text-editor:not(.mini)': [ 4 | { 5 | 'label': 'Git add file', 6 | 'command': 'git-plus:add' 7 | } 8 | ] 9 | 10 | 'menu': [ 11 | { 12 | 'label': 'Packages' 13 | 'submenu': [ 14 | 'label': 'Git Plus' 15 | 'submenu': [ 16 | { 'label': 'Add', 'command': 'git-plus:add' } 17 | { 'label': 'Add All', 'command': 'git-plus:add-all' } 18 | { 'label': 'Commit', 'command': 'git-plus:commit' } 19 | { 'label': 'Diff', 'command': 'git-plus:diff' } 20 | { 'label': 'Diff All', 'command': 'git-plus:diff-all' } 21 | { 'label': 'Add & Commit', 'command': 'git-plus:add-and-commit'} 22 | { 'label': 'Add All & Commit', 'command': 'git-plus:add-all-and-commit'} 23 | { 'label': 'Add All & Commit & Push', 'command': 'git-plus:add-all-commit-and-push'} 24 | { 'label': 'Push', 'command': 'git-plus:push' } 25 | { 'label': 'Log', 'command': 'git-plus:log' } 26 | { 'label': 'Merge', 'command': 'git-plus:merge'}, 27 | { 'label': 'Pull Using Rebase', 'command': 'git-plus:pull-using-rebase'} 28 | ] 29 | ] 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /lib/views/projects-list-view.coffee: -------------------------------------------------------------------------------- 1 | {$$, SelectListView} = require 'atom-space-pen-views' 2 | git = require '../git' 3 | 4 | module.exports = 5 | class ListView extends SelectListView 6 | initialize: -> 7 | super 8 | @currentPane = atom.workspace.getActivePane() 9 | @result = new Promise (resolve, reject) => 10 | @resolve = resolve 11 | @reject = reject 12 | @setup() 13 | 14 | getFilterKey: -> 'path' 15 | 16 | setup: -> 17 | @setItems atom.project.getPaths().map (p) -> 18 | return { 19 | path: p 20 | relativized: p.substring(p.lastIndexOf('/')+1) 21 | } 22 | @show() 23 | 24 | show: -> 25 | @filterEditorView.getModel().placeholderText = 'Initialize new repo where?' 26 | @panel ?= atom.workspace.addModalPanel(item: this) 27 | @panel.show() 28 | @focusFilterEditor() 29 | @storeFocusedElement() 30 | 31 | hide: -> @panel?.destroy() 32 | 33 | cancelled: -> 34 | @hide() 35 | 36 | viewForItem: ({path, relativized}) -> 37 | $$ -> 38 | @li => 39 | @div class: 'text-highlight', relativized 40 | @div class: 'text-info', path 41 | 42 | confirmed: ({path}) -> 43 | @resolve path 44 | @cancel() 45 | -------------------------------------------------------------------------------- /lib/models/git-run.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | {$, TextEditorView, View} = require 'atom-space-pen-views' 3 | 4 | git = require '../git' 5 | notifier = require '../notifier' 6 | 7 | class InputView extends View 8 | @content: -> 9 | @div => 10 | @subview 'commandEditor', new TextEditorView(mini: true, placeHolderText: 'Git command and arguments') 11 | 12 | initialize: (@repo) -> 13 | @disposables = new CompositeDisposable 14 | @currentPane = atom.workspace.getActivePane() 15 | @panel ?= atom.workspace.addModalPanel(item: this) 16 | @panel.show() 17 | @commandEditor.focus() 18 | 19 | @disposables.add atom.commands.add 'atom-text-editor', 'core:cancel': (e) => 20 | @panel?.destroy() 21 | @currentPane.activate() 22 | @disposables.dispose() 23 | 24 | @disposables.add atom.commands.add 'atom-text-editor', 'core:confirm', (e) => 25 | @disposables.dispose() 26 | @panel?.destroy() 27 | args = @commandEditor.getText().split(' ') 28 | if args[0] is 1 then args.shift() 29 | git.cmd 30 | args: args 31 | cwd: @repo.getWorkingDirectory() 32 | stdout: (data) => 33 | notifier.addSuccess(data.toString()) if data.toString().length > 0 34 | git.refresh() 35 | @currentPane.activate() 36 | 37 | module.exports = (repo) -> new InputView(repo) 38 | -------------------------------------------------------------------------------- /lib/views/tag-list-view.coffee: -------------------------------------------------------------------------------- 1 | {BufferedProcess} = require 'atom' 2 | {$$, SelectListView} = require 'atom-space-pen-views' 3 | 4 | TagView = require './tag-view' 5 | TagCreateView = require './tag-create-view' 6 | 7 | module.exports = 8 | class TagListView extends SelectListView 9 | 10 | initialize: (@repo, @data='') -> 11 | super 12 | @show() 13 | @parseData() 14 | 15 | parseData: -> 16 | if @data.length > 0 17 | @data = @data.split("\n")[...-1] 18 | items = ( 19 | for item in @data.reverse() when item != '' 20 | tmp = item.match /([\w\d-_/.]+)\s(.*)/ 21 | {tag: tmp?[1], annotation: tmp?[2]} 22 | ) 23 | else 24 | items = [] 25 | 26 | items.push {tag: '+ Add Tag', annotation: 'Add a tag referencing the current commit.'} 27 | @setItems items 28 | @focusFilterEditor() 29 | 30 | getFilterKey: -> 'tag' 31 | 32 | show: -> 33 | @panel ?= atom.workspace.addModalPanel(item: this) 34 | @panel.show() 35 | @storeFocusedElement() 36 | 37 | cancelled: -> @hide() 38 | 39 | hide: -> @panel?.destroy() 40 | 41 | viewForItem: ({tag, annotation}) -> 42 | $$ -> 43 | @li => 44 | @div class: 'text-highlight', tag 45 | @div class: 'text-warning', annotation 46 | 47 | confirmed: ({tag}) -> 48 | @cancel() 49 | if tag is '+ Add Tag' 50 | new TagCreateView(@repo) 51 | else 52 | new TagView(@repo, tag) 53 | -------------------------------------------------------------------------------- /lib/views/cherry-pick-select-branch-view.coffee: -------------------------------------------------------------------------------- 1 | {BufferedProcess} = require 'atom' 2 | {$$, SelectListView} = require 'atom-space-pen-views' 3 | 4 | git = require '../git' 5 | notifier = require '../notifier' 6 | CherryPickSelectCommits = require './cherry-pick-select-commits-view' 7 | 8 | module.exports = 9 | class CherryPickSelectBranch extends SelectListView 10 | 11 | initialize: (@repo, items, @currentHead) -> 12 | super 13 | @show() 14 | @setItems items 15 | @focusFilterEditor() 16 | 17 | show: -> 18 | @panel ?= atom.workspace.addModalPanel(item: this) 19 | @panel.show() 20 | 21 | @storeFocusedElement() 22 | 23 | cancelled: -> @hide() 24 | 25 | hide: -> 26 | @panel?.destroy() 27 | 28 | viewForItem: (item) -> 29 | $$ -> 30 | @li item 31 | 32 | confirmed: (item) -> 33 | @cancel() 34 | args = [ 35 | 'log' 36 | '--cherry-pick' 37 | '-z' 38 | '--format=%H%n%an%n%ar%n%s' 39 | "#{@currentHead}...#{item}" 40 | ] 41 | 42 | repo = @repo 43 | git.cmd 44 | args: args 45 | cwd: repo.getWorkingDirectory() 46 | stdout: (data) -> 47 | @save ?= '' 48 | @save += data 49 | exit: (exit) -> 50 | if exit is 0 and @save? 51 | new CherryPickSelectCommits(repo, @save.split('\0')[...-1]) 52 | @save = null 53 | else 54 | notifier.addInfo "No commits available to cherry-pick." 55 | -------------------------------------------------------------------------------- /lib/views/merge-list-view.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs-plus' 2 | {$$, SelectListView} = require 'atom-space-pen-views' 3 | git = require '../git' 4 | notifier = require '../notifier' 5 | 6 | module.exports = 7 | class ListView extends SelectListView 8 | initialize: (@repo, @data) -> 9 | super 10 | @show() 11 | @parseData() 12 | 13 | parseData: -> 14 | items = @data.split("\n") 15 | branches = [] 16 | for item in items 17 | item = item.replace(/\s/g, '') 18 | unless item is '' 19 | branches.push {name: item} 20 | @setItems branches 21 | @focusFilterEditor() 22 | 23 | getFilterKey: -> 'name' 24 | 25 | show: -> 26 | @panel ?= atom.workspace.addModalPanel(item: this) 27 | @panel.show() 28 | @storeFocusedElement() 29 | 30 | cancelled: -> @hide() 31 | 32 | hide: -> 33 | @panel?.destroy() 34 | 35 | viewForItem: ({name}) -> 36 | current = false 37 | if name.startsWith "*" 38 | name = name.slice(1) 39 | current = true 40 | $$ -> 41 | @li name, => 42 | @div class: 'pull-right', => 43 | @span('Current') if current 44 | 45 | confirmed: ({name}) -> 46 | @merge name.match(/\*?(.*)/)[1] 47 | @cancel() 48 | 49 | merge: (branch) -> 50 | git.cmd 51 | args: ['merge', branch] 52 | cwd: @repo.getWorkingDirectory() 53 | stdout: (data) => 54 | notifier.addSuccess data.toString() 55 | atom.workspace.getTextEditors().forEach (editor) -> 56 | fs.exists editor.getPath(), (exist) -> editor.destroy() if not exist 57 | git.refresh() 58 | -------------------------------------------------------------------------------- /styles/git-plus.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .git-plus { 8 | .error { 9 | color: @text-color-warning; 10 | } 11 | 12 | .success { 13 | color: @text-color-success; 14 | } 15 | 16 | .atom-panel.modal.from-bottom { 17 | width: 600px; 18 | } 19 | 20 | .info-view { 21 | overflow: auto; 22 | } 23 | } 24 | 25 | atom-text-editor, 26 | atom-text-editor::shadow { 27 | .meta.diff.range.unified { 28 | color: @text-color-info; 29 | } 30 | 31 | .meta.diff.header { 32 | color: @text-color-info; 33 | } 34 | 35 | .markup.deleted.diff { 36 | color: @text-color-error; 37 | } 38 | 39 | .markup.inserted.diff { 40 | color: @text-color-success; 41 | } 42 | } 43 | 44 | .git-plus-log { 45 | background-color: @base-background-color; 46 | overflow-y: auto; 47 | 48 | #git-plus-commits { 49 | color: @text-color; 50 | cursor: pointer; 51 | 52 | td { 53 | padding: 8px 10px; 54 | border-bottom: 1px solid #000; 55 | vertical-align: top; 56 | } 57 | 58 | .author { 59 | width: 30%; 60 | } 61 | 62 | .date { 63 | width: 25%; 64 | } 65 | 66 | .message { 67 | width: 50%; 68 | } 69 | 70 | .hashShort { 71 | width: 10%; 72 | text-align: right; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-plus", 3 | "main": "./lib/git-plus", 4 | "version": "5.4.7", 5 | "description": "Do git things without the terminal", 6 | "keywords": [ 7 | "git" 8 | ], 9 | "activationCommands": { 10 | "atom-workspace": [ 11 | "git-plus:menu", 12 | "git-plus:add", 13 | "git-plus:add-all", 14 | "git-plus:add-all-and-commit", 15 | "git-plus:add-and-commit", 16 | "git-plus:diff-all", 17 | "git-plus:diff", 18 | "git-plus:log", 19 | "git-plus:log-current-file", 20 | "git-plus:status", 21 | "git-plus:push", 22 | "git-plus:pull", 23 | "git-plus:pull-using-rebase", 24 | "git-plus:remove", 25 | "git-plus:remove-current-file", 26 | "git-plus:checkout", 27 | "git-plus:checkout-remote", 28 | "git-plus:checkout-current-file", 29 | "git-plus:checkout-all-files", 30 | "git-plus:cherry-pick", 31 | "git-plus:commit", 32 | "git-plus:commit-amend", 33 | "git-plus:fetch", 34 | "git-plus:new-branch", 35 | "git-plus:reset-head", 36 | "git-plus:show", 37 | "git-plus:stage-files", 38 | "git-plus:stage-hunk", 39 | "git-plus:stash-save", 40 | "git-plus:stash-pop", 41 | "git-plus:stash-keep", 42 | "git-plus:stash-drop", 43 | "git-plus:tags", 44 | "git-plus:unstage-files", 45 | "git-plus:init", 46 | "git-plus:run", 47 | "git-plus:merge" 48 | ] 49 | }, 50 | "repository": "https://github.com/akonwi/git-plus", 51 | "license": "MIT", 52 | "engines": { 53 | "atom": ">0.50.0" 54 | }, 55 | "dependencies": { 56 | "fs-plus": "^2.2.0", 57 | "fuzzaldrin": "^1.2.0", 58 | "underscore-plus": "1.x", 59 | "atom-space-pen-views": "^2.0.3", 60 | "flavored-path": "0.0.8" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/views/branch-list-view.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs-plus' 2 | {$$, SelectListView} = require 'atom-space-pen-views' 3 | git = require '../git' 4 | notifier = require '../notifier' 5 | 6 | module.exports = 7 | class ListView extends SelectListView 8 | args: ['checkout'] 9 | 10 | initialize: (@repo, @data) -> 11 | super 12 | @show() 13 | @parseData() 14 | @currentPane = atom.workspace.getActivePane() 15 | 16 | parseData: -> 17 | items = @data.split("\n") 18 | branches = [] 19 | for item in items 20 | item = item.replace(/\s/g, '') 21 | unless item is '' 22 | branches.push {name: item} 23 | @setItems branches 24 | @focusFilterEditor() 25 | 26 | getFilterKey: -> 'name' 27 | 28 | show: -> 29 | @panel ?= atom.workspace.addModalPanel(item: this) 30 | @panel.show() 31 | 32 | @storeFocusedElement() 33 | 34 | cancelled: -> @hide() 35 | 36 | hide: -> 37 | @panel?.destroy() 38 | 39 | viewForItem: ({name}) -> 40 | current = false 41 | if name.startsWith "*" 42 | name = name.slice(1) 43 | current = true 44 | $$ -> 45 | @li name, => 46 | @div class: 'pull-right', => 47 | @span('Current') if current 48 | 49 | confirmed: ({name}) -> 50 | @checkout name.match(/\*?(.*)/)[1] 51 | @cancel() 52 | 53 | checkout: (branch) -> 54 | git.cmd 55 | cwd: @repo.getWorkingDirectory() 56 | args: @args.concat(branch) 57 | # using `stderr` for success here 58 | stderr: (data) => 59 | notifier.addSuccess data.toString() 60 | atom.workspace.observeTextEditors (editor) => 61 | if filepath = editor.getPath()?.toString() 62 | fs.exists filepath, (exists) => 63 | editor.destroy() if not exists 64 | git.refresh() 65 | @currentPane.activate() 66 | -------------------------------------------------------------------------------- /lib/views/select-unstage-files-view.coffee: -------------------------------------------------------------------------------- 1 | {$, $$} = require 'atom-space-pen-views' 2 | 3 | git = require '../git' 4 | OutputView = require './output-view' 5 | notifier = require '../notifier' 6 | SelectListMultipleView = require './select-list-multiple-view' 7 | 8 | module.exports = 9 | class SelectStageFilesView extends SelectListMultipleView 10 | 11 | initialize: (@repo, items) -> 12 | super 13 | @show() 14 | @setItems items 15 | @focusFilterEditor() 16 | 17 | getFilterKey: -> 18 | 'path' 19 | 20 | addButtons: -> 21 | viewButton = $$ -> 22 | @div class: 'buttons', => 23 | @span class: 'pull-left', => 24 | @button class: 'btn btn-error inline-block-tight btn-cancel-button', 'Cancel' 25 | @span class: 'pull-right', => 26 | @button class: 'btn btn-success inline-block-tight btn-unstage-button', 'Unstage' 27 | viewButton.appendTo(this) 28 | 29 | @on 'click', 'button', ({target}) => 30 | @complete() if $(target).hasClass('btn-unstage-button') 31 | @cancel() if $(target).hasClass('btn-cancel-button') 32 | 33 | show: -> 34 | @panel ?= atom.workspace.addModalPanel(item: this) 35 | @panel.show() 36 | 37 | @storeFocusedElement() 38 | 39 | cancelled: -> @hide() 40 | 41 | hide: -> 42 | @panel?.destroy() 43 | 44 | viewForItem: (item, matchedStr) -> 45 | $$ -> 46 | @li => 47 | @div class: 'pull-right', => 48 | @span class: 'inline-block highlight', item.mode 49 | if matchedStr? then @raw(matchedStr) else @span item.path 50 | 51 | completed: (items) -> 52 | files = (item.path for item in items) 53 | @cancel() 54 | 55 | git.cmd 56 | args: ['reset', 'HEAD', '--'].concat(files) 57 | cwd: @repo.getWorkingDirectory() 58 | stdout: (data) => 59 | notifier.addSuccess(data) 60 | -------------------------------------------------------------------------------- /lib/views/select-stage-files-view.coffee: -------------------------------------------------------------------------------- 1 | {$, $$} = require 'atom-space-pen-views' 2 | 3 | git = require '../git' 4 | OutputView = require './output-view' 5 | notifier = require '../notifier' 6 | SelectListMultipleView = require './select-list-multiple-view' 7 | 8 | module.exports = 9 | class SelectStageFilesView extends SelectListMultipleView 10 | 11 | initialize: (@repo, items) -> 12 | super 13 | @show() 14 | @setItems items 15 | @focusFilterEditor() 16 | 17 | getFilterKey: -> 'path' 18 | 19 | addButtons: -> 20 | viewButton = $$ -> 21 | @div class: 'buttons', => 22 | @span class: 'pull-left', => 23 | @button class: 'btn btn-error inline-block-tight btn-cancel-button', 'Cancel' 24 | @span class: 'pull-right', => 25 | @button class: 'btn btn-success inline-block-tight btn-stage-button', 'Stage' 26 | viewButton.appendTo(this) 27 | 28 | @on 'click', 'button', ({target}) => 29 | @complete() if $(target).hasClass('btn-stage-button') 30 | @cancel() if $(target).hasClass('btn-cancel-button') 31 | 32 | show: -> 33 | @panel ?= atom.workspace.addModalPanel(item: this) 34 | @panel.show() 35 | @storeFocusedElement() 36 | 37 | cancelled: -> @hide() 38 | 39 | hide: -> 40 | @panel?.destroy() 41 | 42 | viewForItem: (item, matchedStr) -> 43 | $$ -> 44 | @li => 45 | @div class: 'pull-right', => 46 | @span class: 'inline-block highlight', item.mode 47 | if matchedStr? then @raw(matchedStr) else @span item.path 48 | 49 | completed: (items) -> 50 | files = (item.path for item in items) 51 | @cancel() 52 | git.cmd 53 | args: ['add', '-f'].concat(files) 54 | cwd: @repo.getWorkingDirectory() 55 | stdout: (data) => 56 | if data is '' 57 | notifier.addSuccess 'File(s) staged successfully' 58 | else 59 | notifier.addSuccess data 60 | -------------------------------------------------------------------------------- /grammars/diff.cson: -------------------------------------------------------------------------------- 1 | 'fileTypes': [ 2 | 'diff' 3 | ] 4 | 'name': 'Diff' 5 | 'patterns': [ 6 | { 7 | 'captures': 8 | '4': 9 | 'name': 'punctuation.definition.from-file.diff' 10 | '6': 11 | 'name': 'punctuation.definition.from-file.diff' 12 | '7': 13 | 'name': 'punctuation.definition.from-file.diff' 14 | 'match': '(^(((-{3}) .+)|((\\*{3}) .+))$\\n?|^(={4}) .+(?= - ))' 15 | 'name': 'meta.diff.header.from-file' 16 | } 17 | { 18 | 'captures': 19 | '2': 20 | 'name': 'punctuation.definition.to-file.diff' 21 | '3': 22 | 'name': 'punctuation.definition.to-file.diff' 23 | '4': 24 | 'name': 'punctuation.definition.to-file.diff' 25 | 'match': '(^(\\+{3}) .+$\\n?| (-) .* (={4})$\\n?)' 26 | 'name': 'meta.diff.header.to-file' 27 | } 28 | { 29 | 'captures': 30 | '1': 31 | 'name': 'punctuation.definition.range.diff' 32 | '2': 33 | 'name': 'meta.toc-list.line-number.diff' 34 | '3': 35 | 'name': 'punctuation.definition.range.diff' 36 | 'match': '^(@@)\\s*(.+?)\\s*(@@)($\\n?)?' 37 | 'name': 'meta.diff.range.unified' 38 | } 39 | { 40 | 'captures': 41 | '3': 42 | 'name': 'punctuation.definition.inserted.diff' 43 | '6': 44 | 'name': 'punctuation.definition.inserted.diff' 45 | 'match': '^(((>)( .*)?)|((\\+).*))$\\n?' 46 | 'name': 'markup.inserted.diff' 47 | } 48 | { 49 | 'captures': 50 | '3': 51 | 'name': 'punctuation.definition.inserted.diff' 52 | '6': 53 | 'name': 'punctuation.definition.inserted.diff' 54 | 'match': '^(((<)( .*)?)|((-).*))$\\n?' 55 | 'name': 'markup.deleted.diff' 56 | } 57 | { 58 | 'match': '\\[\\-.*?\\-\\]' 59 | 'name': 'markup.deleted.diff' 60 | } 61 | { 62 | 'match': '\\{\\+.*?\\+\\}' 63 | 'name': 'markup.inserted.diff' 64 | } 65 | 66 | ] 67 | 'scopeName': 'source.diff' 68 | -------------------------------------------------------------------------------- /lib/views/tag-create-view.coffee: -------------------------------------------------------------------------------- 1 | Os = require 'os' 2 | Path = require 'path' 3 | fs = require 'fs-plus' 4 | 5 | {BufferedProcess, CompositeDisposable} = require 'atom' 6 | {$, TextEditorView, View} = require 'atom-space-pen-views' 7 | notifier = require '../notifier' 8 | git = require '../git' 9 | 10 | module.exports= 11 | class TagCreateView extends View 12 | @content: -> 13 | @div => 14 | @div class: 'block', => 15 | @subview 'tagName', new TextEditorView(mini: true, placeholderText: 'Tag') 16 | @div class: 'block', => 17 | @subview 'tagMessage', new TextEditorView(mini: true, placeholderText: 'Annotation message') 18 | @div class: 'block', => 19 | @span class: 'pull-left', => 20 | @button class: 'btn btn-success inline-block-tight gp-confirm-button', click: 'createTag', 'Create Tag' 21 | @span class: 'pull-right', => 22 | @button class: 'btn btn-error inline-block-tight gp-cancel-button', click: 'destroy', 'Cancel' 23 | 24 | initialize: (@repo) -> 25 | @disposables = new CompositeDisposable 26 | @currentPane = atom.workspace.getActivePane() 27 | @panel ?= atom.workspace.addModalPanel(item: this) 28 | @panel.show() 29 | @tagName.focus() 30 | @disposables.add atom.commands.add 'atom-text-editor', 'core:cancel': => @destroy() 31 | @disposables.add atom.commands.add 'atom-text-editor', 'core:confirm': => @createTag() 32 | 33 | createTag: -> 34 | tag = name: @tagName.getModel().getText(), message: @tagMessage.getModel().getText() 35 | git.cmd 36 | args: ['tag', '-a', tag.name, '-m', tag.message] 37 | cwd: @repo.getWorkingDirectory() 38 | stderr: (data) -> 39 | notifier.addError(data.toString()) 40 | exit: (code) -> 41 | notifier.addSuccess("Tag '#{tag.name}' has been created successfully!") if code is 0 42 | @destroy() 43 | 44 | destroy: -> 45 | @panel?.destroy() 46 | @disposables.dispose() 47 | @currentPane.activate() 48 | -------------------------------------------------------------------------------- /lib/models/git-branch.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | {$, TextEditorView, View} = require 'atom-space-pen-views' 3 | 4 | git = require '../git' 5 | notifier = require '../notifier' 6 | BranchListView = require '../views/branch-list-view' 7 | RemoteBranchListView = require '../views/remote-branch-list-view' 8 | 9 | class InputView extends View 10 | @content: -> 11 | @div => 12 | @subview 'branchEditor', new TextEditorView(mini: true, placeholderText: 'New branch name') 13 | 14 | initialize: (@repo) -> 15 | @disposables = new CompositeDisposable 16 | @currentPane = atom.workspace.getActivePane() 17 | panel = atom.workspace.addModalPanel(item: this) 18 | panel.show() 19 | 20 | destroy = => 21 | panel.destroy() 22 | @disposables.dispose() 23 | @currentPane.activate() 24 | 25 | @branchEditor.focus() 26 | @disposables.add atom.commands.add 'atom-text-editor', 'core:cancel': (event) -> destroy() 27 | @disposables.add atom.commands.add 'atom-text-editor', 'core:confirm': (event) => 28 | editor = @branchEditor.getModel() 29 | name = editor.getText() 30 | if name.length > 0 31 | @createBranch name 32 | destroy() 33 | 34 | createBranch: (name) -> 35 | git.cmd 36 | args: ['checkout', '-b', name] 37 | cwd: @repo.getWorkingDirectory() 38 | # using `stderr` for success 39 | stderr: (data) => 40 | notifier.addSuccess data.toString() 41 | git.refresh() 42 | @currentPane.activate() 43 | 44 | module.exports.newBranch = (repo) -> 45 | new InputView(repo) 46 | 47 | module.exports.gitBranches = (repo) -> 48 | git.cmd 49 | args: ['branch'] 50 | cwd: repo.getWorkingDirectory() 51 | stdout: (data) -> 52 | new BranchListView(repo, data) 53 | 54 | module.exports.gitRemoteBranches = (repo) -> 55 | git.cmd 56 | args: ['branch', '-r'] 57 | cwd: repo.getWorkingDirectory() 58 | stdout: (data) -> 59 | new RemoteBranchListView(repo, data) 60 | -------------------------------------------------------------------------------- /lib/views/tag-view.coffee: -------------------------------------------------------------------------------- 1 | {$$, SelectListView} = require 'atom-space-pen-views' 2 | 3 | git = require '../git' 4 | GitShow = require '../models/git-show' 5 | notifier = require '../notifier' 6 | RemoteListView = require '../views/remote-list-view' 7 | 8 | module.exports = 9 | class TagView extends SelectListView 10 | initialize: (@repo, @tag) -> 11 | super 12 | @show() 13 | @parseData() 14 | 15 | parseData: -> 16 | items = [] 17 | items.push {tag: @tag, cmd: 'Show', description: 'git show'} 18 | items.push {tag: @tag, cmd: 'Push', description: 'git push [remote]'} 19 | items.push {tag: @tag, cmd: 'Checkout', description: 'git checkout'} 20 | items.push {tag: @tag, cmd: 'Verify', description: 'git tag --verify'} 21 | items.push {tag: @tag, cmd: 'Delete', description: 'git tag --delete'} 22 | 23 | @setItems items 24 | @focusFilterEditor() 25 | 26 | show: -> 27 | @panel ?= atom.workspace.addModalPanel(item: this) 28 | @panel.show() 29 | @storeFocusedElement() 30 | 31 | cancelled: -> @hide() 32 | 33 | hide: -> @panel?.destroy() 34 | 35 | viewForItem: ({tag, cmd, description}) -> 36 | $$ -> 37 | @li => 38 | @div class: 'text-highlight', cmd 39 | @div class: 'text-warning', "#{description} #{tag}" 40 | 41 | getFilterKey: -> 'cmd' 42 | 43 | confirmed: ({tag, cmd}) -> 44 | @cancel() 45 | switch cmd 46 | when 'Show' 47 | GitShow(@repo, tag) 48 | return 49 | when 'Push' 50 | git.cmd 51 | args: ['remote'] 52 | cwd: @repo.getWorkingDirectory() 53 | stdout: (data) => new RemoteListView(@repo, data, mode: 'push', tag: @tag) 54 | return 55 | when 'Checkout' 56 | args = ['checkout', tag] 57 | when 'Verify' 58 | args = ['tag', '--verify', tag] 59 | when 'Delete' 60 | args = ['tag', '--delete', tag] 61 | 62 | git.cmd 63 | args: args 64 | cwd: @repo.getWorkingDirectory() 65 | stdout: (data) -> notifier.addSuccess(data.toString()) 66 | -------------------------------------------------------------------------------- /lib/views/cherry-pick-select-commits-view.coffee: -------------------------------------------------------------------------------- 1 | {$, $$} = require 'atom-space-pen-views' 2 | 3 | git = require '../git' 4 | OutputView = require './output-view' 5 | notifier = require '../notifier' 6 | SelectListMultipleView = require './select-list-multiple-view' 7 | 8 | module.exports = 9 | class CherryPickSelectCommits extends SelectListMultipleView 10 | 11 | initialize: (@repo, data) -> 12 | super 13 | @show() 14 | @setItems( 15 | for item in data 16 | item = item.split('\n') 17 | {hash: item[0], author: item[1], time: item[2], subject: item[3]} 18 | ) 19 | @focusFilterEditor() 20 | 21 | getFilterKey: -> 22 | 'hash' 23 | 24 | addButtons: -> 25 | viewButton = $$ -> 26 | @div class: 'buttons', => 27 | @span class: 'pull-left', => 28 | @button class: 'btn btn-error inline-block-tight btn-cancel-button', 'Cancel' 29 | @span class: 'pull-right', => 30 | @button class: 'btn btn-success inline-block-tight btn-pick-button', 'Cherry-Pick!' 31 | viewButton.appendTo(this) 32 | 33 | @on 'click', 'button', ({target}) => 34 | @complete() if $(target).hasClass('btn-pick-button') 35 | @cancel() if $(target).hasClass('btn-cancel-button') 36 | 37 | show: -> 38 | @panel ?= atom.workspace.addModalPanel(item: this) 39 | @panel.show() 40 | 41 | @storeFocusedElement() 42 | 43 | cancelled: -> 44 | @hide() 45 | 46 | hide: -> 47 | @panel?.destroy() 48 | 49 | viewForItem: (item, matchedStr) -> 50 | $$ -> 51 | @li => 52 | @div class: 'text-highlight inline-block pull-right', style: 'font-family: monospace', => 53 | if matchedStr? then @raw(matchedStr) else @span item.hash 54 | @div class: 'text-info', "#{item.author}, #{item.time}" 55 | @div class: 'text-warning', item.subject 56 | 57 | completed: (items) -> 58 | @cancel() 59 | commits = (item.hash for item in items) 60 | git.cmd 61 | args: ['cherry-pick'].concat(commits) 62 | cwd: @repo.getWorkingDirectory() 63 | stdout: (data) => 64 | notifier.addSuccess data 65 | -------------------------------------------------------------------------------- /lib/views/remove-list-view.coffee: -------------------------------------------------------------------------------- 1 | {$, $$, EditorView} = require 'atom-space-pen-views' 2 | 3 | git = require '../git' 4 | OutputView = require './output-view' 5 | notifier = require '../notifier' 6 | SelectListMultipleView = require './select-list-multiple-view' 7 | 8 | module.exports = 9 | class SelectStageFilesView extends SelectListMultipleView 10 | 11 | initialize: (@repo, items) -> 12 | super 13 | @show() 14 | @setItems items 15 | @focusFilterEditor() 16 | 17 | addButtons: -> 18 | viewButton = $$ -> 19 | @div class: 'buttons', => 20 | @span class: 'pull-left', => 21 | @button class: 'btn btn-error inline-block-tight btn-cancel-button', 'Cancel' 22 | @span class: 'pull-right', => 23 | @button class: 'btn btn-success inline-block-tight btn-remove-button', 'Remove' 24 | viewButton.appendTo(this) 25 | 26 | @on 'click', 'button', ({target}) => 27 | if $(target).hasClass('btn-remove-button') 28 | @complete() if window.confirm 'Are you sure?' 29 | @cancel() if $(target).hasClass('btn-cancel-button') 30 | 31 | show: -> 32 | @panel ?= atom.workspace.addModalPanel(item: this) 33 | @panel.show() 34 | @storeFocusedElement() 35 | 36 | cancelled: -> 37 | @hide() 38 | 39 | hide: -> 40 | @panel?.destroy() 41 | 42 | viewForItem: (item, matchedStr) -> 43 | $$ -> 44 | @li => 45 | if matchedStr? then @raw(matchedStr) else @span item 46 | 47 | completed: (items) -> 48 | files = (item for item in items when item isnt '') 49 | @cancel() 50 | currentFile = @repo.relativize atom.workspace.getActiveTextEditor()?.getPath() 51 | 52 | editor = atom.workspace.getActiveTextEditor() 53 | atom.views.getView(editor).remove() if currentFile in files 54 | git.cmd 55 | args: ['rm', '-f'].concat(files) 56 | cwd: @repo.getWorkingDirectory() 57 | stdout: (data) -> 58 | notifier.addSuccess "Removed #{prettify data}" 59 | 60 | # cut off rm '' around the filenames. 61 | prettify = (data) -> 62 | data = data.match(/rm ('.*')/g) 63 | if data?.length >= 1 64 | for file, i in data 65 | data[i] = ' ' + file.match(/rm '(.*)'/)[1] 66 | -------------------------------------------------------------------------------- /lib/views/status-list-view.coffee: -------------------------------------------------------------------------------- 1 | {$$, SelectListView} = require 'atom-space-pen-views' 2 | fs = require 'fs' 3 | Path = require 'path' 4 | git = require '../git' 5 | GitDiff = require '../models/git-diff' 6 | notifier = require '../notifier' 7 | 8 | module.exports = 9 | class StatusListView extends SelectListView 10 | initialize: (@repo, @data, {@onlyCurrentFile}={}) -> 11 | super 12 | @show() 13 | @branch = @data[0] 14 | @setItems @parseData @data[...-1] 15 | @focusFilterEditor() 16 | 17 | parseData: (files) -> 18 | for line in files when /^([ MADRCU?!]{2})\s{1}(.*)/.test line 19 | line = line.match /^([ MADRCU?!]{2})\s{1}(.*)/ 20 | {type: line[1], path: line[2]} 21 | 22 | getFilterKey: -> 'path' 23 | 24 | show: -> 25 | @panel ?= atom.workspace.addModalPanel(item: this) 26 | @panel.show() 27 | @storeFocusedElement() 28 | 29 | cancelled: -> @hide() 30 | 31 | hide: -> @panel?.destroy() 32 | 33 | viewForItem: ({type, path}) -> 34 | getIcon = (s) -> 35 | return 'status-added icon icon-diff-added' if s[0] is 'A' 36 | return 'status-removed icon icon-diff-removed' if s[0] is 'D' 37 | return 'status-renamed icon icon-diff-renamed' if s[0] is 'R' 38 | return 'status-modified icon icon-diff-modified' if s[0] is 'M' or s[1] is 'M' 39 | return '' 40 | 41 | $$ -> 42 | @li => 43 | @div 44 | class: 'pull-right highlight' 45 | style: 'white-space: pre-wrap; font-family: monospace' 46 | type 47 | @span class: getIcon(type) 48 | @span path 49 | 50 | confirmed: ({type, path}) -> 51 | @cancel() 52 | if type is '??' 53 | git.add @repo, file: path 54 | else 55 | openFile = confirm("Open #{path}?") 56 | fullPath = Path.join(@repo.getWorkingDirectory(), path) 57 | 58 | fs.stat fullPath, (err, stat) => 59 | if err 60 | notifier.addError(err.message) 61 | else 62 | isDirectory = stat?.isDirectory() 63 | if openFile 64 | if isDirectory 65 | atom.open(pathsToOpen: fullPath, newWindow: true) 66 | else 67 | atom.workspace.open(fullPath) 68 | else 69 | GitDiff(@repo, file: path) 70 | -------------------------------------------------------------------------------- /lib/models/git-diff.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | Os = require 'os' 3 | Path = require 'path' 4 | fs = require 'fs-plus' 5 | 6 | git = require '../git' 7 | notifier = require '../notifier' 8 | 9 | disposables = new CompositeDisposable 10 | diffFilePath = null 11 | 12 | gitDiff = (repo, {diffStat, file}={}) -> 13 | diffFilePath = Path.join(repo.getPath(), "atom_git_plus.diff") 14 | file ?= repo.relativize(atom.workspace.getActiveTextEditor()?.getPath()) 15 | if not file 16 | return notifier.addError "No open file. Select 'Diff All'." 17 | diffStat ?= '' 18 | args = ['diff', '--color=never'] 19 | args.push 'HEAD' if atom.config.get 'git-plus.includeStagedDiff' 20 | args.push '--word-diff' if atom.config.get 'git-plus.wordDiff' 21 | args.push file if diffStat is '' 22 | git.cmd 23 | args: args 24 | cwd: repo.getWorkingDirectory() 25 | stdout: (data) -> diffStat += data 26 | exit: (code) -> prepFile diffStat if code is 0 27 | 28 | prepFile = (text) -> 29 | if text?.length > 0 30 | fs.writeFileSync diffFilePath, text, flag: 'w+' 31 | showFile() 32 | else 33 | notifier.addInfo 'Nothing to show.' 34 | 35 | showFile = -> 36 | atom.workspace 37 | .open(diffFilePath, searchAllPanes: true) 38 | .done (textEditor) -> 39 | if atom.config.get('git-plus.openInPane') 40 | splitPane(atom.config.get('git-plus.splitPane'), textEditor) 41 | else 42 | disposables.add textEditor.onDidDestroy => 43 | fs.unlink diffFilePath 44 | 45 | splitPane = (splitDir, oldEditor) -> 46 | pane = atom.workspace.paneForURI(diffFilePath) 47 | options = { copyActiveItem: true } 48 | hookEvents = (textEditor) -> 49 | oldEditor.destroy() 50 | disposables.add textEditor.onDidDestroy => 51 | fs.unlink diffFilePath 52 | 53 | directions = 54 | left: => 55 | pane = pane.splitLeft options 56 | hookEvents(pane.getActiveEditor()) 57 | right: => 58 | pane = pane.splitRight options 59 | hookEvents(pane.getActiveEditor()) 60 | up: => 61 | pane = pane.splitUp options 62 | hookEvents(pane.getActiveEditor()) 63 | down: => 64 | pane = pane.splitDown options 65 | hookEvents(pane.getActiveEditor()) 66 | directions[splitDir]() 67 | oldEditor.destroy() 68 | 69 | module.exports = gitDiff 70 | -------------------------------------------------------------------------------- /lib/views/remote-list-view.coffee: -------------------------------------------------------------------------------- 1 | {BufferedProcess} = require 'atom' 2 | {$$, SelectListView} = require 'atom-space-pen-views' 3 | 4 | git = require '../git' 5 | OutputView = require './output-view' 6 | PullBranchListView = require './pull-branch-list-view' 7 | 8 | module.exports = 9 | class ListView extends SelectListView 10 | initialize: (@repo, @data, {@mode, @tag, @extraArgs}) -> 11 | super 12 | @tag ?= '' 13 | @extraArgs ?= [] 14 | @show() 15 | @parseData() 16 | 17 | parseData: -> 18 | items = @data.split("\n") 19 | remotes = [] 20 | for item in items 21 | remotes.push {name: item} unless item is '' 22 | if remotes.length is 1 23 | @confirmed remotes[0] 24 | else 25 | @setItems remotes 26 | @focusFilterEditor() 27 | 28 | getFilterKey: -> 'name' 29 | 30 | show: -> 31 | @panel ?= atom.workspace.addModalPanel(item: this) 32 | @panel.show() 33 | 34 | @storeFocusedElement() 35 | 36 | cancelled: -> @hide() 37 | 38 | hide: -> 39 | @panel?.destroy() 40 | 41 | viewForItem: ({name}) -> 42 | $$ -> 43 | @li name 44 | 45 | confirmed: ({name}) -> 46 | if @mode is 'pull' 47 | git.cmd 48 | args: ['branch', '-r'], 49 | cwd: @repo.getWorkingDirectory() 50 | stdout: (data) => new PullBranchListView(@repo, data, name, @extraArgs) 51 | else if @mode is 'fetch-prune' 52 | @mode = 'fetch' 53 | @execute name, '--prune' 54 | else 55 | @execute name 56 | @cancel() 57 | 58 | execute: (remote, extraArgs='') -> 59 | view = new OutputView() 60 | args = [@mode] 61 | if extraArgs.length > 0 62 | args.push extraArgs 63 | args = args.concat([remote, @tag]) 64 | git.cmd 65 | args: args 66 | cwd: @repo.getWorkingDirectory() 67 | stdout: (data) -> view.addLine(data.toString()) 68 | stderr: (data) -> view.addLine(data.toString()) 69 | exit: (code) => 70 | if code is 128 71 | view.reset() 72 | git.cmd 73 | args: [@mode, '-u', remote, 'HEAD'] 74 | cwd: @repo.getWorkingDirectory() 75 | stdout: (data) -> view.addLine(data.toString()) 76 | stderr: (data) -> view.addLine(data.toString()) 77 | exit: (code) -> view.finish() 78 | else 79 | view.finish() 80 | -------------------------------------------------------------------------------- /lib/models/git-show.coffee: -------------------------------------------------------------------------------- 1 | Os = require 'os' 2 | Path = require 'path' 3 | fs = require 'fs-plus' 4 | 5 | {CompositeDisposable} = require 'atom' 6 | {$, TextEditorView, View} = require 'atom-space-pen-views' 7 | 8 | git = require '../git' 9 | 10 | showCommitFilePath = (objectHash) -> 11 | Path.join Os.tmpDir(), "#{objectHash}.diff" 12 | 13 | showObject = (repo, objectHash, file) -> 14 | args = ['show'] 15 | args.push '--format=full' 16 | args.push '--word-diff' if atom.config.get 'git-plus.wordDiff' 17 | args.push objectHash 18 | if file? 19 | args.push '--' 20 | args.push file 21 | 22 | git.cmd 23 | args: args 24 | cwd: repo.getWorkingDirectory() 25 | stdout: (data) -> prepFile(data, objectHash) if data.length > 0 26 | 27 | prepFile = (text, objectHash) -> 28 | fs.writeFileSync showCommitFilePath(objectHash), text, flag: 'w+' 29 | showFile(objectHash) 30 | 31 | showFile = (objectHash) -> 32 | disposables = new CompositeDisposable 33 | split = if atom.config.get('git-plus.openInPane') then atom.config.get('git-plus.splitPane') 34 | atom.workspace 35 | .open(showCommitFilePath(objectHash), split: split, activatePane: true) 36 | .done (textBuffer) => 37 | if textBuffer? 38 | disposables.add textBuffer.onDidDestroy => 39 | disposables.dispose() 40 | try fs.unlinkSync showCommitFilePath(objectHash) 41 | 42 | class InputView extends View 43 | @content: -> 44 | @div => 45 | @subview 'objectHash', new TextEditorView(mini: true, placeholderText: 'Commit hash to show') 46 | 47 | initialize: (@repo) -> 48 | @disposables = new CompositeDisposable 49 | @currentPane = atom.workspace.getActivePane() 50 | @panel ?= atom.workspace.addModalPanel(item: this) 51 | @panel.show() 52 | @objectHash.focus() 53 | @disposables.add atom.commands.add 'atom-text-editor', 'core:cancel': => @destroy() 54 | @disposables.add atom.commands.add 'atom-text-editor', 'core:confirm': => 55 | text = @objectHash.getModel().getText().split(' ') 56 | name = if text.length is 2 then text[1] else text[0] 57 | showObject(@repo, text) 58 | @destroy() 59 | 60 | destroy: -> 61 | @disposables?.dispose() 62 | @panel?.destroy() 63 | 64 | module.exports = (repo, objectHash, file) -> 65 | if not objectHash? 66 | new InputView(repo) 67 | else 68 | showObject(repo, objectHash, file) 69 | -------------------------------------------------------------------------------- /lib/views/select-stage-hunks-view.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs-plus' 2 | {$, $$} = require 'atom-space-pen-views' 3 | 4 | git = require '../git' 5 | OutputView = require './output-view' 6 | notifier = require '../notifier' 7 | SelectListMultipleView = require './select-list-multiple-view' 8 | 9 | module.exports = 10 | class SelectStageHunks extends SelectListMultipleView 11 | initialize: (@repo, data) -> 12 | super 13 | @patch_header = data[0] 14 | return @completed @_generateObjects(data[1..]) if data.length is 2 15 | @show() 16 | @setItems @_generateObjects(data[1..]) 17 | @focusFilterEditor() 18 | 19 | getFilterKey: -> 20 | 'pos' 21 | 22 | addButtons: -> 23 | viewButton = $$ -> 24 | @div class: 'buttons', => 25 | @span class: 'pull-left', => 26 | @button class: 'btn btn-error inline-block-tight btn-cancel-button', 'Cancel' 27 | @span class: 'pull-right', => 28 | @button class: 'btn btn-success inline-block-tight btn-stage-button', 'Stage' 29 | viewButton.appendTo(this) 30 | 31 | @on 'click', 'button', ({target}) => 32 | @complete() if $(target).hasClass('btn-stage-button') 33 | @cancel() if $(target).hasClass('btn-cancel-button') 34 | 35 | show: -> 36 | @panel ?= atom.workspace.addModalPanel(item: this) 37 | @panel.show() 38 | @storeFocusedElement() 39 | 40 | cancelled: -> @hide() 41 | 42 | hide: -> @panel?.destroy() 43 | 44 | viewForItem: (item, matchedStr) -> 45 | viewItem = $$ -> 46 | @li => 47 | @div class: 'inline-block highlight', => 48 | if matchedStr? then @raw(matchedStr) else @span item.pos 49 | @div class: 'text-warning gp-item-diff', style: 'white-space: pre-wrap; font-family: monospace', item.diff 50 | 51 | completed: (items) -> 52 | @cancel() 53 | return if items.length < 1 54 | 55 | patch_full = @patch_header 56 | patch_full += patch for {patch} in items 57 | 58 | patchPath = @repo.getWorkingDirectory() + '/GITPLUS_PATCH' 59 | fs.writeFileSync patchPath, patch_full, flag: 'w+' 60 | git.cmd 61 | args: ['apply', '--cached', '--', patchPath] 62 | cwd: @repo.getWorkingDirectory() 63 | stdout: (data) => 64 | data = if data? and data isnt '' then data else 'Hunk has been staged!' 65 | notifier.addSuccess(data) 66 | try fs.unlink patchPath 67 | 68 | _generateObjects: (data) -> 69 | for hunk in data when hunk isnt '' 70 | hunkSplit = hunk.match /(@@[ \-\+\,0-9]*@@.*)\n([\s\S]*)/ 71 | { 72 | pos: hunkSplit[1] 73 | diff: hunkSplit[2] 74 | patch: hunk 75 | } 76 | -------------------------------------------------------------------------------- /lib/views/git-palette-view.coffee: -------------------------------------------------------------------------------- 1 | _ = require 'underscore-plus' 2 | {$, $$, SelectListView} = require 'atom-space-pen-views' 3 | git = require '../git' 4 | GitPlusCommands = require '../git-plus-commands' 5 | GitInit = require '../models/git-init' 6 | fuzzy = require('../models/fuzzy').filter 7 | 8 | module.exports = 9 | class GitPaletteView extends SelectListView 10 | 11 | initialize: -> 12 | super 13 | @addClass('git-palette') 14 | @toggle() 15 | 16 | getFilterKey: -> 17 | 'description' 18 | 19 | cancelled: -> @hide() 20 | 21 | toggle: -> 22 | if @panel?.isVisible() 23 | @cancel() 24 | else 25 | @show() 26 | 27 | show: -> 28 | @panel ?= atom.workspace.addModalPanel(item: this) 29 | 30 | @storeFocusedElement() 31 | 32 | if @previouslyFocusedElement[0] and @previouslyFocusedElement[0] isnt document.body 33 | @commandElement = @previouslyFocusedElement 34 | else 35 | @commandElement = atom.views.getView(atom.workspace) 36 | @keyBindings = atom.keymaps.findKeyBindings(target: @commandElement[0]) 37 | 38 | GitPlusCommands() 39 | .then (commands) => 40 | commands = commands.map (c) -> { name: c[0], description: c[1], func: c[2] } 41 | commands = _.sortBy(commands, 'name') 42 | @setItems(commands) 43 | @panel.show() 44 | @focusFilterEditor() 45 | .catch (err) => 46 | (commands = []).push { name: 'git-plus:init', description: 'Init', func: -> GitInit() } 47 | @setItems(commands) 48 | @panel.show() 49 | @focusFilterEditor() 50 | 51 | populateList: -> 52 | return unless @items? 53 | 54 | filterQuery = @getFilterQuery() 55 | if filterQuery.length 56 | options = 57 | pre: '' 58 | post: "" 59 | extract: (el) => if @getFilterKey()? then el[@getFilterKey()] else el 60 | filteredItems = fuzzy(filterQuery, @items, options) 61 | else 62 | filteredItems = @items 63 | 64 | @list.empty() 65 | if filteredItems.length 66 | @setError(null) 67 | for i in [0...Math.min(filteredItems.length, @maxItems)] 68 | item = filteredItems[i].original ? filteredItems[i] 69 | itemView = $(@viewForItem(item, filteredItems[i].string ? null)) 70 | itemView.data('select-list-item', item) 71 | @list.append(itemView) 72 | 73 | @selectItemView(@list.find('li:first')) 74 | else 75 | @setError(@getEmptyMessage(@items.length, filteredItems.length)) 76 | 77 | hide: -> 78 | @panel?.destroy() 79 | 80 | viewForItem: ({name, description}, matchedStr) -> 81 | $$ -> 82 | @li class: 'command', 'data-command-name': name, => 83 | if matchedStr? then @raw(matchedStr) else @span description 84 | 85 | confirmed: ({func}) -> 86 | @cancel() 87 | func() 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git-Plus package 2 | 3 | [![endorse](https://api.coderwall.com/akonwi/endorsecount.png)](https://coderwall.com/akonwi) 4 | 5 | vim-fugitive like package for atom. make commits and other git things without the terminal 6 | 7 | ![A screenshot of your spankin' package](https://raw.githubusercontent.com/akonwi/git-plus/master/commit.gif) 8 | 9 | ## Usage 10 | 11 | # IMPORTANT: Make sure your gitconfig file is configured or at least your `user.email` and `user.name` variables are initialized 12 | 13 | ### Git-Plus Palette 14 | >- `Cmd-Shift-H` on MacOS 15 | >- `Ctrl-Shift-H` on Windows + Linux 16 | >- `Git Plus: Menu` on the atom command palette. 17 | 18 | ### Commands 19 | _Commands are accessible for keybindings by dasherizing the command title._ 20 | > Git Add == `git-plus:add` 21 | 22 | > Git Add All Commit And Push == `git-plus:add-all-commit-and-push` 23 | 24 | __Note: This list is not exclusive__ 25 | 1. `Git add [all]` 26 | 27 | Git add will add the current file and 'add all' will add all changed files 28 | Default key binding: `Cmd-Shift-A` 29 | 30 | 2. `Git add all commit and push` 31 | 32 | `Cmd-Shift-A P` 33 | 34 | 3. `Git commit` 35 | 36 | Will pull up a commit message file. The commit will be made when the file is saved NOT when the pane is closed. You can just cancel by closing the tab. 37 | Default key binding: `Cmd-Shift-C`(*`Ctrl-Shift-X`* on Windows + Linux) 38 | 39 | 4. `Git commit amend` 40 | 41 | Will amend the changes to previous commit. 42 | 43 | 5. `Git checkout current file` 44 | 45 | Undo changes and checkout current file. 46 | 47 | 6. `Git [checkout]` 48 | 49 | Change branches 50 | 51 | 7. `Git Diff [All]` 52 | 53 | Shows diff for current file or All the files. Diff can either be with staged or unstaged as selected in options. 54 | 55 | 8. `Git new branch` 56 | 57 | Create a new branch 58 | 59 | 9. `Git [push|pull]` 60 | 61 | When Pushing, if you have multiple remote repos, you can choose which to push to. 62 | 10. `Git Add and Commit` 63 | 64 | Add the current file and pull up the commit message file. Similar to `Git add` and `Git commit` in succession. 65 | Default key binding: `Cmd-Shift-A c` 66 | 67 | 11. `Git Add All and Commit` 68 | 69 | Add all changed files and pull up the commit message file. Similar to `Git add all` and `Git commit` in succession. 70 | Default key binding: `Cmd-Shift-A a` 71 | 72 | 12. `Git rm [current file]` 73 | 74 | Git rm the current file or open an selector to select the files to remove. You can select multiple files at once. 75 | 76 | 13. `Git Log [Current File]` 77 | 78 | Show the commit history [for the current file] and show display the selected commit. 79 | 80 | 13. `Git Show` 81 | 82 | Show the specified object, for example `HEAD`, `HEAD~2`, `3925a0d`, `origin/master` or `v2.7.3`. 83 | 84 | ## Contributing 85 | 86 | 1. Fork it 87 | 2. Create your feature branch (`git checkout -b my-new-feature`) 88 | 3. Commit your changes (`git commit -am 'Add some feature'`) 89 | 4. Push to the branch (`git push origin my-new-feature`) 90 | 5. Create new Pull Request 91 | -------------------------------------------------------------------------------- /lib/views/log-list-view.coffee: -------------------------------------------------------------------------------- 1 | {Disposable} = require 'atom' 2 | {BufferedProcess} = require 'atom' 3 | {$, $$$, ScrollView} = require 'atom-space-pen-views' 4 | git = require '../git' 5 | GitShow = require '../models/git-show' 6 | 7 | amountOfCommitsToShow = -> 8 | atom.config.get('git-plus.amountOfCommitsToShow') 9 | 10 | module.exports = 11 | class LogListView extends ScrollView 12 | @content: -> 13 | @div class: 'git-plus-log native-key-bindings', tabindex: -1, => 14 | @table id: 'git-plus-commits', outlet: 'commitsListView' 15 | 16 | onDidChangeTitle: -> new Disposable 17 | onDidChangeModified: -> new Disposable 18 | 19 | getURI: -> 'atom://git-plus:log' 20 | 21 | getTitle: -> 'git-plus: Log' 22 | 23 | initialize: -> 24 | super 25 | @skipCommits = 0 26 | @on 'click', '.commit-row', ({currentTarget}) => 27 | @showCommitLog currentTarget.getAttribute('hash') 28 | @scroll => 29 | @getLog() if @scrollTop() + @height() is @prop('scrollHeight') 30 | 31 | setRepo: (@repo) -> 32 | 33 | parseData: (data) -> 34 | if data.length > 0 35 | separator = ';|' 36 | newline = '_.;._' 37 | data = data.substring(0, data.length - newline.length - 1) 38 | 39 | commits = data.split(newline).map (line) -> 40 | if line.trim() isnt '' 41 | tmpData = line.trim().split(separator) 42 | return { 43 | hashShort: tmpData[0] 44 | hash: tmpData[1] 45 | author: tmpData[2] 46 | email: tmpData[3] 47 | message: tmpData[4] 48 | date: tmpData[5] 49 | } 50 | 51 | @renderLog commits 52 | 53 | renderHeader: -> 54 | headerRow = $$$ -> 55 | @tr class: 'commit-header', => 56 | @td 'Date' 57 | @td 'Message' 58 | @td class: 'hashShort', 'Short Hash' 59 | 60 | @commitsListView.append(headerRow) 61 | 62 | renderLog: (commits) -> 63 | commits.forEach (commit) => @renderCommit commit 64 | @skipCommits += amountOfCommitsToShow() 65 | 66 | renderCommit: (commit) -> 67 | commitRow = $$$ -> 68 | @tr class: 'commit-row', hash: "#{commit.hash}", => 69 | @td class: 'date', "#{commit.date} by #{commit.author}" 70 | @td class: 'message', "#{commit.message}" 71 | @td class: 'hashShort', "#{commit.hashShort}" 72 | 73 | @commitsListView.append(commitRow) 74 | 75 | showCommitLog: (hash) -> 76 | GitShow(@repo, hash, @currentFile if @onlyCurrentFile) 77 | 78 | branchLog: -> 79 | @skipCommits = 0 80 | @commitsListView.empty() 81 | @onlyCurrentFile = false 82 | @currentFile = null 83 | @renderHeader() 84 | @getLog() 85 | 86 | currentFileLog: (@onlyCurrentFile, @currentFile) -> 87 | @skipCommits = 0 88 | @commitsListView.empty() 89 | @renderHeader() 90 | @getLog() 91 | 92 | getLog: -> 93 | args = ['log', "--pretty=%h;|%H;|%aN;|%aE;|%s;|%ai_.;._", "-#{amountOfCommitsToShow()}", '--skip=' + @skipCommits] 94 | args.push @currentFile if @onlyCurrentFile and @currentFile? 95 | git.cmd 96 | args: args 97 | cwd: @repo.getWorkingDirectory() 98 | stdout: (data) => 99 | @parseData data 100 | -------------------------------------------------------------------------------- /lib/models/fuzzy.coffee: -------------------------------------------------------------------------------- 1 | # Fuzzy 2 | # https://github.com/myork/fuzzy 3 | # 4 | # Copyright (c) 2012 Matt York 5 | # Licensed under the MIT license. 6 | 7 | fuzzy = {} 8 | module.exports = fuzzy 9 | 10 | # Return all elements of `array` that have a fuzzy 11 | # match against `pattern`. 12 | fuzzy.simpleFilter = (pattern, array) -> 13 | array.filter (string) -> 14 | fuzzy.test pattern, string 15 | 16 | # Does `pattern` fuzzy match `string`? 17 | fuzzy.test = (pattern, string) -> 18 | fuzzy.match(pattern, string) isnt null 19 | 20 | # If `pattern` (input) matches `string` (test against), wrap each matching 21 | # character in `opts.pre` and `opts.post`. If no match, return null 22 | fuzzy.match = (pattern, string, opts={}) -> 23 | patternIdx = 0 24 | result = [] 25 | len = string.length 26 | totalScore = 0 27 | currScore = 0 28 | 29 | # prefix 30 | pre = opts.pre or "" 31 | 32 | # suffix 33 | post = opts.post or "" 34 | 35 | # String to compare against. This might be a lowercase version of the 36 | # raw string 37 | compareString = opts.caseSensitive and string or string.toLowerCase() 38 | ch = undefined 39 | compareChar = undefined 40 | pattern = opts.caseSensitive and pattern or pattern.toLowerCase() 41 | 42 | # For each character in the string, either add it to the result 43 | # or wrap in template if its the next string in the pattern 44 | idx = 0 45 | while idx < len 46 | # Ignore Whitespaces 47 | patternIdx++ if pattern[patternIdx] is ' ' 48 | 49 | ch = string[idx] 50 | if compareString[idx] is pattern[patternIdx] 51 | ch = pre + ch + post 52 | patternIdx += 1 53 | 54 | currScore += 1 + currScore 55 | else 56 | currScore = 0 57 | totalScore += currScore 58 | result[result.length] = ch 59 | idx++ 60 | return {rendered: result.join(""), score: totalScore} if patternIdx is pattern.length 61 | 62 | fuzzy.filter = (pattern, arr, opts={}) -> 63 | highlighted = arr.reduce( 64 | (prev, element, idx, arr) -> 65 | str = element 66 | str = opts.extract(element) if opts.extract 67 | rendered = fuzzy.match(pattern, str, opts) 68 | if rendered? 69 | prev[prev.length] = 70 | string: rendered.rendered 71 | score: rendered.score 72 | index: idx 73 | original: element 74 | prev 75 | ,[] 76 | ).sort (a, b) -> 77 | compare = b.score - a.score 78 | if compare is 0 79 | return opts.extract(a.original).length - opts.extract(b.original).length if opts.extract 80 | return a.original.length - b.original.length 81 | return compare if compare 82 | a.index - b.index 83 | 84 | # No matches? Sort the original array using Damerau-Levenshtein. 85 | if highlighted.length < 1 86 | highlighted = arr.reduce( 87 | (prev, element, idx, arr) -> 88 | str = element 89 | str = opts.extract(element) if opts.extract 90 | prev[prev.length] = 91 | string: str 92 | score: levenshtein(pattern, str) 93 | index: idx 94 | original: element 95 | prev 96 | ,[] 97 | ).sort (a, b) -> 98 | compare = a.score - b.score 99 | return compare if compare 100 | b.index - a.index 101 | highlighted 102 | 103 | ### 104 | # Copyright (c) 2011 Andrei Mackenzie 105 | # 106 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 107 | # this software and associated documentation files (the "Software"), to deal in 108 | # the Software without restriction, including without limitation the rights to 109 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 110 | # the Software, and to permit persons to whom the Software is furnished to do so, 111 | # subject to the following conditions: 112 | # 113 | # The above copyright notice and this permission notice shall be included in all 114 | # copies or substantial portions of the Software. 115 | # 116 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 117 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 118 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 119 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 120 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 121 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 122 | ### 123 | 124 | # Compute the edit distance between the two given strings 125 | levenshtein = (a, b) -> 126 | return b.length if a.length is 0 127 | return a.length if b.length is 0 128 | matrix = [] 129 | 130 | # increment along the first column of each row 131 | i = undefined 132 | i = 0 133 | while i <= b.length 134 | matrix[i] = [i] 135 | i++ 136 | 137 | # increment each column in the first row 138 | j = undefined 139 | j = 0 140 | while j <= a.length 141 | matrix[0][j] = j 142 | j++ 143 | 144 | # Fill in the rest of the matrix 145 | i = 1 146 | while i <= b.length 147 | j = 1 148 | while j <= a.length 149 | if b.charAt(i - 1) is a.charAt(j - 1) 150 | matrix[i][j] = matrix[i - 1][j - 1] 151 | else 152 | # substitution 153 | # insertion 154 | matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)) # deletion 155 | j++ 156 | i++ 157 | matrix[b.length][a.length] 158 | -------------------------------------------------------------------------------- /lib/git-plus-commands.coffee: -------------------------------------------------------------------------------- 1 | git = require './git' 2 | 3 | getCommands = -> 4 | GitAdd = require './models/git-add' 5 | GitAddAllAndCommit = require './models/git-add-all-and-commit' 6 | GitAddAllCommitAndPush = require './models/git-add-all-commit-and-push' 7 | GitAddAndCommit = require './models/git-add-and-commit' 8 | GitBranch = require './models/git-branch' 9 | GitDeleteLocalBranch = require './models/git-delete-local-branch.coffee' 10 | GitDeleteRemoteBranch = require './models/git-delete-remote-branch.coffee' 11 | GitCheckoutAllFiles = require './models/git-checkout-all-files' 12 | GitCheckoutCurrentFile = require './models/git-checkout-current-file' 13 | GitCherryPick = require './models/git-cherry-pick' 14 | GitCommit = require './models/git-commit' 15 | GitCommitAmend = require './models/git-commit-amend' 16 | GitDiff = require './models/git-diff' 17 | GitDiffAll = require './models/git-diff-all' 18 | GitFetch = require './models/git-fetch' 19 | GitFetchPrune = require './models/git-fetch-prune.coffee' 20 | GitInit = require './models/git-init' 21 | GitLog = require './models/git-log' 22 | GitPull = require './models/git-pull' 23 | GitPush = require './models/git-push' 24 | GitRemove = require './models/git-remove' 25 | GitShow = require './models/git-show' 26 | GitStageFiles = require './models/git-stage-files' 27 | GitStageHunk = require './models/git-stage-hunk' 28 | GitStashApply = require './models/git-stash-apply' 29 | GitStashDrop = require './models/git-stash-drop' 30 | GitStashPop = require './models/git-stash-pop' 31 | GitStashSave = require './models/git-stash-save' 32 | GitStatus = require './models/git-status' 33 | GitTags = require './models/git-tags' 34 | GitUnstageFiles = require './models/git-unstage-files' 35 | GitRun = require './models/git-run' 36 | GitMerge = require './models/git-merge' 37 | 38 | git.getRepo() 39 | .then (repo) -> 40 | git.refresh() 41 | commands = [] 42 | commands.push ['git-plus:add', 'Add', -> GitAdd(repo)] 43 | commands.push ['git-plus:add-all', 'Add All', -> GitAdd(repo, addAll: true)] 44 | commands.push ['git-plus:log', 'Log', -> GitLog(repo)] 45 | commands.push ['git-plus:log-current-file', 'Log Current File', -> GitLog(repo, onlyCurrentFile: true)] 46 | commands.push ['git-plus:remove-current-file', 'Remove Current File', -> GitRemove(repo)] 47 | commands.push ['git-plus:checkout-all-files', 'Checkout All Files', -> GitCheckoutAllFiles(repo)] 48 | commands.push ['git-plus:checkout-current-file', 'Checkout Current File', -> GitCheckoutCurrentFile(repo)] 49 | commands.push ['git-plus:commit', 'Commit', -> new GitCommit(repo)] 50 | commands.push ['git-plus:commit-all', 'Commit All', -> new GitCommit(repo, stageChanges: true)] 51 | commands.push ['git-plus:commit-amend', 'Commit Amend', -> GitCommitAmend(repo)] 52 | commands.push ['git-plus:add-and-commit', 'Add And Commit', -> GitAddAndCommit(repo)] 53 | commands.push ['git-plus:add-all-and-commit', 'Add All And Commit', -> GitAddAllAndCommit(repo)] 54 | commands.push ['git-plus:add-all-commit-and-push', 'Add All Commit And Push', -> GitAddAllCommitAndPush(repo)] 55 | commands.push ['git-plus:checkout', 'Checkout', -> GitBranch.gitBranches(repo)] 56 | commands.push ['git-plus:checkout-remote', 'Checkout Remote', -> GitBranch.gitRemoteBranches(repo)] 57 | commands.push ['git-plus:new-branch', 'Checkout New Branch', -> GitBranch.newBranch(repo)] 58 | commands.push ['git-plus:delete-local-branch', 'Delete Local Branch', -> GitDeleteLocalBranch(repo)] 59 | commands.push ['git-plus:delete-remote-branch', 'Delete Remote Branch', -> GitDeleteRemoteBranch(repo)] 60 | commands.push ['git-plus:cherry-pick', 'Cherry-Pick', -> GitCherryPick(repo)] 61 | commands.push ['git-plus:diff', 'Diff', -> GitDiff(repo)] 62 | commands.push ['git-plus:diff-all', 'Diff All', -> GitDiffAll(repo)] 63 | commands.push ['git-plus:fetch', 'Fetch', -> GitFetch(repo)] 64 | commands.push ['git-plus:fetch-prune', 'Fetch Prune', -> GitFetchPrune(repo)] 65 | commands.push ['git-plus:pull', 'Pull', -> GitPull(repo)] 66 | commands.push ['git-plus:pull-using-rebase', 'Pull Using Rebase', -> GitPull(repo, rebase: true)] 67 | commands.push ['git-plus:push', 'Push', -> GitPush(repo)] 68 | commands.push ['git-plus:remove', 'Remove', -> GitRemove(repo, showSelector: true)] 69 | commands.push ['git-plus:reset', 'Reset HEAD', -> git.reset(repo)] 70 | commands.push ['git-plus:show', 'Show', -> GitShow(repo)] 71 | commands.push ['git-plus:stage-files', 'Stage Files', -> GitStageFiles(repo)] 72 | commands.push ['git-plus:unstage-files', 'Unstage Files', -> GitUnstageFiles(repo)] 73 | commands.push ['git-plus:stage-hunk', 'Stage Hunk', -> GitStageHunk(repo)] 74 | commands.push ['git-plus:stash-save-changes', 'Stash: Save Changes', -> GitStashSave(repo)] 75 | commands.push ['git-plus:stash-pop', 'Stash: Apply (Pop)', -> GitStashPop(repo)] 76 | commands.push ['git-plus:stash-apply', 'Stash: Apply (Keep)', -> GitStashApply(repo)] 77 | commands.push ['git-plus:stash-delete', 'Stash: Delete (Drop)', -> GitStashDrop(repo)] 78 | commands.push ['git-plus:status', 'Status', -> GitStatus(repo)] 79 | commands.push ['git-plus:tags', 'Tags', -> GitTags(repo)] 80 | commands.push ['git-plus:run', 'Run', -> new GitRun(repo)] 81 | commands.push ['git-plus:merge', 'Merge', -> GitMerge(repo)] 82 | 83 | return commands 84 | 85 | module.exports = getCommands 86 | -------------------------------------------------------------------------------- /lib/views/select-list-multiple-view.coffee: -------------------------------------------------------------------------------- 1 | fuzzyFilter = require('../models/fuzzy').filter 2 | {$, $$, View, SelectListView} = require 'atom-space-pen-views' 3 | 4 | # Public: Provides a view that renders a list of items with an editor that 5 | # filters the items. Enables you to select multiple items at once. 6 | # 7 | # Subclasses must implement the following methods: 8 | # 9 | # * {::viewForItem} 10 | # * {::completed} 11 | # 12 | # Subclasses should implement the following methods: 13 | # 14 | # * {::addButtons} 15 | # 16 | # ## Requiring in packages 17 | # 18 | # ```coffee 19 | # {SelectListMultipleView} = require 'atom' 20 | # 21 | # class MySelectListView extends SelectListMultipleView 22 | # initialize: -> 23 | # super 24 | # @addClass('overlay from-top') 25 | # @setItems(['Hello', 'World']) 26 | # atom.workspaceView.append(this) 27 | # @focusFilterEditor() 28 | # 29 | # viewForItem: (item) -> 30 | # "
  • #{item}
  • " 31 | # 32 | # completed: (items) -> 33 | # console.log("#{items} were selected") 34 | # ``` 35 | module.exports = 36 | class SelectListMultipleView extends SelectListView 37 | 38 | selectedItems = [] 39 | 40 | # 41 | # This method can be overridden by subclasses but `super` should always 42 | # be called. 43 | initialize: -> 44 | super 45 | selectedItems = [] 46 | @list.addClass('mark-active') 47 | 48 | @on 'mousedown', ({target}) => 49 | false if target is @list[0] or $(target).hasClass('btn') 50 | @on 'keypress', ({keyCode}) => @complete() if keyCode is 13 51 | @addButtons() 52 | 53 | # Public: Function to add buttons to the SelectListMultipleView. 54 | # 55 | # This method can be overridden by subclasses. 56 | # 57 | # ### Important 58 | # There must always be a button to call the function `@complete()` to 59 | # confirm the selections! 60 | # 61 | # #### Example (Default) 62 | # ```coffee 63 | # addButtons: -> 64 | # viewButton = $$ -> 65 | # @div class: 'buttons', => 66 | # @span class: 'pull-left', => 67 | # @button class: 'btn btn-error inline-block-tight btn-cancel-button', 'Cancel' 68 | # @span class: 'pull-right', => 69 | # @button class: 'btn btn-success inline-block-tight btn-complete-button', 'Confirm' 70 | # viewButton.appendTo(this) 71 | # 72 | # @on 'click', 'button', ({target}) => 73 | # @complete() if $(target).hasClass('btn-complete-button') 74 | # @cancel() if $(target).hasClass('btn-cancel-button') 75 | # ``` 76 | addButtons: -> 77 | viewButton = $$ -> 78 | @div class: 'buttons', => 79 | @span class: 'pull-left', => 80 | @button class: 'btn btn-error inline-block-tight btn-cancel-button', 'Cancel' 81 | @span class: 'pull-right', => 82 | @button class: 'btn btn-success inline-block-tight btn-complete-button', 'Confirm' 83 | viewButton.appendTo(this) 84 | 85 | @on 'click', 'button', ({target}) => 86 | @complete() if $(target).hasClass('btn-complete-button') 87 | @cancel() if $(target).hasClass('btn-cancel-button') 88 | 89 | confirmSelection: -> 90 | item = @getSelectedItem() 91 | viewItem = @getSelectedItemView() 92 | if viewItem? 93 | @confirmed(item, viewItem) 94 | else 95 | @cancel() 96 | 97 | confirmed: (item, viewItem) -> 98 | if item in selectedItems 99 | selectedItems = selectedItems.filter (i) -> i isnt item 100 | viewItem.removeClass('active') 101 | else 102 | selectedItems.push item 103 | viewItem.addClass('active') 104 | 105 | complete: -> 106 | if selectedItems.length > 0 107 | @completed(selectedItems) 108 | else 109 | @cancel() 110 | 111 | # Public: Populate the list view with the model items previously set by 112 | # calling {::setItems}. 113 | # 114 | # Subclasses may override this method but should always call `super`. 115 | populateList: -> 116 | return unless @items? 117 | 118 | filterQuery = @getFilterQuery() 119 | if filterQuery.length 120 | options = 121 | pre: '' 122 | post: "" 123 | extract: (el) => if @getFilterKey()? then el[@getFilterKey()] else el 124 | filteredItems = fuzzyFilter(filterQuery, @items, options) 125 | else 126 | filteredItems = @items 127 | 128 | @list.empty() 129 | if filteredItems.length 130 | @setError(null) 131 | for i in [0...Math.min(filteredItems.length, @maxItems)] 132 | item = filteredItems[i].original ? filteredItems[i] 133 | itemView = $(@viewForItem(item, filteredItems[i].string ? null)) 134 | itemView.data('select-list-item', item) 135 | itemView.addClass 'active' if item in selectedItems 136 | @list.append(itemView) 137 | 138 | @selectItemView(@list.find('li:first')) 139 | else 140 | @setError(@getEmptyMessage(@items.length, filteredItems.length)) 141 | 142 | # Public: Create a view for the given model item. 143 | # 144 | # This method must be overridden by subclasses. 145 | # 146 | # This is called when the item is about to appended to the list view. 147 | # 148 | # item - The model item being rendered. This will always be one of 149 | # the items previously passed to {::setItems}. 150 | # matchedStr - The fuzzy highlighted string. 151 | # 152 | # Returns a String of HTML, DOM element, jQuery object, or View. 153 | viewForItem: (item, matchedStr) -> 154 | throw new Error("Subclass must implement a viewForItem(item) method") 155 | 156 | # Public: Callback function for when the complete button is pressed. 157 | # 158 | # This method must be overridden by subclasses. 159 | # 160 | # items - An {Array} containing the selected items. This will always be one 161 | # of the items previously passed to {::setItems}. 162 | # 163 | # Returns a DOM element, jQuery object, or {View}. 164 | completed: (items) -> 165 | throw new Error("Subclass must implement a completed(items) method") 166 | -------------------------------------------------------------------------------- /lib/models/git-commit.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | fs = require 'fs-plus' 3 | Path = require 'flavored-path' 4 | os = require 'os' 5 | 6 | git = require '../git' 7 | notifier = require '../notifier' 8 | GitPush = require './git-push' 9 | 10 | module.exports = 11 | class GitCommit 12 | # Public: Helper method to return the current working directory. 13 | # 14 | # Returns: The cwd as a String. 15 | dir: -> 16 | # path is different for submodules 17 | if @submodule ?= git.getSubmodule() 18 | @submodule.getWorkingDirectory() 19 | else 20 | @repo.getWorkingDirectory() 21 | 22 | # Public: Helper method to join @dir() and filename to use it with fs. 23 | # 24 | # Returns: The full path to our COMMIT_EDITMSG file as {String} 25 | filePath: -> Path.join(@repo.getPath(), 'COMMIT_EDITMSG') 26 | 27 | constructor: (@repo, {@amend, @andPush, @stageChanges}={}) -> 28 | @currentPane = atom.workspace.getActivePane() 29 | @disposables = new CompositeDisposable 30 | 31 | # Check if we are amending right now. 32 | @amend ?= '' 33 | @isAmending = @amend.length > 0 34 | 35 | # Load the commentchar from git config, defaults to '#' 36 | @commentchar = '#' 37 | git.cmd 38 | args: ['config', '--get', 'core.commentchar'], 39 | stdout: (data) => 40 | if data.trim() isnt '' 41 | @commentchar = data.trim() 42 | 43 | if @stageChanges 44 | git.add @repo, 45 | update: true, 46 | exit: (code) => @getStagedFiles() 47 | else 48 | @getStagedFiles() 49 | 50 | getStagedFiles: -> 51 | git.stagedFiles @repo, (files) => 52 | if @amend isnt '' or files.length >= 1 53 | git.cmd 54 | args: ['status'], 55 | cwd: @repo.getWorkingDirectory() 56 | stdout: (data) => @prepFile data 57 | else 58 | @cleanup() 59 | notifier.addInfo 'Nothing to commit.' 60 | 61 | # Public: Prepares our commit message file by writing the status and a 62 | # possible amend message to it. 63 | # 64 | # status - The current status as {String}. 65 | prepFile: (status) -> 66 | # format the status to be ignored in the commit message 67 | status = status.replace(/\s*\(.*\)\n/g, "\n") 68 | status = status.trim().replace(/\n/g, "\n#{@commentchar} ") 69 | 70 | @getTemplate().then (template) => 71 | fs.writeFileSync @filePath(), 72 | """#{ if @amend.length > 0 then @amend else template} 73 | #{@commentchar} Please enter the commit message for your changes. Lines starting 74 | #{@commentchar} with '#{@commentchar}' will be ignored, and an empty message aborts the commit. 75 | #{@commentchar} 76 | #{@commentchar} #{status}""" 77 | @showFile() 78 | 79 | getTemplate: -> 80 | new Promise (resolve, reject) -> 81 | git.cmd 82 | args: ['config', '--get', 'commit.template'] 83 | stdout: (data) => 84 | resolve (if data.trim() isnt '' then fs.readFileSync(Path.get(data.trim())) else '') 85 | 86 | # Public: Helper method to open the commit message file and to subscribe the 87 | # 'saved' and `destroyed` events of the underlaying text-buffer. 88 | showFile: -> 89 | atom.workspace 90 | .open(@filePath(), searchAllPanes: true) 91 | .done (textEditor) => 92 | if atom.config.get('git-plus.openInPane') 93 | @splitPane(atom.config.get('git-plus.splitPane'), textEditor) 94 | else 95 | @disposables.add textEditor.onDidSave => @commit() 96 | @disposables.add textEditor.onDidDestroy => 97 | if @isAmending then @undoAmend() else @cleanup() 98 | 99 | splitPane: (splitDir, oldEditor) -> 100 | pane = atom.workspace.paneForURI(@filePath()) 101 | options = { copyActiveItem: true } 102 | hookEvents = (textEditor) => 103 | oldEditor.destroy() 104 | @disposables.add textEditor.onDidSave => @commit() 105 | @disposables.add textEditor.onDidDestroy => 106 | if @isAmending then @undoAmend() else @cleanup() 107 | 108 | directions = 109 | left: => 110 | pane = pane.splitLeft options 111 | hookEvents(pane.getActiveEditor()) 112 | right: -> 113 | pane = pane.splitRight options 114 | hookEvents(pane.getActiveEditor()) 115 | up: -> 116 | pane = pane.splitUp options 117 | hookEvents(pane.getActiveEditor()) 118 | down: -> 119 | pane = pane.splitDown options 120 | hookEvents(pane.getActiveEditor()) 121 | directions[splitDir]() 122 | 123 | # Public: When the user is done editing the commit message an saves the file 124 | # this method gets invoked and commits the changes. 125 | commit: -> 126 | args = ['commit', '--cleanup=strip', "--file=#{@filePath()}"] 127 | git.cmd 128 | args: args, 129 | options: 130 | cwd: @dir() 131 | stdout: (data) => 132 | notifier.addSuccess data 133 | if @andPush 134 | new GitPush(@repo) 135 | @isAmending = false 136 | @destroyCommitEditor() 137 | # Activate the former active pane. 138 | @currentPane.activate() if @currentPane.alive 139 | git.refresh() 140 | 141 | stderr: (err) => @destroyCommitEditor() 142 | 143 | destroyCommitEditor: -> 144 | @cleanup() 145 | atom.workspace.getPanes().some (pane) -> 146 | pane.getItems().some (paneItem) -> 147 | if paneItem?.getURI?()?.includes 'COMMIT_EDITMSG' 148 | if pane.getItems().length is 1 149 | pane.destroy() 150 | else 151 | paneItem.destroy() 152 | return true 153 | 154 | # Public: Undo the amend 155 | # 156 | # err - The error message as {String}. 157 | undoAmend: (err='') -> 158 | git.cmd 159 | args: ['reset', 'ORIG_HEAD'], 160 | stdout: -> 161 | notifier.addError "#{err}: Commit amend aborted!" 162 | stderr: -> 163 | notifier.addError 'ERROR! Undoing the amend failed! Please fix your repository manually!' 164 | exit: => 165 | # Set @isAmending to false since the amending process has been aborted. 166 | @isAmending = false 167 | 168 | # Destroying the active EditorView will trigger our cleanup method. 169 | @destroyCommitEditor() 170 | 171 | # Public: Cleans up after the EditorView gets destroyed. 172 | cleanup: -> 173 | @currentPane.activate() if @currentPane.alive 174 | @disposables.dispose() 175 | try fs.unlinkSync @filePath() 176 | -------------------------------------------------------------------------------- /lib/git.coffee: -------------------------------------------------------------------------------- 1 | {BufferedProcess} = require 'atom' 2 | RepoListView = require './views/repo-list-view' 3 | notifier = require './notifier' 4 | 5 | # Public: Execute a git command. 6 | # 7 | # options - An {Object} with the following keys: 8 | # :args - The {Array} containing the arguments to pass. 9 | # :cwd - Current working directory as {String}. 10 | # :options - The {Object} with options to pass. 11 | # :stdout - The {Function} to pass the stdout to. 12 | # :exit - The {Function} to pass the exit code to. 13 | # 14 | # Returns nothing. 15 | gitCmd = ({args, cwd, options, stdout, stderr, exit}={}) -> 16 | command = _getGitPath() 17 | options ?= {} 18 | options.cwd ?= cwd 19 | stderr ?= (data) -> notifier.addError data.toString() 20 | 21 | if stdout? and not exit? 22 | c_stdout = stdout 23 | stdout = (data) -> 24 | @save ?= '' 25 | @save += data 26 | exit = (exit) -> 27 | c_stdout @save ?= '' 28 | @save = null 29 | 30 | try 31 | new BufferedProcess 32 | command: command 33 | args: args 34 | options: options 35 | stdout: stdout 36 | stderr: stderr 37 | exit: exit 38 | catch error 39 | notifier.addError 'Git Plus is unable to locate git command. Please ensure process.env.PATH can access git.' 40 | 41 | gitStatus = (repo, stdout) -> 42 | gitCmd 43 | args: ['status', '--porcelain', '-z'] 44 | cwd: repo.getWorkingDirectory() 45 | stdout: (data) -> stdout(if data.length > 2 then data.split('\0') else []) 46 | 47 | gitStagedFiles = (repo, stdout) -> 48 | files = [] 49 | gitCmd 50 | args: ['diff-index', '--cached', 'HEAD', '--name-status', '-z'] 51 | cwd: repo.getWorkingDirectory() 52 | stdout: (data) -> 53 | files = _prettify(data) 54 | stderr: (data) -> 55 | # edge case of no HEAD at initial commit 56 | if data.toString().includes "ambiguous argument 'HEAD'" 57 | files = [1] 58 | else 59 | notifier.addError data.toString() 60 | files = [] 61 | exit: (code) -> stdout(files) 62 | 63 | gitUnstagedFiles = (repo, {showUntracked}={}, stdout) -> 64 | gitCmd 65 | args: ['diff-files', '--name-status', '-z'] 66 | cwd: repo.getWorkingDirectory() 67 | stdout: (data) -> 68 | if showUntracked 69 | gitUntrackedFiles(repo, _prettify(data), stdout) 70 | else 71 | stdout _prettify(data) 72 | 73 | gitUntrackedFiles = (repo, dataUnstaged=[], stdout) -> 74 | gitCmd 75 | args: ['ls-files', '-o', '--exclude-standard','-z'] 76 | cwd: repo.getWorkingDirectory() 77 | stdout: (data) -> 78 | stdout dataUnstaged.concat(_prettifyUntracked(data)) 79 | 80 | gitDiff = (repo, path, stdout) -> 81 | gitCmd 82 | args: ['diff', '-p', '-U1', path] 83 | cwd: repo.getWorkingDirectory() 84 | stdout: (data) -> stdout _prettifyDiff(data) 85 | 86 | gitRefresh = -> 87 | atom.project.getRepositories().forEach (r) -> r?.refreshStatus() 88 | gitCmd 89 | args: ['add', '--refresh', '--', '.'] 90 | stderr: (data) -> # don't really need to flash an error 91 | 92 | gitAdd = (repo, {file, stdout, stderr, exit, update}={}) -> 93 | args = ['add'] 94 | if update then args.push '--update' else args.push '--all' 95 | if file then args.push file else '.' 96 | exit ?= (code) -> 97 | if code is 0 98 | notifier.addSuccess "Added #{file ? 'all files'}" 99 | gitCmd 100 | args: args 101 | cwd: repo.getWorkingDirectory() 102 | stdout: stdout if stdout? 103 | stderr: stderr if stderr? 104 | exit: exit 105 | 106 | gitResetHead = (repo) -> 107 | gitCmd 108 | args: ['reset', 'HEAD'] 109 | cwd: repo.getWorkingDirectory() 110 | stdout: (data) -> 111 | notifier.addSuccess 'All changes unstaged' 112 | 113 | _getGitPath = -> 114 | p = atom.config.get('git-plus.gitPath') ? 'git' 115 | console.log "Git-plus: Using git at", p 116 | return p 117 | 118 | _prettify = (data) -> 119 | data = data.split('\0')[...-1] 120 | files = [] = for mode, i in data by 2 121 | {mode: mode, path: data[i+1]} 122 | 123 | _prettifyUntracked = (data) -> 124 | return [] if not data? 125 | data = data.split('\0')[...-1] 126 | files = [] = for file in data 127 | {mode: '?', path: file} 128 | 129 | _prettifyDiff = (data) -> 130 | data = data.split(/^@@(?=[ \-\+\,0-9]*@@)/gm) 131 | data[1..data.length] = ('@@' + line for line in data[1..]) 132 | data 133 | 134 | # Returns the working directory for a git repo. 135 | # Will search for submodule first if currently 136 | # in one or the project root 137 | # 138 | # @param andSubmodules boolean determining whether to account for submodules 139 | dir = (andSubmodules=true) -> 140 | new Promise (resolve, reject) -> 141 | if andSubmodules and submodule = getSubmodule() 142 | resolve(submodule.getWorkingDirectory()) 143 | else 144 | getRepo().then (repo) -> resolve(repo.getWorkingDirectory()) 145 | 146 | # returns filepath relativized for either a submodule or repository 147 | # otherwise just a full path 148 | relativize = (path) -> 149 | getSubmodule(path)?.relativize(path) ? atom.project.getRepositories()[0]?.relativize(path) ? path 150 | 151 | # returns submodule for given file or undefined 152 | getSubmodule = (path) -> 153 | path ?= atom.workspace.getActiveTextEditor()?.getPath() 154 | repo = atom.project.getRepositories().filter((r) -> 155 | r?.repo.submoduleForPath path 156 | )[0]?.repo?.submoduleForPath path 157 | 158 | # Public: Get the repository of the current file or project if no current file 159 | # Returns a {Promise} that resolves to a repository like object 160 | getRepo = -> 161 | new Promise (resolve, reject) -> 162 | getRepoForCurrentFile().then (repo) -> resolve(repo) 163 | .catch (e) -> 164 | repos = atom.project.getRepositories().filter (r) -> r? 165 | if repos.length is 0 166 | reject("No repos found") 167 | else if repos.length > 1 168 | resolve(new RepoListView(repos).result) 169 | else 170 | resolve(repos[0]) 171 | 172 | getRepoForCurrentFile = -> 173 | new Promise (resolve, reject) -> 174 | project = atom.project 175 | path = atom.workspace.getActiveTextEditor()?.getPath() 176 | directory = project.getDirectories().filter((d) -> d.contains(path))[0] 177 | if directory? 178 | project.repositoryForDirectory(directory).then (repo) -> 179 | submodule = repo.repo.submoduleForPath(path) 180 | if submodule? then resolve(submodule) else resolve(repo) 181 | .catch (e) -> 182 | reject(e) 183 | else 184 | reject "no current file" 185 | 186 | module.exports.cmd = gitCmd 187 | module.exports.stagedFiles = gitStagedFiles 188 | module.exports.unstagedFiles = gitUnstagedFiles 189 | module.exports.diff = gitDiff 190 | module.exports.refresh = gitRefresh 191 | module.exports.status = gitStatus 192 | module.exports.reset = gitResetHead 193 | module.exports.add = gitAdd 194 | module.exports.dir = dir 195 | module.exports.relativize = relativize 196 | module.exports.getSubmodule = getSubmodule 197 | module.exports.getRepo = getRepo 198 | -------------------------------------------------------------------------------- /lib/git-plus.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | git = require './git' 3 | GitPaletteView = require './views/git-palette-view' 4 | GitAdd = require './models/git-add' 5 | GitAddAllAndCommit = require './models/git-add-all-and-commit' 6 | GitAddAllCommitAndPush = require './models/git-add-all-commit-and-push' 7 | GitAddAndCommit = require './models/git-add-and-commit' 8 | GitBranch = require './models/git-branch' 9 | GitDeleteLocalBranch = require './models/git-delete-local-branch.coffee' 10 | GitDeleteRemoteBranch = require './models/git-delete-remote-branch.coffee' 11 | GitCheckoutAllFiles = require './models/git-checkout-all-files' 12 | GitCheckoutCurrentFile = require './models/git-checkout-current-file' 13 | GitCherryPick = require './models/git-cherry-pick' 14 | GitCommit = require './models/git-commit' 15 | GitCommitAmend = require './models/git-commit-amend' 16 | GitDiff = require './models/git-diff' 17 | GitDiffAll = require './models/git-diff-all' 18 | GitFetch = require './models/git-fetch' 19 | GitFetchPrune = require './models/git-fetch-prune.coffee' 20 | GitInit = require './models/git-init' 21 | GitLog = require './models/git-log' 22 | GitPull = require './models/git-pull' 23 | GitPush = require './models/git-push' 24 | GitRemove = require './models/git-remove' 25 | GitShow = require './models/git-show' 26 | GitStageFiles = require './models/git-stage-files' 27 | GitStageHunk = require './models/git-stage-hunk' 28 | GitStashApply = require './models/git-stash-apply' 29 | GitStashDrop = require './models/git-stash-drop' 30 | GitStashPop = require './models/git-stash-pop' 31 | GitStashSave = require './models/git-stash-save' 32 | GitStatus = require './models/git-status' 33 | GitTags = require './models/git-tags' 34 | GitUnstageFiles = require './models/git-unstage-files' 35 | GitRun = require './models/git-run' 36 | GitMerge = require './models/git-merge' 37 | 38 | module.exports = 39 | config: 40 | includeStagedDiff: 41 | title: 'Include staged diffs?' 42 | description: 'description' 43 | type: 'boolean' 44 | default: true 45 | openInPane: 46 | type: 'boolean' 47 | default: true 48 | description: 'Allow commands to open new panes' 49 | splitPane: 50 | title: 'Split pane direction (up, right, down, or left)' 51 | type: 'string' 52 | default: 'right' 53 | description: 'Where should new panes go? (Defaults to right)' 54 | wordDiff: 55 | type: 'boolean' 56 | default: true 57 | description: 'Should word diffs be highlighted in diffs?' 58 | amountOfCommitsToShow: 59 | type: 'integer' 60 | default: 25 61 | minimum: 1 62 | gitPath: 63 | type: 'string' 64 | default: 'git' 65 | description: 'Where is your git?' 66 | messageTimeout: 67 | type: 'integer' 68 | default: 5 69 | description: 'How long should success/error messages be shown?' 70 | 71 | subscriptions: new CompositeDisposable 72 | 73 | activate: (state) -> 74 | repos = atom.project.getRepositories().filter (r) -> r? 75 | if repos.length is 0 76 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:init', -> GitInit() 77 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:menu', -> new GitPaletteView() 78 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:add', -> git.getRepo().then((repo) -> GitAdd(repo)) 79 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:add-all', -> git.getRepo().then((repo) -> GitAdd(repo, addAll: true)) 80 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:commit', -> git.getRepo().then((repo) -> new GitCommit(repo)) 81 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:commit-all', -> git.getRepo().then((repo) -> new GitCommit(repo, stageChanges: true)) 82 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:commit-amend', -> git.getRepo().then((repo) -> new GitCommitAmend(repo)) 83 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:add-and-commit', -> git.getRepo().then((repo) -> GitAddAndCommit(repo)) 84 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:add-all-and-commit', -> git.getRepo().then((repo) -> GitAddAllAndCommit(repo)) 85 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:add-all-commit-and-push', -> git.getRepo().then((repo) -> GitAddAllCommitAndPush(repo)) 86 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:checkout', -> git.getRepo().then((repo) -> GitBranch.gitBranches(repo)) 87 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:checkout-remote', -> git.getRepo().then((repo) -> GitBranch.gitRemoteBranches(repo)) 88 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:checkout-current-file', -> git.getRepo().then((repo) -> GitCheckoutCurrentFile(repo)) 89 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:checkout-all-files', -> git.getRepo().then((repo) -> GitCheckoutAllFiles(repo)) 90 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:new-branch', -> git.getRepo().then((repo) -> GitBranch.newBranch(repo)) 91 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:delete-local-branch', -> git.getRepo().then((repo) -> GitDeleteLocalBranch(repo)) 92 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:delete-remote-branch', -> git.getRepo().then((repo) -> GitDeleteRemoteBranch(repo)) 93 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:cherry-pick', -> git.getRepo().then((repo) -> GitCherryPick(repo)) 94 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:diff', -> git.getRepo().then((repo) -> GitDiff(repo)) 95 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:diff-all', -> git.getRepo().then((repo) -> GitDiffAll(repo)) 96 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:fetch', -> git.getRepo().then((repo) -> GitFetch(repo)) 97 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:fetch-prune', -> git.getRepo().then((repo) -> GitFetchPrune(repo)) 98 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:pull', -> git.getRepo().then((repo) -> GitPull(repo)) 99 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:pull-using-rebase', -> git.getRepo().then((repo) -> GitPull(repo, rebase: true)) 100 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:push', -> git.getRepo().then((repo) -> GitPush(repo)) 101 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:remove', -> git.getRepo().then((repo) -> GitRemove(repo, showSelector: true)) 102 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:remove-current-file', -> git.getRepo().then((repo) -> GitRemove(repo)) 103 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:reset', -> git.getRepo().then((repo) -> git.reset(repo)) 104 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:show', -> git.getRepo().then((repo) -> GitShow(repo)) 105 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:log', -> git.getRepo().then((repo) -> GitLog(repo)) 106 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:log-current-file', -> git.getRepo().then((repo) -> GitLog(repo, onlyCurrentFile: true)) 107 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:stage-files', -> git.getRepo().then((repo) -> GitStageFiles(repo)) 108 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:unstage-files', -> git.getRepo().then((repo) -> GitUnstageFiles(repo)) 109 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:stage-hunk', -> git.getRepo().then((repo) -> GitStageHunk(repo)) 110 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:stash-save-changes', -> git.getRepo().then((repo) -> GitStashSave(repo)) 111 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:stash-pop', -> git.getRepo().then((repo) -> GitStashPop(repo)) 112 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:stash-apply', -> git.getRepo().then((repo) -> GitStashApply(repo)) 113 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:stash-delete', -> git.getRepo().then((repo) -> GitStashDrop(repo)) 114 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:status', -> git.getRepo().then((repo) -> GitStatus(repo)) 115 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:tags', -> git.getRepo().then((repo) -> GitTags(repo)) 116 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:run', -> git.getRepo().then((repo) -> new GitRun(repo)) 117 | @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:merge', -> git.getRepo().then((repo) -> GitMerge(repo)) 118 | 119 | deactivate: -> @subscriptions.dispose() 120 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### 5.4.7 4 | - #269 5 | 6 | ### 5.4.6 7 | - Refactor to fix #266 8 | 9 | ### 5.4.5 10 | - #265 11 | 12 | ### 5.4.4 13 | - #263 14 | 15 | ### 5.4.3 16 | - Add deactivate method to package 17 | - Refactoring 18 | 19 | ### 5.4.2 20 | - #261 21 | 22 | ### 5.4.1 23 | - #260: Destroy 'COMMIT_EDITMSG' pane not just editor 24 | 25 | ### 5.4.0 26 | - #201: Add `Commit All` command. Equivalent of `git commit -a` 27 | 28 | ### 5.3.5 29 | - #209: Only destroy textEditor for 'COMMIT_EDITMSG' 30 | 31 | ### 5.3.4 32 | - unlink COMMIT_EDITMSG file after commits 33 | - Respect no 'open pane' setting with commit window 34 | 35 | ### 5.3.3 36 | - #231: Shift-Enter confirms stage/unstage in dialogs 37 | 38 | ### 5.3.2 39 | - Fix #226: remove COMMIT_EDITMSG file from repo when committing 40 | - Fix #228: Don't show color codes in diff when `color.ui=always` 41 | 42 | ### 5.3.0 43 | - Fix #233 (@hotoiledgoblins) 44 | - Add 'Git checkout remote' to atom command palette 45 | - Respect `commit.template` config option 46 | 47 | ### 5.2.4 48 | - Fix #243 49 | - Fix #42 50 | - Add 'push' command to context menu 51 | 52 | ### 5.2.3 53 | - Make git-diff highlighting non-greedy. Thanks to @Victorystick 54 | 55 | ### 5.2.2 56 | 57 | - fix 'Git log current file' 58 | 59 | ### 5.2.1 60 | 61 | - add support for Git pull using rebase (@maxcnunes) 62 | - Git diff opens panes with respect to the 'open in pane' setting 63 | - Commit and diff won't explode if you don't have the spit panes option selected 64 | 65 | ### 5.1.7 66 | 67 | - Git log command now works with submodules and different repos 68 | - new command: `Remote Checkout` 69 | 70 | ### 5.1.2 71 | 72 | - #206: Fix for commit file syntax highlighting not working sometimes. (@Gwasanaethau) 73 | 74 | ### 5.1.1 75 | 76 | - Fix for commands not working in submodules 77 | - Fix typos with 'Git Fetch Prune' (@Azakur4) 78 | 79 | ### 5.1.0 80 | 81 | - The Split Pane direction setting actually works now. 82 | > Possible choices are [right up down left]. Defaults to right. 83 | 84 | ### 5.0.7 85 | 86 | - Fix #199 87 | - Fix #198 88 | - Fix #197 89 | 90 | ### 5.0.4 91 | 92 | - Fix typo of 'notifer' to 'notifier' 93 | - Fix issue #139 94 | 95 | ### 5.0.3 96 | 97 | - Treeview and StatusBar should update after git commands 98 | - No longer opening blank file on `Git show` if given an invalid object 99 | 100 | ### 5.0.2 101 | 102 | - Fix typo of 'notifer' to 'notifier' 103 | - Brought back the `messageTimeout` setting for remaining StatusViews 104 | 105 | ### 5.0.1 106 | 107 | - Major release to be compatible with atom 1.0.0 108 | - If a window has more than one project with a git repository and a command is attempted, 109 | then you can choose which repo to manipulate. 110 | - New layout for commits in `Git log` command 111 | - Most StatusViews of command output have been moved to the new notificaton system in atom 112 | 113 | ### 4.5.0 114 | 115 | - Remove some more deprecations (@Azakur4) 116 | - New command `Git Add All Commit And Push` (@TwanoO67) 117 | 118 | ### 4.4.13 119 | 120 | - bug fix for those using 1.0.0 preview 121 | 122 | ### 4.4.12 123 | 124 | - bug fix, issue #175 125 | 126 | ### 4.4.11 127 | 128 | - Remove deprecated api code 129 | - Add keywords to package.json 130 | - Fix refreshing git status after commands to update ui 131 | - Remove 'emissary' module because it does not work in helping Status and Output views listen for global events 132 | 133 | ### 4.4.10 134 | 135 | - Remove uses of `atom.project.getRepo()` 136 | 137 | ### 4.4.9 138 | 139 | - Refactoring 140 | - Fixes issue #173 141 | 142 | ### 4.4.8 143 | 144 | - Proper fix for GitRepository trying to refresh on window focus by setting `refreshOnWindowFocus` to false 145 | 146 | ### 4.4.7 147 | 148 | - Update style selectors for diff highlighting 149 | 150 | ### 4.4.6 151 | 152 | - Try to keep only one instance of GitRepository floating around by using either 153 | `atom.project.getRepo` or calling `::destroy` on an opened instance 154 | 155 | ### 4.4.2 156 | 157 | - Gracefully handle `Git not found error` thanks to @TrangPham. 158 | - Fix for files not opening when selected from status list 159 | 160 | ### 4.4.1 161 | 162 | - Fix for `Git status` not opening selected file when accessed outside of repo. 163 | - Fix for some commands working after second time they are selected 164 | 165 | ### 4.4.0 166 | 167 | - Many internal upgrades to keep up with atom 1.0.0 api 168 | - Commands can now be run from the Git-plus palette for files in other repos outside of the current project. 169 | - This means you can open a directory of multiple Git repositories and work with individual repos while in the same project. 170 | 171 | ### 4.3.8 172 | 173 | - minor 174 | 175 | ### 4.3.7 176 | 177 | - More api upgrades 178 | - No longer showing git commands in regular command palette when project is not a repo 179 | 180 | ### 4.3.6 181 | 182 | - Making changes to follow the api for atom 1.0.0 183 | 184 | ### 4.3.5 185 | 186 | - Update css selectors and keymappings with new atom API standards 187 | 188 | ### 4.3.2 189 | 190 | - Fix for `Checkout new branch` 191 | 192 | ### 4.3.1 193 | 194 | - `Git Show` can be cancelled with escape 195 | 196 | ### 4.3.0 197 | 198 | - Confirm on `Git Remove` 199 | 200 | ### 4.2.6 201 | 202 | - Handle case of no available panes after saving commit message 203 | 204 | ### 4.2.5 205 | 206 | - Handle case of no available panes after closing commit message pane 207 | 208 | ### 4.2.4 209 | 210 | - Minor patch 211 | 212 | ### 4.2.3 213 | 214 | - Temporary fix for `Git Pull` issue on yosemite mac's thanks to @Azakur4. 215 | 216 | ### 4.2.2 217 | 218 | - Remove hyphenated package name in menu 219 | 220 | ### 4.2.1 221 | 222 | - Small fix in git-commit.coffee line 90 where promise returns a TextBuffer. Using given TextBuffer for subscriptions 223 | rather than the 'buffer' property in the TextBuffer. 224 | 225 | ### 4.2.0 226 | 227 | - New Git merge feature thanks to @herlon214 228 | 229 | ### 4.1.2 230 | 231 | - Using new atom api for configs and subscribing to make it easier for moving forward and maintenance 232 | 233 | ### 4.1.1 234 | 235 | - Fix issue of commit tab not opening 236 | - Still need to remove dependency on Theorist 237 | 238 | ### 4.1.0 239 | 240 | - Return of git-plus command palette 241 | 242 | ### 4.0.0 243 | 244 | - THIS IS THE LAST PUSH OF NEW FEATURES. I'm stopping development of this package because I don't have time and on top of that, I don't use atom anymore 245 | - Adding new command called 'Git Run'. This allows you to run git commands like in the command line. i.e. `add --all .` or `clone git@github.com:akonwi/git-plus my-git-plus` 246 | - Removed Git-Plus command palette and merged all commands into atom command palette 247 | - all commands are now accessible via keymappings 248 | - Add setting to change message display time in seconds 249 | 250 | ### 3.10.4 251 | 252 | - Fix for object names being shortened unnecessarily. 253 | 254 | ### 3.10.3 255 | 256 | - Fix for branch names being shortened unnecessarily. 257 | 258 | ### 3.10.2 259 | 260 | - Fix 'Git Log' for windows users 261 | 262 | ### 3.10.1 263 | 264 | - Git pull lists remotes if there are multiple and remote branches 265 | 266 | ### 3.9.0 267 | 268 | - From the Git Status list, you can go to the modified file or open its diff file 269 | ### 3.8.0 270 | 271 | - Adding commands for Git stash 272 | 273 | ### 3.7.0 274 | 275 | - new `Reset HEAD` allows unstaging all changes 276 | 277 | ### 3.6.2 278 | 279 | - Patch to resolve when atom project is a subfolder of a repository 280 | 281 | ### 3.6.1 282 | 283 | - Can change commentchar in Git configs and Git-plus will use it in commit messages 284 | 285 | ### 3.6.0 286 | - Can now push tags to remote 287 | 288 | ### 3.5.0 289 | 290 | - The more common commands are now accessible through keybindings 291 | 292 | * Add 293 | * Add all and commit 294 | * Add and commit 295 | * Commit 296 | * Diff [all] 297 | * Log 298 | * Status 299 | * Pull 300 | * Push 301 | 302 | ### 3.4.0 303 | 304 | - Debut of submodule support by the plugin. 305 | 306 | - Submodule commands should be run while a submodule file is the current file 307 | 308 | ### 3.3.2 309 | 310 | - Fix for not being able to commit on windows 311 | 312 | ### 3.3.0 313 | 314 | - New setting to specify where to open the pane for commits and such... 315 | 316 | ### 3.1.0 317 | 318 | - Git-palette doesn't show 'Git-plus:' prefix in front of commands. 319 | 320 | - Add `diff`, `diff all`, `log`, to startup commands in regular command palette 321 | 322 | ### 3.0.2 323 | 324 | - Should be able to close the views with feedback from commands through the `core:cancel` command. 325 | 326 | ### 3.0.0 327 | #### Includes massive amounts of refactoring to internal api 328 | 329 | - Dedicated command palette for git commands. Can be opened with 330 | `[cmd|ctrl]-shift-h` 331 | 332 | - `Git init` is available in projects that are not git repos. 333 | 334 | - Stage/Unstage multiple files at a time. 335 | 336 | - Stage individual hunks of a changed file. 337 | 338 | - `Git checkout all files` 339 | 340 | - Cherry pick commits. 341 | 342 | - Can also set the path to git in settings if it isn't in PATH. 343 | 344 | ### 2.11.3 345 | 346 | - handling null results of getRepo() 347 | 348 | ### 2.11.2 349 | 350 | - Fix hide-output key mapping 351 | 352 | ### 2.11.1 353 | 354 | - Minor fix, changing a call of `@pushTo` to `@execute` 355 | 356 | ### 2.11.0 357 | 358 | - Add hide-output keymapping 359 | 360 | ### 2.10.1 361 | 362 | - Fix for missing fuzzaldrin reference 363 | 364 | ### 2.10.0 365 | 366 | - `Git remove` 367 | 368 | ### 2.9.0 369 | 370 | - `Git fetch` 371 | 372 | ### 2.8.0 373 | 374 | - `Git log` 375 | Can also configure how many commits to show in log 376 | 377 | - `Git show` commits of current file 378 | 379 | - Tree-view gets refreshed after things 380 | 381 | - Polish up git tips in commit message 382 | --------------------------------------------------------------------------------