git_commands 3.1.8 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +44 -21
- data/lib/git_commands/branch.rb +66 -0
- data/lib/git_commands/cli.rb +4 -3
- data/lib/git_commands/command.rb +21 -77
- data/lib/git_commands/prompt.rb +0 -1
- data/lib/git_commands/repository.rb +39 -0
- data/lib/git_commands/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01a6f8395baa1fb4d58f303328df32879e9f2fde
|
4
|
+
data.tar.gz: b2868ae612d6128188cd940e8e25bf36ad301ce1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4f5c35d40aa43b150264c28dd056e1252ac18ebeece5db18c315173a8d3ebcf34933dbad57d9e10db517616ce806981dc1c2ce076dbc06927a1ad723aaf7253
|
7
|
+
data.tar.gz: cac97ac9121721c40287714f5794a1b1fcc7bed5516ed839c15c27627d87bbbf584fb64b92eecf6d3f45f74d062180867e7a769c91e898019f16972bf75eb5fa
|
data/README.md
CHANGED
@@ -15,15 +15,17 @@
|
|
15
15
|
|
16
16
|
## Workflow
|
17
17
|
This script will facilitate adopting a subset of the branch-featuring workflow characterised by:
|
18
|
-
* each
|
19
|
-
*
|
20
|
-
*
|
21
|
-
*
|
18
|
+
* each feature will have its own branch
|
19
|
+
* feature branches derive directly form master
|
20
|
+
* integration of master to feature branch happens via rebasing
|
21
|
+
* force pushing of feature branches to origin is not an issue
|
22
|
+
* release branches are created aggregating multiple branches
|
22
23
|
|
23
24
|
## Scope
|
24
25
|
The scope of this gem is helping out in the following cases:
|
25
26
|
* you have multiple feature branches waiting for release due to some reason (i.e. long QA time...), and need to keep them aligned with master
|
26
27
|
* you need to quickly aggregate branches for a release
|
28
|
+
* you want to cleanup local and remote branches upon release
|
27
29
|
|
28
30
|
## Installation
|
29
31
|
Just install the gem to use the binaries commands.
|
@@ -46,9 +48,9 @@ Display the help of a specific command by:
|
|
46
48
|
|
47
49
|
```
|
48
50
|
rebase --help
|
49
|
-
Usage: rebase --repo
|
51
|
+
Usage: rebase --repo=/Users/Elvis/greatest_hits --branches=feature/love_me_tender,fetaure/teddybear
|
50
52
|
-r, --repo=REPO The path to the existing GIT repository
|
51
|
-
-b, --branches=BRANCHES
|
53
|
+
-b, --branches=BRANCHES Specify branches as: 1. a comma-separated list of names 2. the path to a file containing names on each line 3. via pattern matching
|
52
54
|
-h, --help Prints this help
|
53
55
|
```
|
54
56
|
|
@@ -61,14 +63,13 @@ rebase --repo=invalid
|
|
61
63
|
```
|
62
64
|
|
63
65
|
#### Branches
|
64
|
-
|
65
|
-
You have two main options here:
|
66
|
+
As with the repository you always have to specify the list of branches you want to work with. There are different options:
|
66
67
|
|
67
68
|
##### List of branches
|
68
69
|
Specify a comma separated list of branch names:
|
69
70
|
|
70
71
|
```
|
71
|
-
rebase --branches=feature/love_me_tender,feature/teddybear,feature/return_to_sender
|
72
|
+
rebase --repo=/Users/Elvis/greatest_hits --branches=feature/love_me_tender,feature/teddybear,feature/return_to_sender
|
72
73
|
|
73
74
|
Loading branches file...
|
74
75
|
Successfully loaded 3 branches:
|
@@ -80,7 +81,7 @@ Successfully loaded 3 branches:
|
|
80
81
|
##### Path to a names file
|
81
82
|
Specify a path (absolute or relative) to a file containing the branches names on each line:
|
82
83
|
|
83
|
-
File
|
84
|
+
File */Users/Elvis/greatest_hits/.branches*:
|
84
85
|
```
|
85
86
|
feature/love_me_tender
|
86
87
|
feature/teddybear
|
@@ -89,7 +90,7 @@ feature/in_the_ghetto
|
|
89
90
|
```
|
90
91
|
|
91
92
|
```
|
92
|
-
rebase --branches
|
93
|
+
rebase --repo=/Users/Elvis/greatest_hits --branches=/Users/Elvis/greatest_hits/.branches
|
93
94
|
|
94
95
|
Loading branches file...
|
95
96
|
Successfully loaded 4 branches:
|
@@ -99,25 +100,47 @@ Successfully loaded 4 branches:
|
|
99
100
|
04. feature/in_the_ghetto
|
100
101
|
```
|
101
102
|
|
103
|
+
##### Pattern matching
|
104
|
+
In case you want to work with a set of branches with a common pattern, you have to specify a greedy operator with the wild card you want to match.
|
105
|
+
Just consider you have not to specify *origin/* as the name of the branch, since is managed by the script for you:
|
106
|
+
|
107
|
+
```
|
108
|
+
rebase --repo=/Users/Elvis/greatest_hits --branches=*der
|
109
|
+
|
110
|
+
Loading branches file...
|
111
|
+
Successfully loaded 2 branches:
|
112
|
+
01. feature/love_me_tender
|
113
|
+
02. feature/return_to_sender
|
114
|
+
```
|
115
|
+
|
102
116
|
##### Checking
|
103
|
-
Each loaded branch is validated for existence (via
|
117
|
+
Each loaded branch is validated for existence (but for branches loaded via pattern matching). In case the validation fails, the branch is filtered from the resulting list.
|
104
118
|
|
105
119
|
```
|
106
|
-
rebase --repo
|
120
|
+
rebase --repo=/Users/Elvis/greatest_hits --branches=noent,feature/love_me_tender
|
107
121
|
|
108
122
|
Loading branches file...
|
109
|
-
|
123
|
+
Successfully loaded 1 branch:
|
124
|
+
01. feature/love_me_tender
|
125
|
+
```
|
126
|
+
|
127
|
+
In case no branches have been loaded, an error is raised:
|
128
|
+
|
129
|
+
```
|
130
|
+
cd /Users/Elvis
|
131
|
+
rebase --repo=./greatest_hits --branches=noent1, noent2
|
132
|
+
No branches loaded!
|
110
133
|
```
|
111
134
|
|
112
135
|
##### Master branch
|
113
|
-
Master branch cannot be included into the branches list for obvious reasons (from useless to dangerous ones)
|
114
|
-
An error is raised in case master branch is specified:
|
136
|
+
Master branch cannot be included into the branches list for obvious reasons (from useless to dangerous ones):
|
115
137
|
|
116
138
|
```
|
117
|
-
rebase --repo
|
139
|
+
rebase --repo=/Users/Elvis/greatest_hits --branches=master,feature/love_me_tender
|
118
140
|
|
119
141
|
Loading branches file...
|
120
|
-
|
142
|
+
Successfully loaded 1 branch:
|
143
|
+
01. feature/love_me_tender
|
121
144
|
```
|
122
145
|
|
123
146
|
### Commands
|
@@ -128,7 +151,7 @@ This is probably the most useful command in case you have several branches to re
|
|
128
151
|
A confirmation is asked to before rebasing.
|
129
152
|
|
130
153
|
```
|
131
|
-
rebase --repo
|
154
|
+
rebase --repo=/Users/Elvis/greatest_hits --branches=feature/love_me_tender,feature/teddybear,feature/return_to_sender
|
132
155
|
...
|
133
156
|
```
|
134
157
|
|
@@ -137,7 +160,7 @@ This command remove the specified branches locally and remotely.
|
|
137
160
|
A confirmation is asked before removal.
|
138
161
|
|
139
162
|
```
|
140
|
-
purge --repo=/temp/top_20 --branches
|
163
|
+
purge --repo=/temp/top_20 --branches=*obsolete*
|
141
164
|
...
|
142
165
|
```
|
143
166
|
|
@@ -147,5 +170,5 @@ It uses the following naming convention: *release/yyyy_mm_dd*
|
|
147
170
|
A confirmation is asked before aggregating.
|
148
171
|
|
149
172
|
```
|
150
|
-
aggregate --repo
|
173
|
+
aggregate --repo=/Users/Elvis/greatest_hits --branches=*ready*
|
151
174
|
```
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module GitCommands
|
2
|
+
class Branch
|
3
|
+
MASTER = "master"
|
4
|
+
ORIGIN = "origin/"
|
5
|
+
|
6
|
+
def self.strip_origin(name)
|
7
|
+
name.strip.split(ORIGIN).last
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.by_names(names_list)
|
11
|
+
String(names_list).split(",").map do |name|
|
12
|
+
new(name.strip)
|
13
|
+
end.select(&:valid?)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.by_file(names_file)
|
17
|
+
return [] unless File.file?(names_file)
|
18
|
+
File.foreach(names_file).map do |name|
|
19
|
+
new(name.strip)
|
20
|
+
end.select(&:valid?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.by_pattern(pattern)
|
24
|
+
return [] unless pattern.index("*")
|
25
|
+
`git branch -r --list #{ORIGIN}#{pattern}`.split("\n").map do |name|
|
26
|
+
new(strip_origin(name))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.factory(src)
|
31
|
+
return [] unless src
|
32
|
+
branches = by_file(src)
|
33
|
+
branches = by_pattern(src) if branches.empty?
|
34
|
+
branches = by_names(src) if branches.empty?
|
35
|
+
branches
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :name
|
39
|
+
|
40
|
+
def initialize(name)
|
41
|
+
@name = name
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
@name
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid?
|
49
|
+
return false if master?
|
50
|
+
return false unless exists?
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
self.name == other.name
|
56
|
+
end
|
57
|
+
|
58
|
+
private def master?
|
59
|
+
@name == MASTER
|
60
|
+
end
|
61
|
+
|
62
|
+
private def exists?
|
63
|
+
`git rev-parse --verify origin/#{@name} 2> /dev/null`.match(/^[0-9a-z]+/)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/git_commands/cli.rb
CHANGED
@@ -22,8 +22,9 @@ module GitCommands
|
|
22
22
|
parser.parse!(@args)
|
23
23
|
command = @command_klass.new(repo: @repo, branches: @branches)
|
24
24
|
command.send(@command_name)
|
25
|
-
rescue Command::GitError, AbortError,
|
25
|
+
rescue Command::GitError, AbortError, Repository::InvalidError => e
|
26
26
|
error(e.message)
|
27
|
+
exit
|
27
28
|
end
|
28
29
|
|
29
30
|
private def create_command
|
@@ -37,13 +38,13 @@ module GitCommands
|
|
37
38
|
|
38
39
|
private def parser
|
39
40
|
OptionParser.new do |opts|
|
40
|
-
opts.banner = "Usage: #{@command_name} --repo
|
41
|
+
opts.banner = "Usage: #{@command_name} --repo=/Users/Elvis/greatest_hits --branches=feature/love_me_tender,fetaure/teddybear"
|
41
42
|
|
42
43
|
opts.on("-rREPO", "--repo=REPO", "The path to the existing GIT repository") do |repo|
|
43
44
|
@repo = repo
|
44
45
|
end
|
45
46
|
|
46
|
-
opts.on("-bBRANCHES", "--branches=BRANCHES", "
|
47
|
+
opts.on("-bBRANCHES", "--branches=BRANCHES", "Specify branches as: 1. a comma-separated list of names 2. the path to a file containing names on each line 3. via pattern matching") do |branches|
|
47
48
|
@branches = branches
|
48
49
|
end
|
49
50
|
|
data/lib/git_commands/command.rb
CHANGED
@@ -2,41 +2,25 @@ require "pathname"
|
|
2
2
|
require "fileutils"
|
3
3
|
require "net/http"
|
4
4
|
require "git_commands/prompt"
|
5
|
+
require "git_commands/branch"
|
6
|
+
require "git_commands/repository"
|
5
7
|
|
6
8
|
module GitCommands
|
7
9
|
class Command
|
8
10
|
include Prompt
|
9
11
|
|
10
12
|
class GitError < StandardError; end
|
11
|
-
class NoBranchesError < ArgumentError; end
|
12
|
-
class NoentRepositoryError < ArgumentError; end
|
13
|
-
|
14
|
-
GITHUB_HOST = "github.com"
|
15
|
-
UNFINISHED_REBASE_FILES = %w(rebase-merge rebase-apply)
|
16
|
-
|
17
|
-
def self.check_connection
|
18
|
-
!!Net::HTTP.new(GITHUB_HOST).head("/")
|
19
|
-
rescue Errno::ENETUNREACH => e
|
20
|
-
raise e, "There is no connection!"
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.git_repo?(repo)
|
24
|
-
`git rev-parse --is-inside-work-tree 2> /dev/null`.strip == "true"
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.valid_branch?(branch)
|
28
|
-
`git rev-parse --verify origin/#{branch} 2> /dev/null`.match(/^[0-9a-z]+/)
|
29
|
-
end
|
30
13
|
|
31
14
|
attr_reader :out
|
32
15
|
|
33
|
-
def initialize(repo:, branches:, out: STDOUT)
|
34
|
-
self.class.check_connection
|
16
|
+
def initialize(repo:, branches:, repo_klass: Repository, branch_klass: Branch, out: STDOUT)
|
35
17
|
@out = out
|
36
|
-
@repo =
|
37
|
-
@
|
38
|
-
|
39
|
-
|
18
|
+
@repo = repo_klass.new(repo)
|
19
|
+
Dir.chdir(@repo) do
|
20
|
+
@branches = branch_klass.factory(branches)
|
21
|
+
@timestamp = Time.new.strftime("%Y-%m-%d")
|
22
|
+
print_branches
|
23
|
+
end
|
40
24
|
end
|
41
25
|
|
42
26
|
def purge
|
@@ -54,15 +38,15 @@ module GitCommands
|
|
54
38
|
end
|
55
39
|
|
56
40
|
def rebase
|
57
|
-
confirm("Proceed rebasing these branches") do
|
41
|
+
confirm("Proceed rebasing these branches with master") do
|
58
42
|
enter_repo do
|
59
43
|
@branches.each do |branch|
|
60
44
|
warning("Rebasing branch: #{branch}")
|
61
45
|
`git checkout #{branch}`
|
62
46
|
`git pull origin #{branch}`
|
63
|
-
rebase_with_master
|
64
|
-
`git push origin #{branch}`
|
65
|
-
`git checkout
|
47
|
+
next unless rebase_with_master
|
48
|
+
`git push -f origin #{branch}`
|
49
|
+
`git checkout #{Branch::MASTER}`
|
66
50
|
`git branch -D #{branch}`
|
67
51
|
success "Rebased successfully!"
|
68
52
|
end
|
@@ -79,53 +63,20 @@ module GitCommands
|
|
79
63
|
@branches.each do |branch|
|
80
64
|
warning("Merging branch: #{branch}")
|
81
65
|
`git checkout -b #{temp} origin/#{branch} --no-track`
|
82
|
-
rebase_with_master
|
66
|
+
next unless rebase_with_master
|
83
67
|
`git rebase #{aggregate}`
|
84
68
|
`git checkout #{aggregate}`
|
85
69
|
`git merge #{temp}`
|
86
70
|
`git branch -d #{temp}`
|
87
|
-
`git checkout master`
|
88
71
|
end
|
72
|
+
`git checkout #{Branch::MASTER}`
|
89
73
|
end
|
90
74
|
success "#{aggregate} branch created"
|
91
75
|
end
|
92
76
|
end
|
93
77
|
|
94
|
-
private def fetch_repo(repo)
|
95
|
-
fail NoentRepositoryError, "Please specify a valid GIT repository!" unless repo
|
96
|
-
fail NoentRepositoryError, "'#{repo}' is not a valid GIT repository!" unless valid_repo?(repo)
|
97
|
-
Pathname::new(repo)
|
98
|
-
end
|
99
|
-
|
100
|
-
private def valid_repo?(repo)
|
101
|
-
return false unless File.directory?(repo)
|
102
|
-
Dir.chdir(repo) do
|
103
|
-
self.class.git_repo?(repo)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
private def fetch_branches(src)
|
108
|
-
warning("Loading branches file")
|
109
|
-
branches = File.foreach(src).map(&:strip) if valid_file?(src)
|
110
|
-
branches ||= src.split(",").map(&:strip)
|
111
|
-
branches.tap do |list|
|
112
|
-
fail(NoBranchesError, "No branches have been loaded!") if list.empty?
|
113
|
-
list.each { |branch| check_branch(branch) }
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
private def check_branch(branch)
|
118
|
-
Dir.chdir(@repo) do
|
119
|
-
fail(GitError, "Commands cannot interact directly with 'master' branch!") if branch == "master"
|
120
|
-
fail(GitError, "Branch '#{branch}' does not exist!") unless self.class.valid_branch?(branch)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
private def valid_file?(branches)
|
125
|
-
File.exist?(branches) && !File.directory?(branches)
|
126
|
-
end
|
127
|
-
|
128
78
|
private def print_branches
|
79
|
+
fail GitError, "No branches loaded!" if @branches.empty?
|
129
80
|
size = @branches.to_a.size
|
130
81
|
plural = size > 1 ? "es" : ""
|
131
82
|
success "Successfully loaded #{size} branch#{plural}:"
|
@@ -133,16 +84,15 @@ module GitCommands
|
|
133
84
|
end
|
134
85
|
|
135
86
|
private def pull_master
|
136
|
-
`git checkout
|
87
|
+
`git checkout #{Branch::MASTER}`
|
137
88
|
`git pull`
|
138
89
|
end
|
139
90
|
|
140
91
|
private def rebase_with_master
|
141
|
-
`git rebase origin
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
end
|
92
|
+
`git rebase origin/#{Branch::MASTER}`
|
93
|
+
return true unless @repo.locked?
|
94
|
+
`git rebase --abort`
|
95
|
+
error("Got conflicts, skipping rebase!")
|
146
96
|
end
|
147
97
|
|
148
98
|
private def enter_repo
|
@@ -151,11 +101,5 @@ module GitCommands
|
|
151
101
|
yield
|
152
102
|
end
|
153
103
|
end
|
154
|
-
|
155
|
-
private def unfinished_rebase?
|
156
|
-
UNFINISHED_REBASE_FILES.any? do |name|
|
157
|
-
File.exists?(@repo.join(".git", name))
|
158
|
-
end
|
159
|
-
end
|
160
104
|
end
|
161
105
|
end
|
data/lib/git_commands/prompt.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module GitCommands
|
4
|
+
class Repository
|
5
|
+
LOCKING_FILES = %w(rebase-merge rebase-apply)
|
6
|
+
|
7
|
+
class InvalidError < StandardError; end
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = Pathname::new(path.to_s)
|
11
|
+
fail InvalidError, "'#{path}' is not a valid GIT repository!" unless valid?
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_path
|
15
|
+
@path.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def locked?
|
19
|
+
LOCKING_FILES.any? do |name|
|
20
|
+
File.exists?(@path.join(".git", name))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private def valid?
|
25
|
+
return false unless exists?
|
26
|
+
work_tree?
|
27
|
+
end
|
28
|
+
|
29
|
+
private def exists?
|
30
|
+
File.directory?(@path)
|
31
|
+
end
|
32
|
+
|
33
|
+
private def work_tree?
|
34
|
+
Dir.chdir(@path) do
|
35
|
+
`git rev-parse --is-inside-work-tree 2> /dev/null`.strip == "true"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/git_commands/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_commands
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- costajob
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -75,10 +75,12 @@ files:
|
|
75
75
|
- bin/setup
|
76
76
|
- git_commands.gemspec
|
77
77
|
- lib/git_commands.rb
|
78
|
+
- lib/git_commands/branch.rb
|
78
79
|
- lib/git_commands/cli.rb
|
79
80
|
- lib/git_commands/colorize.rb
|
80
81
|
- lib/git_commands/command.rb
|
81
82
|
- lib/git_commands/prompt.rb
|
83
|
+
- lib/git_commands/repository.rb
|
82
84
|
- lib/git_commands/version.rb
|
83
85
|
homepage: https://github.com/costajob/git_commands.git
|
84
86
|
licenses:
|