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 CHANGED
@@ -1,13 +1,12 @@
1
- Git 'Er Done is a ruby tool for automating common git operations. It's similiar in concept to git flow (https://github.com/nvie/gitflow) but implements a simpler branching model.
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. I have a terrible memory for command line syntax, so I want something that reduces the number of commands I have to remember to do things right.
8
- 2. I want to understand more how to configure git process flows (much like git-flow) does, but have more configurability (via Ruby)
9
- 3. Gives an interesting test case for my Thor talk at DCRUG and Arlington RUG.
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
- Things you can do:
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://reinh.com/blog/2009/03/02/a-git-workflow-for-agile-teams.html - Target workflow
54
- http://grit.rubyforge.org/ - A Ruby wrapper around git: Might be useful for more indepth interaction with git.
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
@@ -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
- # Add everything, commit it, merge it back into the main branch.
22
- desc 'done (NAME)', 'Completes a feature (commits, squashes then merges into master). Call sync before doing this.'
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 => 'master'
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 master
45
- git :rebase => "-i master"
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(master_branch.name, current_branch.name)
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
@@ -1,7 +1,7 @@
1
1
  module Git
2
2
  module Er
3
3
  module Done
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
6
6
  end
7
7
  end
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.3.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: 2012-01-13 00:00:00.000000000Z
12
+ date: 2013-09-04 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
16
- requirement: &70179246615980 !ruby/object:Gem::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: *70179246615980
24
+ version_requirements: *70323278285920
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: grit
27
- requirement: &70179246615400 !ruby/object:Gem::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: *70179246615400
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.10
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.