riquedafreak-github 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -17,7 +17,7 @@ Run it:
17
17
 
18
18
 
19
19
  =============
20
- Pulling Changes
20
+ Pulling Upstream Changes
21
21
  =============
22
22
 
23
23
  Let's say you just forked `github-gem` on GitHub from defunkt.
@@ -44,6 +44,40 @@ master branch, use the `merge` flag:
44
44
 
45
45
  $ github pull --merge defunkt
46
46
 
47
+
48
+ ==========
49
+ Fetching and Evaluation Downstream Changes
50
+ ==========
51
+
52
+ If you are the maintainer of a project, you will often need to fetch commits
53
+ from other developers, evaluate and/or test them, then merge them into the
54
+ project.
55
+
56
+ Let's say you are 'defunkt' and 'mojombo' has forked your 'github-gem' repo,
57
+ made some changes and issues you a pull request for his 'master' branch.
58
+
59
+ From the root of the project, you can do:
60
+
61
+ $ github fetch mojombo master
62
+
63
+ This will leave you in the 'mojombo/master' branch after fetching his commits.
64
+ Your local 'mojombo/master' branch is now at the exact same place as mojombo's
65
+ 'master' branch. You can now run tests or evaluate the code for awesomeness.
66
+
67
+ If mojombo's changes are good, you'll want to merge your 'master' (or another
68
+ branch) into those changes so you can retest post-integration:
69
+
70
+ $ git merge master
71
+
72
+ Test/analyze again and if everything is ok:
73
+
74
+ $ git checkout master
75
+ $ git merge mojombo/master
76
+
77
+ The latter command will be a fast-forward merge since you already did the
78
+ real merge previously.
79
+
80
+
47
81
  ==========
48
82
  Contributors
49
83
  ==========
@@ -52,3 +86,4 @@ Contributors
52
86
  - maddox
53
87
  - halorgium
54
88
  - kballard
89
+ - mojombo
data/commands/commands.rb CHANGED
@@ -61,6 +61,22 @@ command :track do |remote, user|
61
61
  end
62
62
  end
63
63
 
64
+ desc "Fetch from a remote to a local branch."
65
+ command :fetch do |user, branch|
66
+ die "Specify a user to pull from" if user.nil?
67
+ user, branch = user.split("/", 2) if branch.nil?
68
+ branch ||= 'master'
69
+ GitHub.invoke(:track, user) unless helper.tracking?(user)
70
+
71
+ die "Unknown branch (#{branch}) specified" unless helper.remote_branch?(user, branch)
72
+ die "Unable to switch branches, your current branch has uncommitted changes" if helper.branch_dirty?
73
+
74
+ puts "Fetching #{user}/#{branch}"
75
+ git "fetch #{user} #{branch}:refs/remotes/#{user}/#{branch}"
76
+ git "update-ref refs/heads/#{user}/#{branch} refs/remotes/#{user}/#{branch}"
77
+ git_exec "checkout #{user}/#{branch}"
78
+ end
79
+
64
80
  desc "Pull from a remote."
65
81
  flags :merge => "Automatically merge remote's changes into your master."
66
82
  command :pull do |user, branch|
@@ -68,28 +84,15 @@ command :pull do |user, branch|
68
84
  user, branch = user.split("/", 2) if branch.nil?
69
85
  branch ||= 'master'
70
86
  GitHub.invoke(:track, user) unless helper.tracking?(user)
71
-
72
- # check to see if the branch even exists on the remote user
73
- die "Unknown branch (#{branch}) specified" if !helper.heads?(user, branch)
74
-
75
- # if the merge option was specified then don't switch to a new branch
76
- if !options[:merge]
77
- puts "Switching to #{user}/#{branch}"
78
87
 
79
- if helper.heads?(".", "#{user}/#{branch}")
80
- # if the remote branch is already locally tracked then switch to it
81
- die "Unable to checkout branch (#{branch})" if git("checkout #{user}/#{branch}").error?
82
- else
83
- # if the remote branch is not already locally tracked, then fetch it
84
- git_exec "fetch #{user} +#{branch}:#{user}/#{branch}"
85
- die "Unable to checkout branch (#{branch})" if git("checkout #{user}/#{branch}").error?
86
- return
87
- end
88
- else
89
- puts "Merging from contents of #{user}/#{branch}"
88
+ die "Unknown branch (#{branch}) specified" unless helper.remote_branch?(user, branch)
89
+ die "Unable to switch branches, your current branch has uncommitted changes" if helper.branch_dirty?
90
+
91
+ unless options[:merge]
92
+ puts "Switching to #{user}/#{branch}"
93
+ git "update-ref refs/heads/#{user}/#{branch} HEAD"
94
+ git "checkout #{user}/#{branch}"
90
95
  end
91
-
92
- # get the contents of the branch, merging it with whatever is in the local branch
93
96
  git_exec "pull #{user} #{branch}"
94
97
  end
95
98
 
@@ -120,4 +123,4 @@ command :'pull-request' do |user, branch|
120
123
 
121
124
  git_exec "request-pull #{user}/#{branch} origin"
122
125
  end
123
- end
126
+ end
data/commands/helpers.rb CHANGED
@@ -36,7 +36,7 @@ end
36
36
 
37
37
  helper :remotes do
38
38
  regexp = '^remote\.(.+)\.url$'
39
- `git config --get-regexp '#{regexp}'`.split(/\n/).inject({}) do |memo, line|
39
+ `git config --get-regexp '#{regexp}'`.split("\n").inject({}) do |memo, line|
40
40
  name_string, url = line.split(/ /, 2)
41
41
  m, name = *name_string.match(/#{regexp}/)
42
42
  memo[name.to_sym] = url
@@ -44,20 +44,25 @@ helper :remotes do
44
44
  end
45
45
  end
46
46
 
47
- helper :heads do |user|
48
- results = `git ls-remote -h #{user} 2> /dev/null`
49
- results = `git rev-parse --symbolic-full-name --branches 2> /dev/null | grep refs/heads/#{user}` if $?.exitstatus != 0
50
-
51
- results.split(/\n/).inject({}) do |memo, line|
47
+ helper :remote_branches_for do |user|
48
+ `git ls-remote -h #{user} 2> /dev/null`.split(/\n/).inject({}) do |memo, line|
52
49
  hash, head = line.split(/\t/, 2)
53
- head = line.scan(/refs\/heads\/(.+)$/)[0][0]
54
- memo[head] = hash
50
+ head = head[%r{refs/heads/(.+)$},1] unless head.nil?
51
+ memo[head] = hash unless head.nil?
55
52
  memo
56
- end
53
+ end if !(user.nil? || user.strip.empty?)
54
+ end
55
+
56
+ helper :remote_branch? do |user, branch|
57
+ remote_branches_for(user).key?(branch)
57
58
  end
58
59
 
59
- helper :heads? do |user, branch|
60
- heads(user).key?(branch)
60
+ helper :branch_dirty? do
61
+ # see if there are any cached or tracked files that have been modified
62
+ # originally, we were going to use git-ls-files but that could only
63
+ # report modified track files...not files that have been staged
64
+ # for committal
65
+ !(system("git diff --quiet 2>/dev/null") or !system("git diff --cached --quiet 2>/dev/null"))
61
66
  end
62
67
 
63
68
  helper :tracking do
@@ -40,11 +40,11 @@ module GitHub
40
40
  def git_exec(*command)
41
41
  cmdstr = ['git', command].flatten.join(' ')
42
42
  GitHub.debug "exec: #{cmdstr}"
43
- `#{cmdstr}`
43
+ exec cmdstr
44
44
  end
45
45
 
46
46
  def sh(*command)
47
- return Shell.new(*command).run
47
+ Shell.new(*command).run
48
48
  end
49
49
 
50
50
  def die(message)
@@ -53,17 +53,24 @@ module GitHub
53
53
  end
54
54
 
55
55
  class Shell < String
56
+ attr_reader :error
57
+ attr_reader :out
58
+
56
59
  def initialize(*command)
57
60
  @command = command
58
61
  end
59
62
 
60
63
  def run
61
64
  GitHub.debug "sh: #{command}"
62
-
63
- out = `#{command} 2>&1`
64
- replace @error = $?.exitstatus.to_s if $?.exitstatus != 0
65
+ _, out, err = Open3.popen3(*@command)
66
+
67
+ out = out.read.strip
68
+ err = err.read.strip
69
+
70
+ replace @error = err if err.any?
65
71
  replace @out = out if out.any?
66
- return self
72
+
73
+ self
67
74
  end
68
75
 
69
76
  def command
@@ -73,7 +80,7 @@ module GitHub
73
80
  def error?
74
81
  !!@error
75
82
  end
76
-
83
+
77
84
  def out?
78
85
  !!@out
79
86
  end
data/spec/command_spec.rb CHANGED
@@ -24,14 +24,25 @@ describe GitHub::Command do
24
24
  unguard(Kernel, :exec)
25
25
  hi = @command.sh("echo hi")
26
26
  hi.should == "hi"
27
+ hi.out.should == "hi"
27
28
  hi.out?.should be(true)
29
+ hi.error.should be_nil
28
30
  hi.error?.should be(false)
29
31
  hi.command.should == "echo hi"
30
32
  bye = @command.sh("echo bye >&2")
31
33
  bye.should == "bye"
34
+ bye.out.should be_nil
32
35
  bye.out?.should be(false)
36
+ bye.error.should == "bye"
33
37
  bye.error?.should be(true)
34
38
  bye.command.should == "echo bye >&2"
39
+ hi_and_bye = @command.sh("echo hi; echo bye >&2")
40
+ hi_and_bye.should == "hi"
41
+ hi_and_bye.out.should == "hi"
42
+ hi_and_bye.out?.should be(true)
43
+ hi_and_bye.error.should == "bye"
44
+ hi_and_bye.error?.should be(true)
45
+ hi_and_bye.command.should == "echo hi; echo bye >&2"
35
46
  end
36
47
 
37
48
  it "should return the results of a git operation" do
data/spec/helper_spec.rb CHANGED
@@ -149,6 +149,67 @@ remote.nex3.url git://github.com/nex3/github-gem.git
149
149
  end
150
150
  end
151
151
 
152
+ helper :remote_branches_for do
153
+ it "should return an empty list because no user was provided" do
154
+ @helper.remote_branches_for(nil).should == nil
155
+ end
156
+
157
+ it "should return a list of remote branches for defunkt" do
158
+ @helper.should_receive(:`).with('git ls-remote -h defunkt 2> /dev/null').and_return <<-EOF
159
+ fe1f852f3cf719c7cd86147031732f570ad89619 refs/heads/kballard/master
160
+ f8a6bb42b0ed43ac7336bfcda246e59a9da949d6 refs/heads/master
161
+ 624d9c2f742ff24a79353a7e02bf289235c72ff1 refs/heads/restart
162
+ EOF
163
+ @helper.remote_branches_for("defunkt").should == {
164
+ "master" => "f8a6bb42b0ed43ac7336bfcda246e59a9da949d6",
165
+ "kballard/master" => "fe1f852f3cf719c7cd86147031732f570ad89619",
166
+ "restart" => "624d9c2f742ff24a79353a7e02bf289235c72ff1"
167
+ }
168
+ end
169
+
170
+ it "should return an empty list of remote branches for nex3 and nex4" do
171
+ # the following use-case should never happen as the -h parameter should only return heads on remote branches
172
+ # however, we are testing this particular case to verify how remote_branches_for would respond if random
173
+ # git results
174
+ @helper.should_receive(:`).with('git ls-remote -h nex3 2> /dev/null').and_return <<-EOF
175
+ fe1f852f3cf719c7cd86147031732f570ad89619 HEAD
176
+ a1a392369e5b7842d01cce965272d4b96c2fd343 refs/tags/v0.1.3
177
+ 624d9c2f742ff24a79353a7e02bf289235c72ff1 refs/remotes/origin/master
178
+ random
179
+ random_again
180
+ EOF
181
+ @helper.remote_branches_for("nex3").should be_empty
182
+
183
+ @helper.should_receive(:`).with('git ls-remote -h nex4 2> /dev/null').and_return ""
184
+ @helper.remote_branches_for("nex4").should be_empty
185
+ end
186
+ end
187
+
188
+ helper :remote_branch? do
189
+ it "should return whether the branch exists at the remote user" do
190
+ @helper.should_receive(:remote_branches_for).with("defunkt").any_number_of_times.and_return({
191
+ "master" => "f8a6bb42b0ed43ac7336bfcda246e59a9da949d6",
192
+ "kballard/master" => "fe1f852f3cf719c7cd86147031732f570ad89619",
193
+ "restart" => "624d9c2f742ff24a79353a7e02bf289235c72ff1"
194
+ })
195
+ @helper.remote_branch?("defunkt", "master").should == true
196
+ @helper.remote_branch?("defunkt", "not_master").should == false
197
+ end
198
+ end
199
+
200
+ helper :branch_dirty? do
201
+ it "should return false" do
202
+ @helper.should_receive(:system).with(/^git diff/).and_return(0, 0)
203
+ @helper.branch_dirty?.should == 0
204
+ end
205
+
206
+ it "should return true" do
207
+ @helper.should_receive(:system).with(/^git diff/).and_return(1, 1, 0, 1)
208
+ @helper.branch_dirty?.should == 1
209
+ @helper.branch_dirty?.should == 1
210
+ end
211
+ end
212
+
152
213
  helper :tracking do
153
214
  it "should return a list of remote/user_or_url pairs" do
154
215
  @helper.should_receive(:remotes).and_return({
data/spec/ui_spec.rb CHANGED
@@ -157,16 +157,16 @@ EOF
157
157
  end
158
158
  end
159
159
 
160
- # -- pull --
161
- specify "pull should die with no args" do
162
- running :pull do
163
- @command.should_receive(:die).with("Specify a user to pull from").and_return { raise "Died" }
160
+ # -- fetch --
161
+ specify "fetch should die with no args" do
162
+ running :fetch do
163
+ @command.should_receive(:die).with("Specify a user to pull from").and_return { raise "Died "}
164
164
  self.should raise_error("Died")
165
165
  end
166
166
  end
167
167
 
168
- specify "pull defunkt should start tracking defunkt if they're not already tracked" do
169
- running :pull, "defunkt" do
168
+ specify "fetch defunkt should start tracking defunkt if they're not already tracked" do
169
+ running :fetch, "defunkt" do
170
170
  setup_remote(:origin, :user => "user", :ssh => true)
171
171
  setup_remote(:external, :url => "home:/path/to/project.git")
172
172
  GitHub.should_receive(:invoke).with(:track, "defunkt").and_return { raise "Tracked" }
@@ -174,56 +174,98 @@ EOF
174
174
  end
175
175
  end
176
176
 
177
- specify "pull defunkt should create defunkt/master and pull from the defunkt remote" do
178
- running :pull, "defunkt" do
177
+ specify "fetch defunkt should create defunkt/master and fetch from the defunkt remote" do
178
+ running :fetch, "defunkt" do
179
179
  setup_remote(:defunkt)
180
- @command.should_receive(:git).with("checkout -b defunkt/master").ordered.and_return do
181
- mock("checkout -b defunkt/master").tap { |m| m.stub!(:error?) }
182
- end
183
- @command.should_receive(:git_exec).with("pull defunkt master").ordered
180
+ @helper.should_receive(:branch_dirty?).and_return false
181
+ @command.should_receive(:git).with("update-ref refs/heads/defunkt/master HEAD").ordered
182
+ @command.should_receive(:git).with("checkout defunkt/master").ordered
183
+ @command.should_receive(:git_exec).with("fetch defunkt master").ordered
184
184
  stdout.should == "Switching to defunkt/master\n"
185
185
  end
186
186
  end
187
187
 
188
- specify "pull defunkt should switch to pre-existing defunkt/master and pull from the defunkt remote" do
189
- running :pull, "defunkt" do
188
+ specify "fetch defunkt should die if there is a dirty branch" do
189
+ running :fetch, "defunkt" do
190
190
  setup_remote(:defunkt)
191
- @command.should_receive(:git).with("checkout -b defunkt/master").ordered.and_return do
192
- mock("checkout -b defunkt/master").tap { |m| m.should_receive(:error?) { true } }
193
- end
191
+ @helper.should_receive(:branch_dirty?).and_return true
192
+ @command.should_receive(:die).with("Unable to switch branches, your current branch has uncommitted changes").and_return { raise "Died" }
193
+ self.should raise_error("Died")
194
+ end
195
+ end
196
+
197
+ specify "fetch defunkt/wip should create defunkt/wip and fetch from wip branch on defunkt remote" do
198
+ running :fetch, "defunkt/wip" do
199
+ setup_remote(:defunkt, :remote_branches => ["master", "wip"])
200
+ @helper.should_receive(:branch_dirty?).and_return false
201
+ @command.should_receive(:git).with("update-ref refs/heads/defunkt/wip HEAD").ordered
202
+ @command.should_receive(:git).with("checkout defunkt/wip").ordered
203
+ @command.should_receive(:git_exec).with("fetch defunkt wip").ordered
204
+ stdout.should == "Switching to defunkt/wip\n"
205
+ end
206
+ end
207
+
208
+ specify "fetch --merge defunkt should fetch from defunkt remote into current branch" do
209
+ running :fetch, "--merge", "defunkt" do
210
+ setup_remote(:defunkt)
211
+ @helper.should_receive(:branch_dirty?).and_return false
212
+ @command.should_receive(:git_exec).with("fetch defunkt master")
213
+ end
214
+ end
215
+
216
+ # -- fetch --
217
+ specify "fetch should die with no args" do
218
+ running :fetch do
219
+ @command.should_receive(:die).with("Specify a user to fetch from").and_return { raise "Died" }
220
+ self.should raise_error("Died")
221
+ end
222
+ end
223
+
224
+ specify "fetch defunkt should start tracking defunkt if they're not already tracked" do
225
+ running :fetch, "defunkt" do
226
+ setup_remote(:origin, :user => "user", :ssh => true)
227
+ setup_remote(:external, :url => "home:/path/to/project.git")
228
+ GitHub.should_receive(:invoke).with(:track, "defunkt").and_return { raise "Tracked" }
229
+ self.should raise_error("Tracked")
230
+ end
231
+ end
232
+
233
+ specify "fetch defunkt should create defunkt/master and fetch from the defunkt remote" do
234
+ running :fetch, "defunkt" do
235
+ setup_remote(:defunkt)
236
+ @helper.should_receive(:branch_dirty?).and_return false
237
+ @command.should_receive(:git).with("update-ref refs/heads/defunkt/master HEAD").ordered
194
238
  @command.should_receive(:git).with("checkout defunkt/master").ordered
195
- @command.should_receive(:git_exec).with("pull defunkt master").ordered
239
+ @command.should_receive(:git_exec).with("fetch defunkt master").ordered
196
240
  stdout.should == "Switching to defunkt/master\n"
197
241
  end
198
242
  end
199
243
 
200
- specify "pull defunkt wip should create defunkt/wip and pull from wip branch on defunkt remote" do
201
- running :pull, "defunkt", "wip" do
244
+ specify "fetch defunkt should die if there is a dirty branch" do
245
+ running :fetch, "defunkt" do
202
246
  setup_remote(:defunkt)
203
- @command.should_receive(:git).with("checkout -b defunkt/wip").ordered.and_return do
204
- mock("checkout -b defunkt/wip").tap { |m| m.stub!(:error?) }
205
- end
206
- @command.should_receive(:git_exec).with("pull defunkt wip").ordered
207
- stdout.should == "Switching to defunkt/wip\n"
247
+ @helper.should_receive(:branch_dirty?).and_return true
248
+ @command.should_receive(:die).with("Unable to switch branches, your current branch has uncommitted changes").and_return { raise "Died" }
249
+ self.should raise_error("Died")
208
250
  end
209
251
  end
210
252
 
211
- specify "pull defunkt/wip should switch to pre-existing defunkt/wip and pull from wip branch on defunkt remote" do
212
- running :pull, "defunkt/wip" do
213
- setup_remote(:defunkt)
214
- @command.should_receive(:git).with("checkout -b defunkt/wip").ordered.and_return do
215
- mock("checkout -b defunkt/wip").tap { |m| m.should_receive(:error?) { true } }
216
- end
253
+ specify "fetch defunkt/wip should create defunkt/wip and fetch from wip branch on defunkt remote" do
254
+ running :fetch, "defunkt/wip" do
255
+ setup_remote(:defunkt, :remote_branches => ["master", "wip"])
256
+ @helper.should_receive(:branch_dirty?).and_return false
257
+ @command.should_receive(:git).with("update-ref refs/heads/defunkt/wip HEAD").ordered
217
258
  @command.should_receive(:git).with("checkout defunkt/wip").ordered
218
- @command.should_receive(:git_exec).with("pull defunkt wip").ordered
259
+ @command.should_receive(:git_exec).with("fetch defunkt wip").ordered
219
260
  stdout.should == "Switching to defunkt/wip\n"
220
261
  end
221
262
  end
222
263
 
223
- specify "pull --merge defunkt should pull from defunkt remote into current branch" do
224
- running :pull, "--merge", "defunkt" do
264
+ specify "fetch --merge defunkt should fetch from defunkt remote into current branch" do
265
+ running :fetch, "--merge", "defunkt" do
225
266
  setup_remote(:defunkt)
226
- @command.should_receive(:git_exec).with("pull defunkt master")
267
+ @helper.should_receive(:branch_dirty?).and_return false
268
+ @command.should_receive(:git_exec).with("fetch defunkt master")
227
269
  end
228
270
  end
229
271
 
@@ -390,12 +432,14 @@ EOF
390
432
  @stderr_mock.invoke unless @stderr_mock.nil?
391
433
  end
392
434
 
393
- def setup_remote(remote, options = {:user => nil, :project => "project"})
435
+ def setup_remote(remote, options = {:user => nil, :project => "project", :remote_branches => nil})
394
436
  @remotes ||= {}
437
+ @remote_branches ||= {}
395
438
  user = options[:user] || remote
396
439
  project = options[:project]
397
440
  ssh = options[:ssh]
398
441
  url = options[:url]
442
+ remote_branches = options[:remote_branches] || ["master"]
399
443
  if url
400
444
  @remotes[remote.to_sym] = url
401
445
  elsif ssh
@@ -403,6 +447,11 @@ EOF
403
447
  else
404
448
  @remotes[remote.to_sym] = "git://github.com/#{user}/#{project}.git"
405
449
  end
450
+
451
+ @remote_branches[remote.to_sym] = (@remote_branches[remote.to_sym] || Array.new) | remote_branches
452
+ @helper.should_receive(:remote_branch?).any_number_of_times.and_return do |remote, branch|
453
+ @remote_branches.fetch(remote.to_sym,[]).include?(branch)
454
+ end
406
455
  end
407
456
 
408
457
  def mock_remotes()
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riquedafreak-github
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Wanstrath, Kevin Ballard