branch_cli 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/bin/branch +4 -0
- data/branch_cli.gemspec +24 -0
- data/lib/branch_cli.rb +30 -0
- data/lib/branch_cli/branch.rb +196 -0
- data/lib/branch_cli/commit.rb +55 -0
- data/lib/branch_cli/main.rb +36 -0
- data/lib/branch_cli/options.rb +58 -0
- data/lib/branch_cli/print.rb +5 -0
- data/lib/branch_cli/run.rb +19 -0
- data/lib/branch_cli/string.rb +38 -0
- data/lib/branch_cli/swift_compatibility.rb +70 -0
- data/screenshot.png +0 -0
- data/spec/integration/help_spec.rb +8 -0
- data/spec/integration/status_spec.rb +29 -0
- data/spec/integration/switch_branch_spec.rb +57 -0
- data/spec/integration/version_spec.rb +9 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/support/cli.rb +46 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4b756cb09351d8216b599d29aa0d6ee7cccc79f2
|
4
|
+
data.tar.gz: 122054773404b0c5e5381c9c43b1d72df359e9b6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 16968f525b1cc7177c2afe372e962462b26f5798e8e66bceec21110ddd2b4b97ebd7b3862cb6a16d01a3b5bef0eeeaaabab655dd86444dcea8227f10c1c587e0
|
7
|
+
data.tar.gz: a3b917ad7dbadf14a6840fa6c7ab9bbb476db48a6a4793161e4ad97b7b1ba95ec0d9c0b8bbb4a371d5a9c0d7dc8a62f479dd0a18ec919b9bb048344afa3f0a2b
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
spec/examples.txt
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
branch_cli (0.7.0)
|
5
|
+
binding_of_caller (~> 0.7.2)
|
6
|
+
formatador (~> 0.2.5)
|
7
|
+
inquirer (~> 0.2.1)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
binding_of_caller (0.7.2)
|
13
|
+
debug_inspector (>= 0.0.1)
|
14
|
+
coderay (1.1.1)
|
15
|
+
debug_inspector (0.0.3)
|
16
|
+
diff-lcs (1.3)
|
17
|
+
formatador (0.2.5)
|
18
|
+
inquirer (0.2.1)
|
19
|
+
term-ansicolor (>= 1.2.2)
|
20
|
+
method_source (0.8.2)
|
21
|
+
pry (0.10.4)
|
22
|
+
coderay (~> 1.1.0)
|
23
|
+
method_source (~> 0.8.1)
|
24
|
+
slop (~> 3.4)
|
25
|
+
rspec (3.6.0)
|
26
|
+
rspec-core (~> 3.6.0)
|
27
|
+
rspec-expectations (~> 3.6.0)
|
28
|
+
rspec-mocks (~> 3.6.0)
|
29
|
+
rspec-core (3.6.0)
|
30
|
+
rspec-support (~> 3.6.0)
|
31
|
+
rspec-expectations (3.6.0)
|
32
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
33
|
+
rspec-support (~> 3.6.0)
|
34
|
+
rspec-mocks (3.6.0)
|
35
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
36
|
+
rspec-support (~> 3.6.0)
|
37
|
+
rspec-support (3.6.0)
|
38
|
+
slop (3.6.0)
|
39
|
+
term-ansicolor (1.6.0)
|
40
|
+
tins (~> 1.0)
|
41
|
+
tins (1.14.0)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
branch_cli!
|
48
|
+
pry (~> 0.10.4)
|
49
|
+
rspec (~> 3.6)
|
50
|
+
|
51
|
+
BUNDLED WITH
|
52
|
+
1.14.6
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Daniel Inkpen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# branch cli
|
2
|
+
|
3
|
+
`branch` simplifies the average workflow of Git. By working on simple assumptions of workflow, it's easier, more memorable, quicker and safer to use.
|
4
|
+
|
5
|
+
The assumptions `branch` makes are:
|
6
|
+
- You don't care about staging/unstaging files. All changes are treated as staged.
|
7
|
+
- You use a single remote (origin) and your local "my-branch" is always going to have the upstream as "origin/my-branch"
|
8
|
+
|
9
|
+

|
10
|
+
|
11
|
+
## Basic usage
|
12
|
+
|
13
|
+
- Use `branch` as an alternative for `git status`
|
14
|
+
- to change branch, use `branch BRANCH-NAME`
|
15
|
+
- To get a list of the most recent branches `branch --list`
|
16
|
+
|
17
|
+
## Features
|
18
|
+
|
19
|
+
- When changing branch
|
20
|
+
- Warns if you have local changes and prompts on whether to continue
|
21
|
+
- Warns if the remote branch has diverged and prompts on whether you want to keep your local branch or to reset with the remote
|
22
|
+
- Formatted list of most recent branches with `--list` argument
|
23
|
+
|
24
|
+
## Future ideas/plans
|
25
|
+
|
26
|
+
- Recording base branch when creating a new one
|
27
|
+
- Suggestion to rebase if it detects that you should (i.e. automatic `git pull --rebase` and `git rebase origin/base-branch`)
|
28
|
+
- Nicer flow for rebasing (visual representation of progress)
|
29
|
+
- clear indication of files in conflict (as well as automatic indication of when you have resolved them)
|
data/bin/branch
ADDED
data/branch_cli.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = 'branch_cli'
|
6
|
+
gem.version = '0.7.0'
|
7
|
+
gem.authors = ['Daniel Inkpen']
|
8
|
+
gem.email = ['dan2552@gmail.com']
|
9
|
+
gem.description = %q{Faster, safer git branching.}
|
10
|
+
gem.summary = %q{Branch aims to simplify a developer's daily workflow of Git. It is in no means supposed to replace Git, but provide a quicker and easier way to do some more common functions (with more memorable commands). Branch is pretty opinionated in the way it does things (i.e. I don't care about staging/unstaging files, I just want all of my current changes in a single bucket). It also makes the assumption that you use a single remote (origin) and your local "my-branch" is always going to have the upstream as "origin/my-branch" (i.e. it fits with the most common of workflows).}
|
11
|
+
gem.homepage = 'https://github.com/Dan2552/branch'
|
12
|
+
gem.license = 'MIT'
|
13
|
+
|
14
|
+
gem.add_dependency "formatador", '~> 0.2.5'
|
15
|
+
gem.add_dependency "inquirer", '~> 0.2.1'
|
16
|
+
gem.add_dependency "binding_of_caller", '~> 0.7.2'
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec', '~> 3.6'
|
19
|
+
gem.add_development_dependency 'pry', '~> 0.10.4'
|
20
|
+
|
21
|
+
gem.files = `git ls-files`.split($/)
|
22
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
23
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
24
|
+
end
|
data/lib/branch_cli.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module BranchCli
|
3
|
+
def self.root
|
4
|
+
File.dirname __dir__
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'open3'
|
9
|
+
|
10
|
+
path = File.expand_path('../../lib/branch_cli', __FILE__)
|
11
|
+
$LOAD_PATH.unshift path
|
12
|
+
|
13
|
+
require 'formatador'
|
14
|
+
require 'inquirer'
|
15
|
+
require 'binding_of_caller'
|
16
|
+
|
17
|
+
require 'swift_compatibility'
|
18
|
+
|
19
|
+
require 'branch'
|
20
|
+
require 'commit'
|
21
|
+
require 'options'
|
22
|
+
require 'print'
|
23
|
+
require 'run'
|
24
|
+
require 'string'
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'main'
|
28
|
+
rescue Interrupt
|
29
|
+
exit 1
|
30
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
class Branch < SwiftStruct
|
2
|
+
let name = nil
|
3
|
+
|
4
|
+
def origin
|
5
|
+
return "origin/#{name}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def getCurrentBranch
|
10
|
+
let result = runCommand("git symbolic-ref HEAD")
|
11
|
+
let matches = result.stdout.matches(forRegex: "heads\\/(.*)")
|
12
|
+
if matches.count == 0
|
13
|
+
return nil
|
14
|
+
else
|
15
|
+
return Branch.new(name: matches[0])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def setCurrentBranch(branch)
|
20
|
+
prettyPrint("Switching to branch #{branch.name.s.Bold}...")
|
21
|
+
fetch
|
22
|
+
detectChanges
|
23
|
+
resetLocal
|
24
|
+
switchBranch(branch)
|
25
|
+
detectAhead
|
26
|
+
resetToOrigin
|
27
|
+
end
|
28
|
+
|
29
|
+
def detectChanges
|
30
|
+
let status = gitStatus
|
31
|
+
if status.contains("to be committed") || status.contains("for commit:") || status.contains("Untracked files:")
|
32
|
+
uncommitedChanges
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def resetLocal
|
37
|
+
runCommand("git reset --hard")
|
38
|
+
end
|
39
|
+
|
40
|
+
def switchBranch(branch)
|
41
|
+
runCommand("git checkout #{branch.name}")
|
42
|
+
|
43
|
+
if getCurrentBranch.name != branch.name
|
44
|
+
runCommand("git checkout -b #{branch.name}")
|
45
|
+
end
|
46
|
+
|
47
|
+
runCommand("git branch --set-upstream-to=origin/#{branch.name}")
|
48
|
+
|
49
|
+
if getCurrentBranch.name != branch.name
|
50
|
+
prettyPrint("🤔 Failed to switch branch".f.Red)
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def detectAhead
|
56
|
+
let status = gitStatus
|
57
|
+
if status.contains("can be fast-forwarded.") || status.contains("is ahead of 'origin") || status.contains(" have diverged")
|
58
|
+
branchesDiverged
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# On branch master
|
63
|
+
# Your branch and 'origin/master' have diverged,
|
64
|
+
# and have 1 and 1 different commit each, respectively.
|
65
|
+
# (use "git pull" to merge the remote branch into yours)
|
66
|
+
# nothing to commit, working directory clean
|
67
|
+
#
|
68
|
+
# On branch master
|
69
|
+
# Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
|
70
|
+
# (use "git pull" to update your local branch)
|
71
|
+
# nothing to commit, working directory clean
|
72
|
+
#
|
73
|
+
# On branch master
|
74
|
+
# Your branch is ahead of 'origin/master' by 1 commit.
|
75
|
+
# (use "git push" to publish your local commits)
|
76
|
+
# nothing to commit, working directory clean
|
77
|
+
def branchesDiverged
|
78
|
+
prettyPrint("\n😱 You appear to have a diverged branch:".f.Red)
|
79
|
+
let matches = gitStatus.matches(forRegex: "(Your branch .*\\s*.*)")
|
80
|
+
|
81
|
+
matches.each do |match|
|
82
|
+
prettyPrint(match)
|
83
|
+
end
|
84
|
+
|
85
|
+
promptKeepLocal
|
86
|
+
end
|
87
|
+
|
88
|
+
def resetToOrigin
|
89
|
+
let origin = getCurrentBranch.origin
|
90
|
+
let reset = runCommand("git reset --hard #{origin}")
|
91
|
+
|
92
|
+
if reset.exitStatus == 0
|
93
|
+
prettyPrint("Using remote branch".f.Green)
|
94
|
+
else
|
95
|
+
prettyPrint("Using local branch (no origin branch found)".f.Green)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def printCurrentBranch
|
100
|
+
let branchName = getCurrentBranch&.name || "no branch"
|
101
|
+
prettyPrint("On branch #{branchName.s.Bold}")
|
102
|
+
end
|
103
|
+
|
104
|
+
def printRecentBranches
|
105
|
+
let command = runCommand("git for-each-ref --sort=-committerdate --format=\"%(refname)\" --count=30 refs/heads/ refs/remotes")
|
106
|
+
let references = command.stdout.components(separatedBy: "\n")
|
107
|
+
|
108
|
+
commits = references.map do |r|
|
109
|
+
Commit.new(message: "", sha: r.clearQuotes)
|
110
|
+
end
|
111
|
+
|
112
|
+
strings = commits.map do |c|
|
113
|
+
c.printableFormat("%Cgreen%cr%Creset %C(yellow)%d%Creset %C(bold blue)<%an>%Creset%n")
|
114
|
+
end
|
115
|
+
var lastStr = ""
|
116
|
+
strings.each do |str|
|
117
|
+
if str != lastStr
|
118
|
+
prettyPrint(str) # avoid dups
|
119
|
+
end
|
120
|
+
lastStr = str
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def uncommitedChanges
|
125
|
+
prettyPrint("\n😱 You appear to have uncommited changes:".f.Red)
|
126
|
+
printGitStatus
|
127
|
+
promptContinueAnyway
|
128
|
+
end
|
129
|
+
|
130
|
+
def gitStatus
|
131
|
+
return runCommand("git status").stdout
|
132
|
+
end
|
133
|
+
|
134
|
+
def printGitStatus(preceedingNewline: false)
|
135
|
+
let diff = gitStatus.matches(forRegex: "\t([a-z ]*:.*)")
|
136
|
+
if diff.count > 0 && preceedingNewline
|
137
|
+
prettyPrint("")
|
138
|
+
end
|
139
|
+
|
140
|
+
diff.each do |line|
|
141
|
+
prettyPrint("\t#{line}".f.Green)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def promptContinueAnyway
|
146
|
+
puts ""
|
147
|
+
response = Ask.list "Continue anyway? Changes will be lost", [
|
148
|
+
"Stop",
|
149
|
+
"Continue"
|
150
|
+
]
|
151
|
+
|
152
|
+
exit(1) if response == 0
|
153
|
+
end
|
154
|
+
|
155
|
+
def promptKeepLocal
|
156
|
+
let choice: String
|
157
|
+
let options = Options.sharedInstance
|
158
|
+
|
159
|
+
if options.preferLocal
|
160
|
+
choice = "local"
|
161
|
+
elsif options.preferRemote
|
162
|
+
choice = "remote"
|
163
|
+
else
|
164
|
+
puts ""
|
165
|
+
response = Ask.list "Keep remote or local copy?", [
|
166
|
+
"Remote",
|
167
|
+
"Local"
|
168
|
+
]
|
169
|
+
|
170
|
+
choice = "remote" if response == 0
|
171
|
+
choice = "local" if response == 1
|
172
|
+
end
|
173
|
+
|
174
|
+
if choice != "remote"
|
175
|
+
prettyPrint("Using local branch (user specified)")
|
176
|
+
exit(0)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def addAll
|
181
|
+
runCommand("git reset --mixed")
|
182
|
+
runCommand("git add . -A")
|
183
|
+
end
|
184
|
+
|
185
|
+
def fetch
|
186
|
+
let fetch = runCommand("git fetch")
|
187
|
+
let error = fetch.stderr
|
188
|
+
if error.contains("No remote repository specified")
|
189
|
+
prettyPrint("\n⚠️ No remote repository is setup\n".f.Yellow)
|
190
|
+
return
|
191
|
+
end
|
192
|
+
if fetch.exitStatus != 0
|
193
|
+
prettyPrint("\n🤔 Failed to fetch.".f.Red)
|
194
|
+
exit(1)
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Commit < SwiftStruct
|
2
|
+
var message = nil
|
3
|
+
var sha = nil
|
4
|
+
|
5
|
+
def initialize(message: String, sha: String)
|
6
|
+
self.message = message.clearQuotes
|
7
|
+
self.sha = sha.clearQuotes
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.getCurrentHead
|
11
|
+
return Commit.from(identifier: "HEAD")
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from(identifier: String)
|
15
|
+
return Commit.new(
|
16
|
+
message: message(forIdentifier: identifier),
|
17
|
+
sha: sha(forIdentifier: identifier)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def commitsLeading(commit: Commit)
|
22
|
+
let run = runCommand("git rev-list #{sha}..#{commit.sha} --reverse").stdout
|
23
|
+
var commits = [] # [Commit]
|
24
|
+
let shas = run.components(separatedBy: "\n")
|
25
|
+
shas.each do |sha|
|
26
|
+
commits.append(Commit.from(identifier: sha))
|
27
|
+
end
|
28
|
+
return commits
|
29
|
+
end
|
30
|
+
|
31
|
+
def mostRecentCommonAncestor(commit: Commit)
|
32
|
+
let mergeBase = runCommand("git merge-base #{sha} #{commit.sha}").stdout
|
33
|
+
return Commit.from(identifier: mergeBase)
|
34
|
+
end
|
35
|
+
|
36
|
+
def printableFormat(format)
|
37
|
+
let command = runCommand("git", args: [
|
38
|
+
"log",
|
39
|
+
"-n1",
|
40
|
+
sha,
|
41
|
+
"--format=\"#{format}\""
|
42
|
+
])
|
43
|
+
return command.stdout.gsub("\n", "")
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.sha(identifier: String)
|
49
|
+
return runCommand("git log -1 #{identifier} --format=\"%H\"").stdout
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.message(identifier: String)
|
53
|
+
return runCommand("git log -1 #{identifier} --format=\"%s\"").stdout
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
def main(arguments)
|
2
|
+
Options.reset
|
3
|
+
let options = Options.sharedInstance
|
4
|
+
options.loadOptions(arguments: arguments)
|
5
|
+
|
6
|
+
if options.isShowVersion
|
7
|
+
spec = Gem::Specification::load("#{BranchCli.root}/branch_cli.gemspec")
|
8
|
+
prettyPrint("branch cli #{spec.version}")
|
9
|
+
exit(0)
|
10
|
+
end
|
11
|
+
|
12
|
+
if options.isShowList
|
13
|
+
printRecentBranches
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
if options.isHelp
|
18
|
+
prettyPrint("usage: branch BRANCH-NAME [ARGS]")
|
19
|
+
prettyPrint("")
|
20
|
+
prettyPrint("--version | -v \tShows the current version")
|
21
|
+
prettyPrint("--verbose \t\tPrints all the git commands as they run")
|
22
|
+
prettyPrint("--list | -l \t\tPrints the most recently updated branches")
|
23
|
+
prettyPrint("--prefer=PREFERENCE \tWhere PREFERENCE is local or remote, will use the set preference rather than ask")
|
24
|
+
prettyPrint("--help | help \tShows this help")
|
25
|
+
exit(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
addAll
|
29
|
+
|
30
|
+
if options.isBranchSupplied
|
31
|
+
setCurrentBranch(Branch.new(name: options.suppliedBranch))
|
32
|
+
else
|
33
|
+
printCurrentBranch
|
34
|
+
printGitStatus(preceedingNewline: true)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Options < SwiftObject
|
2
|
+
def self.sharedInstance
|
3
|
+
@sharedInstance ||= Options.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.reset
|
7
|
+
@sharedInstance = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
var isVerbose = false
|
11
|
+
var isShowVersion = false
|
12
|
+
var suppliedBranch = nil
|
13
|
+
var isHelp = false
|
14
|
+
var isTestRebase = false
|
15
|
+
var isShowList = false
|
16
|
+
var preferLocal = false
|
17
|
+
var preferRemote = false
|
18
|
+
|
19
|
+
def isBranchSupplied
|
20
|
+
return suppliedBranch != nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def loadOptions(arguments:)
|
24
|
+
if arguments.contains("help") || arguments.contains("--help")
|
25
|
+
self.isHelp = true
|
26
|
+
end
|
27
|
+
|
28
|
+
if arguments.contains("--verbose")
|
29
|
+
self.isVerbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
if arguments.contains("-v") || arguments.contains("--version")
|
33
|
+
self.isShowVersion = true
|
34
|
+
end
|
35
|
+
|
36
|
+
if arguments.contains("--test-rebase")
|
37
|
+
self.isTestRebase = true
|
38
|
+
end
|
39
|
+
|
40
|
+
if arguments.contains("--list") || arguments.contains("-l")
|
41
|
+
self.isShowList = true
|
42
|
+
end
|
43
|
+
|
44
|
+
if arguments.contains("--prefer=local")
|
45
|
+
self.preferLocal = true
|
46
|
+
end
|
47
|
+
|
48
|
+
if arguments.contains("--prefer=remote")
|
49
|
+
self.preferRemote = true
|
50
|
+
end
|
51
|
+
|
52
|
+
if arguments.count > 0
|
53
|
+
if !arguments[0].hasPrefix("-")
|
54
|
+
self.suppliedBranch = arguments[0]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class RunResult < SwiftStruct
|
2
|
+
let stdout = nil
|
3
|
+
let stderr = nil
|
4
|
+
let exitStatus = nil
|
5
|
+
end
|
6
|
+
|
7
|
+
def runCommand(command, args: [])
|
8
|
+
command_with_args = "#{command} #{args.join(" ")}"
|
9
|
+
|
10
|
+
if Options.sharedInstance.isVerbose
|
11
|
+
prettyPrint(command_with_args.f.Blue)
|
12
|
+
end
|
13
|
+
|
14
|
+
Open3.popen3(command_with_args) do |stdin, stdout, stderr, wait_thr|
|
15
|
+
RunResult.new(stdout: stdout.read,
|
16
|
+
stderr: stderr.read,
|
17
|
+
exitStatus: wait_thr.value.to_i)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class String
|
2
|
+
def matches(forRegex:)
|
3
|
+
lines = split("\n")
|
4
|
+
lines.map { |l| l.match(forRegex)&.captures }.flatten.compact
|
5
|
+
end
|
6
|
+
|
7
|
+
def clearQuotes
|
8
|
+
gsub("\"", with: "")
|
9
|
+
end
|
10
|
+
|
11
|
+
def s
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def Bold
|
16
|
+
"[bold]#{self}[/]"
|
17
|
+
end
|
18
|
+
|
19
|
+
def f
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def Green
|
24
|
+
"[green]#{self}[/]"
|
25
|
+
end
|
26
|
+
|
27
|
+
def Blue
|
28
|
+
"[blue]#{self}[/]"
|
29
|
+
end
|
30
|
+
|
31
|
+
def Red
|
32
|
+
"[red]#{self}[/]"
|
33
|
+
end
|
34
|
+
|
35
|
+
def Yellow
|
36
|
+
"[yellow]#{self}[/]"
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Object
|
2
|
+
def let(*args)
|
3
|
+
end
|
4
|
+
|
5
|
+
def var(*args)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class SwiftObject
|
10
|
+
def self.let(*args)
|
11
|
+
bind = binding.of_caller(1)
|
12
|
+
bind.local_variables.each do |v|
|
13
|
+
attr_reader(v)
|
14
|
+
|
15
|
+
variable_defaults[v] = bind.local_variable_get(v)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.var(*args)
|
20
|
+
bind = binding.of_caller(1)
|
21
|
+
bind.local_variables.each do |v|
|
22
|
+
attr_reader(v)
|
23
|
+
attr_writer(v)
|
24
|
+
|
25
|
+
variable_defaults[v] = bind.local_variable_get(v)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.variable_defaults
|
30
|
+
@variable_defaults ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
params = self.class.variable_defaults
|
35
|
+
|
36
|
+
params.each do |key, value|
|
37
|
+
instance_variable_set("@#{key}", value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class SwiftStruct < SwiftObject
|
43
|
+
def initialize(params = {})
|
44
|
+
params = self.class.variable_defaults.merge!(params)
|
45
|
+
|
46
|
+
params.each do |key, value|
|
47
|
+
instance_variable_set("@#{key}", value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Array
|
53
|
+
def contains(*args)
|
54
|
+
include?(*args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class String
|
59
|
+
def contains(*args)
|
60
|
+
include?(*args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def hasPrefix(str)
|
64
|
+
start_with?(str)
|
65
|
+
end
|
66
|
+
|
67
|
+
def components(separatedBy:)
|
68
|
+
split(separatedBy)
|
69
|
+
end
|
70
|
+
end
|
data/screenshot.png
ADDED
Binary file
|
@@ -0,0 +1,29 @@
|
|
1
|
+
RSpec.describe "status" do
|
2
|
+
subject { execute(args) }
|
3
|
+
let(:args) { [] }
|
4
|
+
|
5
|
+
it "prints the current branch" do
|
6
|
+
expect_output /On branch .*master/
|
7
|
+
end
|
8
|
+
|
9
|
+
context "when there are changed files" do
|
10
|
+
before do
|
11
|
+
touch "awholenewfile"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "prints out the files" do
|
15
|
+
expect_output /awholenewfile/
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when there are committed files" do
|
20
|
+
before do
|
21
|
+
touch "awholenewfile"
|
22
|
+
commit
|
23
|
+
end
|
24
|
+
|
25
|
+
it "does not print committed files" do
|
26
|
+
expect_to_not_output "awholenewfile"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
RSpec.describe "switching branch" do
|
2
|
+
subject { execute(args) }
|
3
|
+
let(:args) { %w{new-branch} }
|
4
|
+
|
5
|
+
it "prints that it's switching branch" do
|
6
|
+
expect_output /Switching to branch .*new-branch/
|
7
|
+
end
|
8
|
+
|
9
|
+
it "switches the branch" do
|
10
|
+
subject
|
11
|
+
expect_branch "new-branch"
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when the branch doesn't exist on remote" do
|
15
|
+
it "prints that it's using local branch" do
|
16
|
+
expect_output /Using local branch \(no origin branch found\)/
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when the working copy has changes" do
|
21
|
+
before { touch("changes") }
|
22
|
+
|
23
|
+
it "asks whether to discard changes" do
|
24
|
+
expect(Ask).to receive(:list)
|
25
|
+
.with("Continue anyway? Changes will be lost", ["Stop", "Continue"])
|
26
|
+
subject
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when the branch exists on remote" do
|
31
|
+
let(:args) { %w{spec} }
|
32
|
+
before { clone_remote_repo }
|
33
|
+
|
34
|
+
context "when the local branch matches the remote" do
|
35
|
+
let(:args) { %w{master} }
|
36
|
+
before { execute %w{spec --prefer=remote} }
|
37
|
+
|
38
|
+
it "prints that it's using the remote branch" do
|
39
|
+
expect_output /Using remote branch/
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when the local branch is diverged from the remote" do
|
44
|
+
before do
|
45
|
+
touch "changes"
|
46
|
+
commit
|
47
|
+
end
|
48
|
+
let(:args) { %w{master} }
|
49
|
+
|
50
|
+
it "asks whether to use the remote or local" do
|
51
|
+
expect(Ask).to receive(:list)
|
52
|
+
.with("Keep remote or local copy?", ["Remote", "Local"])
|
53
|
+
subject
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
RSpec.describe "Version" do
|
2
|
+
subject { execute(args) }
|
3
|
+
let(:args) { %w{--version} }
|
4
|
+
|
5
|
+
it "prints out the version number of the release" do
|
6
|
+
spec = Gem::Specification::load("#{BranchCli.root}/branch_cli.gemspec")
|
7
|
+
expect_output /branch cli #{spec.version}/
|
8
|
+
end
|
9
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'pry'
|
3
|
+
require 'branch_cli'
|
4
|
+
require_relative 'support/cli'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
# rspec-expectations config goes here. You can use an alternate
|
8
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
9
|
+
# assertions if you prefer.
|
10
|
+
config.expect_with :rspec do |expectations|
|
11
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
12
|
+
# and `failure_message` of custom matchers include text for helper methods
|
13
|
+
# defined using `chain`, e.g.:
|
14
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
15
|
+
# # => "be bigger than 2 and smaller than 4"
|
16
|
+
# ...rather than:
|
17
|
+
# # => "be bigger than 2"
|
18
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
19
|
+
end
|
20
|
+
|
21
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
22
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
23
|
+
config.mock_with :rspec do |mocks|
|
24
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
25
|
+
# a real object. This is generally recommended, and will default to
|
26
|
+
# `true` in RSpec 4.
|
27
|
+
mocks.verify_partial_doubles = true
|
28
|
+
|
29
|
+
# To allow expectations on `nil` and suppress this message, set `config.allow_message_expectations_on_nil` to `true`.
|
30
|
+
# To disallow expectations on `nil`, set `config.allow_message_expectations_on_nil` to `false`.
|
31
|
+
mocks.allow_message_expectations_on_nil = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
35
|
+
# have no way to turn it off -- the option exists only for backwards
|
36
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
37
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
38
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
39
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
40
|
+
|
41
|
+
# This allows you to limit a spec run to individual examples or groups
|
42
|
+
# you care about by tagging them with `:focus` metadata. When nothing
|
43
|
+
# is tagged with `:focus`, all examples get run. RSpec also provides
|
44
|
+
# aliases for `it`, `describe`, and `context` that include `:focus`
|
45
|
+
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
46
|
+
config.filter_run_when_matching :focus
|
47
|
+
|
48
|
+
# Allows RSpec to persist some state between runs in order to support
|
49
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
50
|
+
# you configure your source control system to ignore this file.
|
51
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
52
|
+
|
53
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
54
|
+
# recommended. For more details, see:
|
55
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
56
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
57
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
58
|
+
config.disable_monkey_patching!
|
59
|
+
|
60
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
61
|
+
# file, and it's useful to allow more verbose output when running an
|
62
|
+
# individual spec file.
|
63
|
+
if config.files_to_run.one?
|
64
|
+
config.default_formatter = 'doc'
|
65
|
+
end
|
66
|
+
|
67
|
+
# Print the 10 slowest examples and example groups at the
|
68
|
+
# end of the spec run, to help surface which specs are running
|
69
|
+
# particularly slow.
|
70
|
+
config.profile_examples = 10
|
71
|
+
|
72
|
+
# Run specs in random order to surface order dependencies. If you find an
|
73
|
+
# order dependency and want to debug it, you can fix the order by providing
|
74
|
+
# the seed, which is printed after each run.
|
75
|
+
# --seed 1234
|
76
|
+
config.order = :random
|
77
|
+
end
|
data/spec/support/cli.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
def execute(args)
|
2
|
+
@original = Dir.pwd
|
3
|
+
Dir.chdir "/tmp/branch-cli-test"
|
4
|
+
main(args)
|
5
|
+
rescue SystemExit
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_test_repo
|
9
|
+
`mkdir /tmp/branch-cli-test`
|
10
|
+
`cd /tmp/branch-cli-test && git init`
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown_test_repo
|
14
|
+
`rm -rf /tmp/branch-cli-test`
|
15
|
+
Dir.chdir "/tmp"
|
16
|
+
end
|
17
|
+
|
18
|
+
def clone_remote_repo
|
19
|
+
teardown_test_repo
|
20
|
+
`cd /tmp && git clone https://github.com/Dan2552/branch.git branch-cli-test`
|
21
|
+
end
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
config.before(:each) { teardown_test_repo; create_test_repo }
|
25
|
+
config.after(:each) { teardown_test_repo }
|
26
|
+
end
|
27
|
+
|
28
|
+
def expect_output(out)
|
29
|
+
expect { subject }.to output(out).to_stdout
|
30
|
+
end
|
31
|
+
|
32
|
+
def expect_to_not_output(out)
|
33
|
+
expect { subject }.to_not output(out).to_stdout
|
34
|
+
end
|
35
|
+
|
36
|
+
def expect_branch(branch)
|
37
|
+
expect(`cd /tmp/branch-cli-test && git status`).to match(/On branch #{branch}/)
|
38
|
+
end
|
39
|
+
|
40
|
+
def touch(filename)
|
41
|
+
`cd /tmp/branch-cli-test && touch #{filename}`
|
42
|
+
end
|
43
|
+
|
44
|
+
def commit
|
45
|
+
`cd /tmp/branch-cli-test && git add . -A && git commit -m "test"`
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: branch_cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Inkpen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: formatador
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: inquirer
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.2.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: binding_of_caller
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.7.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.7.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.6'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.10.4
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.10.4
|
83
|
+
description: Faster, safer git branching.
|
84
|
+
email:
|
85
|
+
- dan2552@gmail.com
|
86
|
+
executables:
|
87
|
+
- branch
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- Gemfile
|
94
|
+
- Gemfile.lock
|
95
|
+
- LICENSE
|
96
|
+
- README.md
|
97
|
+
- bin/branch
|
98
|
+
- branch_cli.gemspec
|
99
|
+
- lib/branch_cli.rb
|
100
|
+
- lib/branch_cli/branch.rb
|
101
|
+
- lib/branch_cli/commit.rb
|
102
|
+
- lib/branch_cli/main.rb
|
103
|
+
- lib/branch_cli/options.rb
|
104
|
+
- lib/branch_cli/print.rb
|
105
|
+
- lib/branch_cli/run.rb
|
106
|
+
- lib/branch_cli/string.rb
|
107
|
+
- lib/branch_cli/swift_compatibility.rb
|
108
|
+
- screenshot.png
|
109
|
+
- spec/integration/help_spec.rb
|
110
|
+
- spec/integration/status_spec.rb
|
111
|
+
- spec/integration/switch_branch_spec.rb
|
112
|
+
- spec/integration/version_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
- spec/support/cli.rb
|
115
|
+
homepage: https://github.com/Dan2552/branch
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.6.8
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Branch aims to simplify a developer's daily workflow of Git. It is in no
|
139
|
+
means supposed to replace Git, but provide a quicker and easier way to do some more
|
140
|
+
common functions (with more memorable commands). Branch is pretty opinionated in
|
141
|
+
the way it does things (i.e. I don't care about staging/unstaging files, I just
|
142
|
+
want all of my current changes in a single bucket). It also makes the assumption
|
143
|
+
that you use a single remote (origin) and your local "my-branch" is always going
|
144
|
+
to have the upstream as "origin/my-branch" (i.e. it fits with the most common of
|
145
|
+
workflows).
|
146
|
+
test_files:
|
147
|
+
- spec/integration/help_spec.rb
|
148
|
+
- spec/integration/status_spec.rb
|
149
|
+
- spec/integration/switch_branch_spec.rb
|
150
|
+
- spec/integration/version_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/support/cli.rb
|