git_er_done 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +64 -11
- data/features/cli.feature +54 -1
- data/features/step_definitions/cli_steps.rb +41 -0
- data/lib/git_er_done/app.rb +79 -24
- data/lib/git_er_done/version.rb +1 -1
- metadata +7 -7
data/README.md
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
Git 'Er Done is a ruby tool for automating common git operations. It's
|
1
|
+
Git 'Er Done is a ruby tool for automating common git operations. It's similar in concept to git flow (https://github.com/nvie/gitflow) but is designed for small teams. It's branching model is quite a bit simpler but still makes it easy to keep an organized Git repository.
|
2
2
|
|
3
3
|
## Reasons for this project
|
4
4
|
|
5
5
|
This project exists for a few reasons:
|
6
6
|
|
7
|
-
1.
|
8
|
-
|
9
|
-
|
10
|
-
4. Make doing the 'right' thing easy (i.e. feature branches should be simple. Smaller projects)
|
7
|
+
1. Encourage good habits: Practices like rebasing, feature branches and squashing commits make code repositories easy to follow.
|
8
|
+
1. Make 'Right way' easy: Doing proper git workflows require a lot of commands. We should script them to make them easy.
|
9
|
+
1. Exploration: I want to understand more how to configure git process flows (much like git-flow) does, but have more configurability (via Ruby)
|
11
10
|
|
12
11
|
## Installation
|
13
12
|
|
@@ -17,9 +16,63 @@ gem install git_er_done
|
|
17
16
|
|
18
17
|
This makes the `gd` command available on your system.
|
19
18
|
|
19
|
+
|
20
|
+
## What you get
|
21
|
+
|
22
|
+
### 1. Starting Feature Branches
|
23
|
+
|
24
|
+
When you work locally, it's helpful to work in a feature branch. To create a feature branch, you would normally do something like:
|
25
|
+
|
26
|
+
```
|
27
|
+
git checkout -b features/new_widget
|
28
|
+
```
|
29
|
+
|
30
|
+
With GitErDone, you can type the following to get the same thing:
|
31
|
+
|
32
|
+
```
|
33
|
+
$ gd feature new_widget
|
34
|
+
````
|
35
|
+
|
36
|
+
Really, the only benefit from this is prefixing branches with features/, which provides some context to your branch names. (Later, we might add bugs/ and/or 'remote-features')
|
37
|
+
|
38
|
+
### 2. Finishing a feature
|
39
|
+
|
40
|
+
After you are done with your feature, you might want to do a few things:
|
41
|
+
|
42
|
+
1. Squash your feature's commits into a single commit
|
43
|
+
1. Checkout master
|
44
|
+
1. Merge your changes
|
45
|
+
1. Delete the branch
|
46
|
+
|
47
|
+
That's a lot of commands to remember and type. Instead, you can do:
|
48
|
+
|
49
|
+
```
|
50
|
+
gd done
|
51
|
+
```
|
52
|
+
|
53
|
+
This will trigger an [interactive rebase](http://book.git-scm.com/4_interactive_rebasing.html) including all changes made since master. You can choose to squash them (or not). Then it will merge your changes with master, and delete your branch.
|
54
|
+
|
55
|
+
Pretty simple.
|
56
|
+
|
57
|
+
### 3. Keeping in Sync with the master using rebase
|
58
|
+
|
59
|
+
Unless you are working by yourself, its likely you may start a feature and before you can merge and push your changes, somebody is going to push changes. It's going to be a lot easier to reconcile your changes sooner than later. With GitErDone, you can do the following when working in your feature branch.
|
60
|
+
|
61
|
+
```
|
62
|
+
(features/new_widget)$ gd sync
|
63
|
+
```
|
64
|
+
|
65
|
+
This will do the following:
|
66
|
+
|
67
|
+
1. Checkout master and then pull
|
68
|
+
1. Checkout your branch
|
69
|
+
1. Rebase your branch from master. See [rebasing](http://book.git-scm.com/4_rebasing.html) for more info.
|
70
|
+
|
71
|
+
Syncing frequently will help avoid nasty merges later.
|
72
|
+
|
20
73
|
## Syntax
|
21
74
|
|
22
|
-
|
75
|
+
Command reference:
|
23
76
|
|
24
77
|
```
|
25
78
|
gd - Lists all available commands.
|
@@ -29,6 +82,7 @@ gd done new_widget - Completes a feature branch with the name 'new_widget' (Com
|
|
29
82
|
gd done - Completes the current feature branch you are on.
|
30
83
|
gd squash - Condenses multiple commits for the current branch into a single commit.
|
31
84
|
gd sync - Brings your branch up to date with the latest version (Use before finishing a feature)
|
85
|
+
gd inception - Determines the original branch(es) your current commit came from.
|
32
86
|
```
|
33
87
|
|
34
88
|
## Goals
|
@@ -36,7 +90,7 @@ gd sync - Brings your branch up to date with the latest version (Use before fini
|
|
36
90
|
Here's what I want to be able to support
|
37
91
|
|
38
92
|
* Creating and closing feature branches should be simple.
|
39
|
-
* Small projects that can work from and merge to master shouldn't need a 'develop' branch.
|
93
|
+
* Small projects that can work from and merge to master shouldn't need a 'develop' branch. (i.e. git-flow)
|
40
94
|
* Should automatically squashing commits from feature branches into a single commit via rebase
|
41
95
|
* Support git-flow's Feature, hotfix, release branches to/from develop if necessary.
|
42
96
|
* Provide a nice discoverable CLI for doing this sort of thing.
|
@@ -45,11 +99,10 @@ Here's what I want to be able to support
|
|
45
99
|
|
46
100
|
* Improve error messages for incorrectly supplied parameters. (i.e. gd feature)
|
47
101
|
* If you gd sync with unstaged changes, it should not switch branches. (It does, which is probably wrong)
|
48
|
-
* Figure out how to unit test this.
|
49
102
|
* Better error checking (merges failing may cause problems)
|
50
103
|
|
51
104
|
## References
|
52
105
|
|
53
|
-
http://
|
54
|
-
http://
|
55
|
-
http://stackoverflow.com/questions/5294069/best-way-to-create-an-executable-for-a-gem-using-rake - Minimum work to make a CLI bin file.
|
106
|
+
* Interactive Rebasing (i.e. Squashing Commits) - http://book.git-scm.com/4_interactive_rebasing.html
|
107
|
+
* Target workflow - http://reinh.com/blog/2009/03/02/a-git-workflow-for-agile-teams.html -
|
108
|
+
* http://stackoverflow.com/questions/5294069/best-way-to-create-an-executable-for-a-gem-using-rake - Minimum work to make a CLI bin file.
|
data/features/cli.feature
CHANGED
@@ -27,4 +27,57 @@ Feature: CLI
|
|
27
27
|
And the output should contain "Switched to branch 'features/new_widget"
|
28
28
|
And the output should contain "Current branch features/new_widget is up to date"
|
29
29
|
|
30
|
-
|
30
|
+
Scenario: Inception of a branch
|
31
|
+
Given I am working on a git project
|
32
|
+
And I run `git checkout -b staging`
|
33
|
+
And I commit a new file
|
34
|
+
When I run `gd feature new_widget`
|
35
|
+
And I commit another file
|
36
|
+
And I run `gd inception`
|
37
|
+
Then the output should contain:
|
38
|
+
"""
|
39
|
+
Current branch 'features/new_widget' was forked from 'staging'
|
40
|
+
"""
|
41
|
+
|
42
|
+
Scenario: Ambiguous inception of a branch
|
43
|
+
Given I am working on a git project
|
44
|
+
And I run `git checkout -b staging`
|
45
|
+
When I run `gd feature new_widget`
|
46
|
+
And I commit a new file
|
47
|
+
And I run `gd inception`
|
48
|
+
Then the output should contain:
|
49
|
+
"""
|
50
|
+
Current branch 'features/new_widget' matches the following branches: 'master, staging'
|
51
|
+
"""
|
52
|
+
|
53
|
+
Scenario: Completing work branched from a non-master branch
|
54
|
+
Given I am working on a git project
|
55
|
+
And I have been working in the "staging" branch
|
56
|
+
When I run `gd feature new_widget`
|
57
|
+
And I commit a new file
|
58
|
+
And I run `gd done`
|
59
|
+
Then the output should contain:
|
60
|
+
"""
|
61
|
+
Switched to branch 'staging'
|
62
|
+
"""
|
63
|
+
|
64
|
+
Scenario: Completing work that could have been from multiple branches
|
65
|
+
Given I am working on a git project
|
66
|
+
And I run `git checkout -b staging`
|
67
|
+
When I run `gd feature new_widget`
|
68
|
+
And I commit a new file
|
69
|
+
And I run `gd done` interactively
|
70
|
+
And I type "1"
|
71
|
+
Then the output should contain:
|
72
|
+
"""
|
73
|
+
Switched to branch 'master'
|
74
|
+
"""
|
75
|
+
|
76
|
+
Scenario: Selecting an invalid branch number.
|
77
|
+
Given I am working on a git project
|
78
|
+
And I run `git checkout -b staging`
|
79
|
+
When I run `gd feature new_widget`
|
80
|
+
And I commit a new file
|
81
|
+
And I run `gd done` interactively
|
82
|
+
And I type "3"
|
83
|
+
Then the program should fail using Thor's erroneous exit codes
|
@@ -12,4 +12,45 @@ Given /^I am working on the "(.*)" branch$/ do |branch|
|
|
12
12
|
end
|
13
13
|
When /^should display the current version$/ do
|
14
14
|
assert_partial_output(Git::Er::Done::VERSION, all_output)
|
15
|
+
end
|
16
|
+
|
17
|
+
When /^I commit a new file$/ do
|
18
|
+
write_file('first.md', "A commit")
|
19
|
+
run_simple "git add ."
|
20
|
+
run_simple "git commit -m 'Add a file'"
|
21
|
+
end
|
22
|
+
|
23
|
+
When /^I commit another file$/ do
|
24
|
+
write_file('second.md', "A commit")
|
25
|
+
run_simple "git add ."
|
26
|
+
run_simple "git commit -m 'Add a file'"
|
27
|
+
end
|
28
|
+
|
29
|
+
When /^I make several commits$/ do
|
30
|
+
write_file('first.md', "A commit")
|
31
|
+
run_simple "git add ."
|
32
|
+
run_simple "git commit -m 'Add a file'"
|
33
|
+
|
34
|
+
write_file('second.md', "A commit")
|
35
|
+
run_simple "git add ."
|
36
|
+
run_simple "git commit -m 'Add a file'"
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
When /^I have been working in the "([^"]*)" branch$/ do |branch_name|
|
41
|
+
steps %Q{
|
42
|
+
And I run `git checkout -b #{branch_name}`
|
43
|
+
}
|
44
|
+
write_file("#{branch_name}.md", "First commit for #{branch_name}")
|
45
|
+
run_simple "git add ."
|
46
|
+
run_simple "git commit -m 'First commit for #{branch_name}'"
|
47
|
+
end
|
48
|
+
|
49
|
+
Then /^the program should fail using Thor's erroneous exit codes$/ do
|
50
|
+
# This really should be 1, but thor doesn't seem to respect error statuses
|
51
|
+
# See http://stackoverflow.com/questions/17241932/ruby-thor-exit-status-in-case-of-an-error
|
52
|
+
steps %Q{
|
53
|
+
Then the exit status should be 0
|
54
|
+
And the output should contain "Error!"
|
55
|
+
}
|
15
56
|
end
|
data/lib/git_er_done/app.rb
CHANGED
@@ -6,51 +6,64 @@ module Git
|
|
6
6
|
module Er
|
7
7
|
module Done
|
8
8
|
class App < Thor
|
9
|
-
|
9
|
+
|
10
10
|
include Thor::Actions
|
11
11
|
include Git::Er::Done::Actions
|
12
|
-
|
12
|
+
|
13
13
|
FEATURES_PATH = "features/"
|
14
|
-
|
14
|
+
|
15
15
|
desc 'feature [NAME]', 'Start a new feature using a branch.'
|
16
16
|
def feature(name)
|
17
17
|
puts "Creating a new feature called #{feature_branch(name)}."
|
18
18
|
git :checkout => "-b #{feature_branch(name)}"
|
19
19
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
|
21
|
+
desc 'inception', 'Find the branch(es) that the current branch was originally created from.'
|
22
|
+
def inception
|
23
|
+
# Find the first commit that matches one of the heads.
|
24
|
+
matching_branches = inception_branches()
|
25
|
+
|
26
|
+
if matching_branches.size > 1
|
27
|
+
puts "Current branch '#{current_branch.name}' matches the following branches: '#{matching_branches.collect {|b| b.name}.join(", ")}'"
|
28
|
+
else
|
29
|
+
puts "Current branch '#{current_branch.name}' was forked from '#{matching_branches.first.name}'"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'done (NAME)', 'Completes a feature (commits, squashes then merges into the inception branch).
|
35
|
+
Recommend calling sync before doing this.'
|
23
36
|
def done(name=nil)
|
24
37
|
unless name
|
25
|
-
name = current_feature
|
38
|
+
name = current_feature
|
26
39
|
end
|
27
40
|
puts "Completing a feature called #{feature_branch(name)}"
|
28
41
|
git :add=>"."
|
29
42
|
git :commit
|
30
43
|
squash
|
31
44
|
# Should we also sync with master first?
|
32
|
-
git :checkout =>
|
45
|
+
git :checkout => inception_branch_name
|
33
46
|
git :merge => feature_branch(name)
|
34
|
-
git :branch => "-d #{feature_branch(name)}"
|
47
|
+
git :branch => "-d #{feature_branch(name)}"
|
35
48
|
end
|
36
|
-
|
37
|
-
desc 'squash', 'Squash all commits from the current branch into a single commit.'
|
49
|
+
|
50
|
+
desc 'squash', 'Squash all commits from the current branch into a single commit (since inception).'
|
38
51
|
def squash
|
39
52
|
new_commits = commits_in_feature_branch.size
|
40
53
|
if new_commits < 2
|
41
54
|
puts "Only '#{new_commits}' new commits in this branch, so no squashing necessary."
|
42
55
|
return
|
43
56
|
end
|
44
|
-
# Squash all changes since we branched away from
|
45
|
-
git :rebase => "-i
|
57
|
+
# Squash all changes since we branched away from inception branch
|
58
|
+
git :rebase => "-i #{inception_branch_name}"
|
46
59
|
end
|
47
|
-
|
60
|
+
|
48
61
|
desc 'info', "Report information about the current feature branch you are in."
|
49
62
|
def info
|
50
63
|
commits = commits_in_feature_branch
|
51
64
|
puts "There are #{commits.size} new commits for #{current_branch_name}"
|
52
65
|
end
|
53
|
-
|
66
|
+
|
54
67
|
desc 'sync', 'Update your branch with the latest from master.'
|
55
68
|
def sync
|
56
69
|
return_to_branch = current_branch_name
|
@@ -65,13 +78,55 @@ module Git
|
|
65
78
|
def version
|
66
79
|
puts "Git-Er-Done #{Git::Er::Done::VERSION}"
|
67
80
|
end
|
81
|
+
|
68
82
|
private
|
69
|
-
|
83
|
+
|
84
|
+
def inception_branch_name
|
85
|
+
return @inception_branch.name if @inception_branch
|
86
|
+
branches = inception_branches
|
87
|
+
@inception_branch = if branches.size > 1
|
88
|
+
say 'There is more than one possible inception branch for your current feature.', :green
|
89
|
+
branches.each_with_index do |branch, i|
|
90
|
+
say "#{i + 1}. #{branch.name}", :yellow
|
91
|
+
end
|
92
|
+
index = ask "Which would you like merge it into?:", :green
|
93
|
+
unless index.to_i.between?(1, branches.size)
|
94
|
+
say "Error! Invalid branch selected.", :red
|
95
|
+
exit
|
96
|
+
end
|
97
|
+
branches[index.to_i - 1]
|
98
|
+
else
|
99
|
+
branches.first
|
100
|
+
end
|
101
|
+
@inception_branch.name
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns a list of branches that the current commit could have been originated from.
|
105
|
+
# @return [Array<Grit::Head>]
|
106
|
+
def inception_branches
|
107
|
+
commit = current_branch.commit
|
108
|
+
|
109
|
+
# Look through the parent history of this branch
|
110
|
+
while true
|
111
|
+
matching_branches = long_running_branches.select { |branch| commit.id == branch.commit.id }
|
112
|
+
return matching_branches unless matching_branches.empty?
|
113
|
+
commit = commit.parents.first
|
114
|
+
end
|
115
|
+
[]
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns all branches that are 'long lived', i.e. not a feature.
|
119
|
+
#
|
120
|
+
# @return [Array<Grit::Head>]
|
121
|
+
def long_running_branches
|
122
|
+
repo.heads.select { |head| !head.name.include?('features') }
|
123
|
+
end
|
124
|
+
|
70
125
|
# Returns a list of all commits for the current branch since it was forked from master.
|
71
126
|
def commits_in_feature_branch
|
72
|
-
repo.commits_between(
|
127
|
+
repo.commits_between(inception_branch_name, current_branch.name)
|
73
128
|
end
|
74
|
-
|
129
|
+
|
75
130
|
# Returns the name of the feature for the current branch
|
76
131
|
# @return [String] Name of feature (not include features/)
|
77
132
|
def current_feature
|
@@ -81,27 +136,27 @@ module Git
|
|
81
136
|
end
|
82
137
|
return ""
|
83
138
|
end
|
84
|
-
|
139
|
+
|
85
140
|
def current_branch
|
86
141
|
repo.head
|
87
142
|
end
|
88
|
-
|
143
|
+
|
89
144
|
def current_branch_name
|
90
145
|
current_branch.name
|
91
146
|
end
|
92
|
-
|
147
|
+
|
93
148
|
def repo
|
94
149
|
repo ||= Grit::Repo.new('.')
|
95
150
|
end
|
96
|
-
|
151
|
+
|
97
152
|
def master_branch
|
98
153
|
repo.heads.each do |head|
|
99
154
|
return head if head.name == "master"
|
100
155
|
end
|
101
156
|
raise "No branch named 'master' found."
|
102
157
|
end
|
103
|
-
|
104
|
-
def feature_branch(name)
|
158
|
+
|
159
|
+
def feature_branch(name)
|
105
160
|
"#{FEATURES_PATH}#{name}"
|
106
161
|
end
|
107
162
|
end
|
data/lib/git_er_done/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_er_done
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-09-04 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thor
|
16
|
-
requirement: &
|
16
|
+
requirement: &70323278285920 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.14.6
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70323278285920
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: grit
|
27
|
-
requirement: &
|
27
|
+
requirement: &70323278285460 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '2.4'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70323278285460
|
36
36
|
description: A gem to assist with git workflow, similar to git-flow.
|
37
37
|
email:
|
38
38
|
- peakpg@gmail.com
|
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
75
|
version: '0'
|
76
76
|
requirements: []
|
77
77
|
rubyforge_project: git_er_done
|
78
|
-
rubygems_version: 1.8.
|
78
|
+
rubygems_version: 1.8.15
|
79
79
|
signing_key:
|
80
80
|
specification_version: 3
|
81
81
|
summary: A gem to assist with git workflow.
|