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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 67b5256b61bb6187f47f4b24fe7220750b022b55
4
- data.tar.gz: 9a40a26fbfb14155999237513714678a379943df
3
+ metadata.gz: 01a6f8395baa1fb4d58f303328df32879e9f2fde
4
+ data.tar.gz: b2868ae612d6128188cd940e8e25bf36ad301ce1
5
5
  SHA512:
6
- metadata.gz: e3358e46e6c0a3f3d84709e4ed8cdb46503c47c23dc1f4bf6ac6dbad26650314b6d5c5c6cf8e8c1a0d77152ecc8e27f7e3e39b6950f5e8605c4f9d883e66fc22
7
- data.tar.gz: b29fd122148138fc29b0a7ef6e3d53eb2c01630c363bc57afaf194b81ff0d89b7be4de91bdd2ce5d42a6dd3698fffb1e35d68d5f4a6edb48dc9cc85de35f4d86
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 **feature** will have **its own branch**
19
- * **feature** branches **derive** directly **form master**
20
- * **integration** of master to feature branch happens **via rebasing**
21
- * **release** branches are created **aggregating multiple branches** into a new one
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=./Sites/oro --branches=feature/add_bin,fetaure/remove_rake_task
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 The comma-separated list of branches or the path to a file listing branches names on each line
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
- Along with the repository you always have to specify the list of branches you want the command to interact with.
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 *./Sites/greatest_hits*:
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=./Sites/greatest_hits
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 *rev-parse*), in case it does not an error is raised:
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=./Sites/greatest_hits --branches=noent
120
+ rebase --repo=/Users/Elvis/greatest_hits --branches=noent,feature/love_me_tender
107
121
 
108
122
  Loading branches file...
109
- Branch 'noent' does not exist!
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=./Sites/greatest_hits --branches=master
139
+ rebase --repo=/Users/Elvis/greatest_hits --branches=master,feature/love_me_tender
118
140
 
119
141
  Loading branches file...
120
- Commands cannot interact directly with 'master' branch!
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=./Sites/greatest_hits --branches=feature/love_me_tender,feature/teddybear,feature/return_to_sender
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=release/in_the_ghetto
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=./Sites/greatest_hits --branches=./Sites/greatest_hits
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
@@ -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, ArgumentError => e
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=./Sites/oro --branches=feature/add_bin,fetaure/remove_rake_task"
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", "The comma-separated list of branches or the path to a file listing branches names on each line") do |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
 
@@ -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 = fetch_repo(repo)
37
- @branches = fetch_branches(String(branches))
38
- @timestamp = Time.new.strftime("%Y-%m-%d")
39
- print_branches
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 master`
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 master`
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/master`
142
- if unfinished_rebase?
143
- `git rebase --abort`
144
- fail(GitError, "Halting unfinished rebase!")
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
@@ -34,7 +34,6 @@ module GitCommands
34
34
 
35
35
  def error(message)
36
36
  out.puts message.to_s.red
37
- exit
38
37
  end
39
38
 
40
39
  private def ask(message)
@@ -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
@@ -1,3 +1,3 @@
1
1
  module GitCommands
2
- VERSION = "3.1.8"
2
+ VERSION = "3.2.0"
3
3
  end
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.1.8
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-18 00:00:00.000000000 Z
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: