git_commands 3.1.8 → 3.2.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 +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:
|