git-process 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- git-process (0.9.4)
4
+ git-process (0.9.5)
5
5
  highline (~> 1.6.12)
6
6
  json (~> 1.7.3)
7
7
  launchy (~> 2.1.0)
data/README.md CHANGED
@@ -1,22 +1,39 @@
1
1
  [![Build Status](https://secure.travis-ci.org/jdigger/git-process.png)](http://travis-ci.org/jdigger/git-process)
2
2
 
3
- # Purpose #
3
+ # Purpose and Motivation #
4
4
 
5
5
  This provides an easy way to work with a sane git workflow process.
6
-
6
+ Short-lived feature branches can work quite well, but the formal git-flow process can be rather heavy in cost.
7
7
 
8
8
  # Installation #
9
9
 
10
- $ gem install git-process
10
+ $ sudo gem install git-process
11
+
12
+ ## Configurables ##
13
+ See notes for more details
11
14
 
15
+ * OAuth2 Token
16
+ * The name of the integration branch (defaults to `origin/master`, but can be set to `develop` or other)
12
17
 
18
+ ---
13
19
  # Overview #
14
20
 
21
+ ## Anticipated Use Cases ##
22
+
23
+ 1. User Creates new local branch
24
+ 1. User pushes local branch to remote (as feature branch) by rebasing integration branch, then pushing branch to remote
25
+ 1. User closes local branch by rebasing integration branch first, then pushing local to integration
26
+ 1. User initiates GitHub "pull request"
27
+
28
+ ## Command List ##
29
+
15
30
  * `git new-fb` - Create a new feature branch based on the integration branch.
16
31
  * `git sync` - Gets the latest changes that have happened on the integration branch, then pushes your changes to a "private" branch on the server.
17
32
  * `git pull-request` - Creates a Pull Request for the current branch.
18
33
  * `git to-master` - Rebase against the integration branch, then pushes to it.
19
34
 
35
+ **All commands are well documented within themselves: Use the "-h" switch to see the full documentation.** (e.g., "`git sync -h`")
36
+
20
37
  # Workflow #
21
38
 
22
39
  _The following assumes that the integration branch is "origin/master"._
@@ -26,7 +43,7 @@ _The following assumes that the integration branch is "origin/master"._
26
43
  1. When starting work on a new feature, use "`git new-fb feature-name`".
27
44
  * This creates a new branch called "`feature-name`" based on "`origin/master`".
28
45
  2. After making some changes, if you want to pick up any changes other people have made, as well
29
- as save your work on the server, do "`git synch`".
46
+ as save your work on the server, do "`git sync`".
30
47
  * That will merge in the changes that have occurred in "`origin/master`" and then push the
31
48
  result to the "`feature_branch`" branch to the server.
32
49
  3. When you feel your work is ready for others to look at, do another "`git sync`" to post your
@@ -41,7 +58,7 @@ _The following assumes that the integration branch is "origin/master"._
41
58
  1. When starting work on a new feature, use "`git new-fb feature-name`".
42
59
  * This creates a new branch called "`feature-name`" based on "`origin/master`".
43
60
  2. After making some changes, if you want to pick up any changes other people have made, as well
44
- as save your work on the server, do "`git synch`".
61
+ as save your work on the server, do "`git sync`".
45
62
  * That will merge in the changes that have occurred in "`origin/master`" and then push the
46
63
  result to the "`feature_branch`" branch to the server.
47
64
  3. When you are ready to merge your work into the mainline, "`git to-master`".
@@ -71,6 +88,45 @@ _The following assumes that the integration branch is "origin/master"._
71
88
  * This tries to respond "intelligently" to the use of 'rerere'.
72
89
 
73
90
 
91
+ # FAQ #
92
+
93
+ ## Q: How is this different from git-flow or GitHub flow? ##
94
+
95
+ ["git-flow"](http://nvie.com/posts/a-successful-git-branching-model/) is designed around having a very strongly defined process around keeping new development, hotfixes, release process changes, etc. all clearly separated. The problem I have with it is that it's too much "process" for not enough gain. (It has a waterfall feel to it, very much against the more modern [Continuous Delivery](http://continuousdelivery.com/) approach.)
96
+
97
+ ["GitHub Flow"](http://scottchacon.com/2011/08/31/github-flow.html) is a lot cleaner, but relies too heavily (IMHO) on web-based tools and on merging instead of rebasing. It is also focussed very tightly on a Continuous Deployment process, which is great for them, but not practical for everyone.
98
+
99
+
100
+ ## Q: Wait, I heard "branches are evil." Why should I do something evil? ##
101
+
102
+ Branches are extremely powerful tools that allow for clean organization/modularization of development.
103
+
104
+ * Branches make it easy to sandbox changes while they are in a state of flux, while at the same time be very fearless about making potentially breaking changes.
105
+ * For example, I commit "green to green": Doing [TDD](http://en.wikipedia.org/wiki/Test-driven_development), I commit every time I have a newly passing test case. So, assuming I'm in a regular development flow, I'm committing my changes every five minutes. Tiny commits, but lots of them. What that means is that if I make a "less than wise choice" at some point, it's trivial to rewind to before I'd made the mistake, potentially keep the throw-away code in another branch while I do my cleanup, and generally use the full power of a revision control system to make my life safer and easier. The branch(es) are pretty chaotic, but that's not a problem because before integrating with the mainline, I take a moment to cleanup: Squash related commits together, write clearer commit messages (since now I know what "the answer" is), and generally move from my drafts to a more finished result. (See below on objections related to "lying with rebase.") That may just be me, though, because I'm very paranoid when it comes to computers. I tend to automatically hit Cmd/Ctl-S every time I type a period when I'm writing, or when I close a block when I'm programming. I have a minimum of three copies/backups around the world of all my important documents. And I "`git sync`" frequently to make sure my machine isn't the only place where all my hard work is being stored. Have I mentioned I don't trust computers?
106
+
107
+ * Branches allow for focused collaboration. Because a branch is about exactly one thing, it means that a team can collaborate around a feature/bug (especially when used in conjunction with a "pull request"), and keep such changes sandboxed until such time that they are ready to bring a larger audience into the mix.
108
+ * Branches encourage being less "shy" about your code. I have heard, on a number of occasions, developers say "I'm not ready to push this to the server yet because [it's still rough (and embarrassing)]/[it may break other people]/etc." All of those reasons for "hoarding" code are moot with branches.
109
+
110
+ Jez Humble, a brilliant Principle at ThoughtWorks Studios, talks a lot about how "branches are evil." Unfortunately, people hear that, know how smart he is, and simply repeat it without really understanding what his objections are. Fortunately, he [posted clarification about what's really meant by that](http://continuousdelivery.com/2011/07/on-dvcs-continuous-integration-and-feature-branches/). He essentially says that the problem is that developers abuse branches by not merging with mainline (i.e., "master") on a regular basis. Not constantly getting changes *from* mainline makes life rough when it comes time to integrate. Not putting your changes *into* mainline means that your changes are not being validated (via [Continuous Integration](http://martinfowler.com/articles/continuousIntegration.html), or -- better -- with [Continuous Delivery](http://continuousdelivery.com/)). Both are, in fact, sins akin to not doing automated testing.
111
+
112
+ Making it "easier to do things right than wrong" (i.e., using branches and keeping them synced with mainline) was the primary motivation for this project. This should be especially evident in the "`git sync`" and "`git to-master`" commands.
113
+
114
+
115
+ ## Q: Why so much emphasis on rebasing? Isn't rebasing a dangerous lie? ##
116
+
117
+ Like any powerful tool, "`git rebase`" is "dangerous" if used incorrectly, just like "`rm -rf`". You simply need to know when and how to use it safely. And in the world of version control systems, "rebasing" is easily one of the most _**useful**_ tools to come around since the "`commit`" command.
118
+
119
+ [A famous article](http://paul.stadig.name/2010/12/thou-shalt-not-lie-git-rebase-ammend.html) that people have been parroting in various forms for a while makes the case that rebasing (and its various forms, such as squashing, amending commits, etc.) is a "lie." As with so many things, context is everything.
120
+
121
+ You almost certainly should *not* rebase things that you have "published." Generally this really means "Don't rebase the 'master' branch!" Fortunately, these scripts make it impossible to rebase the mainline by accident. By default "`git sync`" uses "merge" instead of "rebase" to encourage collaboration. (Though you can easily use "-r" if you know no one else is working on the branch.) When it's time to actually merge your work into the mainline (and thus no one is working against it except in the context of mainline), that's when it gets rebased in.
122
+
123
+ Rebasing "your" code is an extremely useful way of communicating clearly. In the "green to green" scenario above about branches, a lot of noise is generated. If someone wants to review my code, or cherry-pick in my changes, it's too much of a mess to effectively do so. Also, as part of the process of squashing, I have the opportunity to write clearer commit message based upon my newly enhanced understanding. The intermediate commits were my "drafts" and I'm now submitting my cleaned up copy.
124
+
125
+ If you have ever seen an "active" project that uses a process like "git-flow" that encourages a lot of branching and merging, you've seen how hard it can be to follow a particular line of development. Branch lines are flying around everywhere, and half the commits are pretty much pure noise. (e.g., "Merge branch 'develop' of ... into develop".) It's also hard to follow the order in which commits actually impacted the mainline. In many ways, in practice merges turn into "a truth effectively being a lie (because it's buried in the noise)" versus rebases that are "a lie (changed from it's 'original' form) to tell an effective truth (clean and very clear about its impact)."
126
+
127
+ I am trying to promote clear communication about current reality over micro-management over no-longer-relevant history. Thus the judicious use of rebase.
128
+
129
+
74
130
  # Contributing #
75
131
 
76
132
  ## Coding Setup ##
data/bin/git-sync CHANGED
@@ -17,7 +17,7 @@ DESCRIPTION
17
17
 
18
18
  This fetches the latest repository from the server, rebases/merges the current branch \
19
19
  against the changes in the integration branch, then pushes the result up to a branch on \
20
- the server of the same name.
20
+ the server of the same name. (Unless told not to.)
21
21
 
22
22
  If there is a problem, such as a merge conflict, this tries to \
23
23
  resolve it automatically. If it can not do so in an automated way, \
@@ -40,8 +40,10 @@ DESC
40
40
  parser.opt :rebase, "Rebase instead of merge against the integration branch"
41
41
  parser.opt :merge, "Merge instead of rebase against the integration branch", :short => :none, :default => true
42
42
  parser.opt :force, "Force the push; defaults to true if --rebase is used", :short => :f, :default => false
43
+ parser.opt :local, "Do not do a push; gets remote changes, but does not update the server", :short => :l, :default => false
43
44
 
44
45
  parser.conflicts :rebase, :merge
46
+ parser.conflicts :local, :force
45
47
  end
46
48
 
47
49
 
@@ -71,6 +71,11 @@ module GitProc
71
71
  end
72
72
 
73
73
 
74
+ def integration_branch
75
+ has_a_remote? ? remote_master_branch : master_branch
76
+ end
77
+
78
+
74
79
  def Process.log_level(opts)
75
80
  if opts[:quiet]
76
81
  Logger::ERROR
@@ -31,7 +31,7 @@ module GitProc
31
31
  mybranches.parking.delete
32
32
  new_branch
33
33
  else
34
- checkout(@branch_name, :new_branch => remote_master_branch)
34
+ checkout(@branch_name, :new_branch => integration_branch)
35
35
  end
36
36
  end
37
37
 
@@ -21,8 +21,17 @@ module GitProc
21
21
  class Sync < Process
22
22
 
23
23
  def initialize(dir, opts)
24
+ opts[:force] = true if opts[:rebase]
25
+
26
+ if !opts[:merge].nil? and opts[:merge] == opts[:rebase]
27
+ raise ArgumentError.new(":merge = #{opts[:merge]} and :rebase = #{opts[:rebase]}")
28
+ end
29
+
30
+ raise ArgumentError.new(":rebase is not set") if opts[:rebase].nil?
31
+
24
32
  @do_rebase = opts[:rebase]
25
33
  @force = opts[:force]
34
+ @local = opts[:local]
26
35
  super
27
36
  end
28
37
 
@@ -31,8 +40,8 @@ module GitProc
31
40
  raise UncommittedChangesError.new unless status.clean?
32
41
  raise ParkedChangesError.new(self) if is_parked?
33
42
 
34
- current_branch = branches.current
35
- remote_branch = "#{server_name}/#{current_branch}"
43
+ @current_branch ||= branches.current
44
+ @remote_branch ||= "#{server_name}/#{@current_branch}"
36
45
 
37
46
  fetch(server_name)
38
47
 
@@ -42,19 +51,30 @@ module GitProc
42
51
  proc_merge(remote_master_branch)
43
52
  end
44
53
 
45
- old_sha = command('rev-parse', remote_branch) rescue ''
46
-
47
- unless current_branch == master_branch
48
- fetch(server_name)
49
- new_sha = command('rev-parse', remote_branch) rescue ''
50
- unless old_sha == new_sha
51
- logger.warn("'#{current_branch}' changed on '#{server_name}'"+
52
- " [#{old_sha[0..5]}->#{new_sha[0..5]}]; trying sync again.")
53
- sync_with_server(@do_rebase, @force)
54
- end
55
- push(server_name, current_branch, current_branch, :force => @force)
54
+ if @local
55
+ logger.debug("Not pushing to the server because the user selected local-only.")
56
+ elsif @current_branch == master_branch
57
+ logger.warn("Not pushing to the server because the current branch is the mainline branch.")
56
58
  else
57
- logger.warn("Not pushing to the server because the current branch is the master branch.")
59
+ old_sha = rev_parse(@remote_branch) rescue ''
60
+
61
+ handle_remote_changed(old_sha)
62
+
63
+ push(server_name, @current_branch, @current_branch, :force => @force)
64
+ end
65
+ end
66
+
67
+
68
+ private
69
+
70
+
71
+ def handle_remote_changed(old_sha)
72
+ fetch(server_name)
73
+ new_sha = rev_parse(@remote_branch) rescue ''
74
+ unless old_sha == new_sha
75
+ logger.warn("'#{@current_branch}' changed on '#{server_name}'"+
76
+ " [#{old_sha[0..5]}->#{new_sha[0..5]}]; trying sync again.")
77
+ runner # try again
58
78
  end
59
79
  end
60
80
 
@@ -14,7 +14,7 @@ module GitProc
14
14
  module Version
15
15
  MAJOR = 0
16
16
  MINOR = 9
17
- PATCH = 4
17
+ PATCH = 5
18
18
  BUILD = nil
19
19
 
20
20
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
data/spec/new_fb_spec.rb CHANGED
@@ -56,6 +56,15 @@ describe GitProc::NewFeatureBranch do
56
56
  end
57
57
 
58
58
 
59
+ it "should use 'integration_branch' instead of 'remote_master_branch'" do
60
+ change_file_and_commit('a', '')
61
+
62
+ new_branch = gitprocess.run
63
+
64
+ new_branch.name.should == 'test_branch'
65
+ end
66
+
67
+
59
68
  it "should bring new/uncommitted changes on _parking_ over to the new branch" do
60
69
  gitprocess.branch('origin/master', :base_branch => 'master')
61
70
  gitprocess.checkout('_parking_', :new_branch => 'master')
data/spec/sync_spec.rb CHANGED
@@ -15,51 +15,69 @@ describe GitProc::Sync do
15
15
  end
16
16
 
17
17
 
18
+ def log_level
19
+ Logger::ERROR
20
+ end
21
+
22
+
18
23
  def create_process(dir, opts)
19
- opts[:rebase] = false
20
- opts[:force] = false
21
- GitProc::Sync.new(dir, opts)
24
+ GitProc::Sync.new(dir, opts.merge({:rebase => false, :force => false}))
22
25
  end
23
26
 
24
27
 
25
- describe "#run" do
28
+ it "should work when pushing with fast-forward" do
29
+ change_file_and_commit('a', '')
26
30
 
27
- def log_level
28
- Logger::ERROR
31
+ gitprocess.branch('fb', :base_branch => 'master')
32
+
33
+ clone('fb') do |gp|
34
+ change_file_and_commit('a', 'hello', gp)
35
+ gp.branches.include?('origin/fb').should be_true
36
+ gp.runner
37
+ gp.branches.include?('origin/fb').should be_true
38
+ gitprocess.branches.include?('fb').should be_true
29
39
  end
40
+ end
30
41
 
31
42
 
32
- it "should work when pushing with fast-forward" do
33
- change_file_and_commit('a', '')
43
+ it "should work with a different remote server name" do
44
+ change_file_and_commit('a', '')
34
45
 
35
- gitprocess.branch('fb', :base_branch => 'master')
46
+ gitprocess.branch('fb', :base_branch => 'master')
36
47
 
37
- clone('fb') do |gp|
38
- change_file_and_commit('a', 'hello', gp)
39
- gp.branches.include?('origin/fb').should be_true
40
- gp.run
41
- gp.branches.include?('origin/fb').should be_true
42
- gitprocess.branches.include?('fb').should be_true
43
- end
48
+ clone('fb', 'a_remote') do |gp|
49
+ change_file_and_commit('a', 'hello', gp)
50
+ gp.branches.include?('a_remote/fb').should be_true
51
+ gp.runner
52
+ gp.branches.include?('a_remote/fb').should be_true
53
+ gitprocess.branches.include?('fb').should be_true
44
54
  end
55
+ end
45
56
 
46
57
 
47
- it "should work with a different remote server name" do
48
- change_file_and_commit('a', '')
58
+ it "should fail when pushing with non-fast-forward and no force" do
59
+ change_file_and_commit('a', '')
49
60
 
50
- gitprocess.branch('fb', :base_branch => 'master')
61
+ gitprocess.branch('fb', :base_branch => 'master')
51
62
 
52
- clone('fb', 'a_remote') do |gp|
53
- change_file_and_commit('a', 'hello', gp)
54
- gp.branches.include?('a_remote/fb').should be_true
55
- gp.run
56
- gp.branches.include?('a_remote/fb').should be_true
57
- gitprocess.branches.include?('fb').should be_true
63
+ clone('fb') do |gp|
64
+ gitprocess.checkout('fb') do
65
+ change_file_and_commit('a', 'hello', gitprocess)
58
66
  end
67
+
68
+ expect {gp.runner}.should raise_error GitProc::GitExecuteError
59
69
  end
70
+ end
71
+
60
72
 
73
+ describe "when forcing the push" do
74
+
75
+ def create_process(dir, opts)
76
+ GitProc::Sync.new(dir, opts.merge({:rebase => false, :force => true}))
77
+ end
61
78
 
62
- it "should fail when pushing with non-fast-forward and no force" do
79
+
80
+ it "should work when pushing with non-fast-forward" do
63
81
  change_file_and_commit('a', '')
64
82
 
65
83
  gitprocess.branch('fb', :base_branch => 'master')
@@ -69,81 +87,84 @@ describe GitProc::Sync do
69
87
  change_file_and_commit('a', 'hello', gitprocess)
70
88
  end
71
89
 
72
- expect {gp.run}.should raise_error GitProc::GitExecuteError
90
+ expect {gp.runner}.should_not raise_error GitProc::GitExecuteError
73
91
  end
74
92
  end
75
93
 
76
94
  end
77
95
 
78
96
 
79
- describe "when forcing the push" do
97
+ describe "when rebasing" do
80
98
 
81
99
  def create_process(dir, opts)
82
- opts[:force] = false
83
- opts[:force] = true
84
- GitProc::Sync.new(dir, opts)
100
+ GitProc::Sync.new(dir, opts.merge({:rebase => true, :force => false}))
85
101
  end
86
102
 
87
103
 
88
- it "should work when pushing with non-fast-forward" do
104
+ it "should work when pushing (non-fast-forward)" do
89
105
  change_file_and_commit('a', '')
90
106
 
91
107
  gitprocess.branch('fb', :base_branch => 'master')
92
108
 
93
109
  clone('fb') do |gp|
94
110
  gitprocess.checkout('fb') do
95
- change_file_and_commit('a', 'hello', gp)
111
+ change_file_and_commit('a', 'hello', gitprocess)
96
112
  end
97
113
 
98
- expect {gp.run}.should_not raise_error GitProc::GitExecuteError
114
+ expect {gp.runner}.should_not raise_error GitProc::GitExecuteError
99
115
  end
100
116
  end
101
117
 
102
118
  end
103
119
 
104
120
 
105
- describe "sync_with_server with different remote name" do
106
-
107
- def log_level
108
- Logger::ERROR
109
- end
110
-
121
+ describe "when forcing local-only" do
111
122
 
112
123
  def create_process(dir, opts)
113
- opts[:force] = false
114
- opts[:force] = true
115
- gp = GitProc::Sync.new(dir, opts)
116
- gp.instance_variable_set('@server_name', 'a_remote')
117
- gp
124
+ GitProc::Sync.new(dir, opts.merge({:rebase => true, :force => false, :local => true}))
118
125
  end
119
126
 
120
127
 
121
- it "should work with a different remote server name" do
128
+ it "should not try to push" do
122
129
  change_file_and_commit('a', '')
123
130
 
124
131
  gitprocess.branch('fb', :base_branch => 'master')
125
132
 
126
- clone('fb', 'a_remote') do |gp|
127
- change_file_and_commit('a', 'hello', gp)
128
- gp.branches.include?('a_remote/fb').should be_true
129
- gp.run
130
- gp.branches.include?('a_remote/fb').should be_true
131
- gitprocess.branches.include?('fb').should be_true
133
+ clone('fb') do |gp|
134
+ gitprocess.checkout('fb') do
135
+ change_file_and_commit('a', 'hello', gitprocess)
136
+ end
137
+
138
+ gp.should_receive(:fetch) # want to get remote changes
139
+ gp.should_not_receive(:push) # ...but not push any
140
+
141
+ gp.runner
132
142
  end
133
143
  end
134
144
 
135
145
  end
136
146
 
137
147
 
138
- describe "remove current feature branch when used while on _parking_" do
148
+ it "should work with a different remote server name than 'origin'" do
149
+ change_file_and_commit('a', '')
139
150
 
140
- it 'should fail #sync_with_server' do
141
- gitprocess.checkout('_parking_', :new_branch => 'master')
142
- change_file_and_commit('a', '')
151
+ gitprocess.branch('fb', :base_branch => 'master')
143
152
 
144
- expect {gitprocess.runner}.should raise_error GitProc::ParkedChangesError
153
+ clone('fb', 'a_remote') do |gp|
154
+ change_file_and_commit('a', 'hello', gp)
155
+ gp.branches.include?('a_remote/fb').should be_true
156
+ gp.runner
157
+ gp.branches.include?('a_remote/fb').should be_true
158
+ gitprocess.branches.include?('fb').should be_true
145
159
  end
160
+ end
161
+
162
+
163
+ it 'should fail when removing current feature while on _parking_' do
164
+ gitprocess.checkout('_parking_', :new_branch => 'master')
165
+ change_file_and_commit('a', '')
146
166
 
167
+ expect {gitprocess.runner}.should raise_error GitProc::ParkedChangesError
147
168
  end
148
169
 
149
170
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 9
8
- - 4
9
- version: 0.9.4
8
+ - 5
9
+ version: 0.9.5
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jim Moore
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-07-10 00:00:00 -06:00
17
+ date: 2012-07-18 00:00:00 -06:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency