riquedafreak-github 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Chris Wanstrath
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,17 @@
1
+ bin/github
2
+ commands/commands.rb
3
+ commands/helpers.rb
4
+ lib/github/extensions.rb
5
+ lib/github/command.rb
6
+ lib/github/helper.rb
7
+ lib/github.rb
8
+ LICENSE
9
+ Manifest
10
+ README
11
+ spec/command_spec.rb
12
+ spec/extensions_spec.rb
13
+ spec/github_spec.rb
14
+ spec/helper_spec.rb
15
+ spec/spec_helper.rb
16
+ spec/ui_spec.rb
17
+ spec/windoze_spec.rb
data/README ADDED
@@ -0,0 +1,54 @@
1
+ The GitHub Gem
2
+ =============
3
+
4
+ This gem'll work hand-in-hand with GitHub's API to help you out.
5
+
6
+ Catch us in the #github room on freenode if you want to get involved. Or just fork and send a pull request.
7
+
8
+ ===========
9
+ Getting started
10
+ ===========
11
+
12
+ $ gem install defunkt-github -s http://gems.github.com
13
+
14
+ Run it:
15
+
16
+ $ github <command> <args>
17
+
18
+
19
+ =============
20
+ Pulling Changes
21
+ =============
22
+
23
+ Let's say you just forked `github-gem` on GitHub from defunkt.
24
+
25
+ $ git clone git://github.com/YOU/github-gem.git
26
+ $ cd github-gem
27
+ $ github pull defunkt
28
+
29
+ This will setup a remote and branch for defunkt's repository at master.
30
+ In this case, a 'defunkt/master' branch.
31
+
32
+ If defunkt makes some changes you want, simply `github pull defunkt`. This will
33
+ leave you in the 'defunkt/master' branch after pulling changes from defunkt's
34
+ remote. After confirming that defunkt's changes were what you wanted, run `git
35
+ checkout master` and then `git merge defunkt/master` to merge defunkt's changes
36
+ into your own master branch. In summary:
37
+
38
+ $ github pull defunkt
39
+ $ git checkout master
40
+ $ git merge defunkt/master
41
+
42
+ If you've already reviewed defunkt's changes and just want to merge them into your
43
+ master branch, use the `merge` flag:
44
+
45
+ $ github pull --merge defunkt
46
+
47
+ ==========
48
+ Contributors
49
+ ==========
50
+
51
+ - defunkt
52
+ - maddox
53
+ - halorgium
54
+ - kballard
data/bin/github ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/github'
4
+
5
+ GitHub.activate ARGV
@@ -0,0 +1,123 @@
1
+ desc "Open this repo's master branch in a web browser."
2
+ command :home do |user|
3
+ if helper.project
4
+ helper.open helper.homepage_for(user || helper.owner, 'master')
5
+ end
6
+ end
7
+
8
+ desc "Open this repo in a web browser."
9
+ command :browse do |user, branch|
10
+ if helper.project
11
+ # if one arg given, treat it as a branch name
12
+ # unless it maches user/branch, then split it
13
+ # if two args given, treat as user branch
14
+ # if no args given, use defaults
15
+ user, branch = user.split("/", 2) if branch.nil? unless user.nil?
16
+ branch = user and user = nil if branch.nil?
17
+ user ||= helper.branch_user
18
+ branch ||= helper.branch_name
19
+ helper.open helper.homepage_for(user, branch)
20
+ end
21
+ end
22
+
23
+ desc "Open the network page for this repo in a web browser."
24
+ command :network do |user|
25
+ if helper.project
26
+ user ||= helper.owner
27
+ helper.open helper.network_page_for(user)
28
+ end
29
+ end
30
+
31
+ desc "Info about this project."
32
+ command :info do
33
+ puts "== Info for #{helper.project}"
34
+ puts "You are #{helper.owner}"
35
+ puts "Currently tracking:"
36
+ helper.tracking.sort { |(a,),(b,)| a == :origin ? -1 : b == :origin ? 1 : a.to_s <=> b.to_s }.each do |(name,user_or_url)|
37
+ puts " - #{user_or_url} (as #{name})"
38
+ end
39
+ end
40
+
41
+ desc "Track another user's repository."
42
+ flags :private => "Use git@github.com: instead of git://github.com/."
43
+ flags :ssh => 'Equivalent to --private'
44
+ command :track do |remote, user|
45
+ # track remote user
46
+ # track remote user/repo
47
+ # track user
48
+ # track user/repo
49
+ user, remote = remote, nil if user.nil?
50
+ die "Specify a user to track" if user.nil?
51
+ user, repo = user.split("/", 2)
52
+ die "Already tracking #{user}" if helper.tracking?(user)
53
+ repo = @helper.project if repo.nil?
54
+ repo.chomp!(".git")
55
+ remote ||= user
56
+
57
+ if options[:private] || options[:ssh]
58
+ git "remote add #{remote} #{helper.private_url_for_user_and_repo(user, repo)}"
59
+ else
60
+ git "remote add #{remote} #{helper.public_url_for_user_and_repo(user, repo)}"
61
+ end
62
+ end
63
+
64
+ desc "Pull from a remote."
65
+ flags :merge => "Automatically merge remote's changes into your master."
66
+ command :pull do |user, branch|
67
+ die "Specify a user to pull from" if user.nil?
68
+ user, branch = user.split("/", 2) if branch.nil?
69
+ branch ||= 'master'
70
+ 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
+
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}"
90
+ end
91
+
92
+ # get the contents of the branch, merging it with whatever is in the local branch
93
+ git_exec "pull #{user} #{branch}"
94
+ end
95
+
96
+ desc "Clone a repo."
97
+ flags :ssh => "Clone using the git@github.com style url."
98
+ command :clone do |user, repo, dir|
99
+ die "Specify a user to pull from" if user.nil?
100
+ if user.include? ?/
101
+ die "Expected user/repo dir, given extra argument" if dir
102
+ (user, repo), dir = [user.split('/', 2), repo]
103
+ end
104
+ die "Specify a repo to pull from" if repo.nil?
105
+
106
+ if options[:ssh]
107
+ git_exec "clone git@github.com:#{user}/#{repo}.git" + (dir ? " #{dir}" : "")
108
+ else
109
+ git_exec "clone git://github.com/#{user}/#{repo}.git" + (dir ? " #{dir}" : "")
110
+ end
111
+ end
112
+
113
+ desc "Generate the text for a pull request."
114
+ command :'pull-request' do |user, branch|
115
+ if helper.project
116
+ die "Specify a user for the pull request" if user.nil?
117
+ user, branch = user.split('/', 2) if branch.nil?
118
+ branch ||= 'master'
119
+ GitHub.invoke(:track, user) unless helper.tracking?(user)
120
+
121
+ git_exec "request-pull #{user}/#{branch} origin"
122
+ end
123
+ end
@@ -0,0 +1,139 @@
1
+ helper :user_and_repo_from do |url|
2
+ case url
3
+ when %r|^git://github\.com/([^/]+/[^/]+)$|: $1.split('/')
4
+ when %r|^(?:ssh://)?(?:git@)?github\.com:([^/]+/[^/]+)$|: $1.split('/')
5
+ end
6
+ end
7
+
8
+ helper :user_and_repo_for do |remote|
9
+ user_and_repo_from(url_for(remote))
10
+ end
11
+
12
+ helper :user_for do |remote|
13
+ user_and_repo_for(remote).try.first
14
+ end
15
+
16
+ helper :repo_for do |remote|
17
+ user_and_repo_for(remote).try.last
18
+ end
19
+
20
+ helper :project do
21
+ repo = repo_for(:origin)
22
+ if repo.nil?
23
+ if url_for(:origin) == ""
24
+ STDERR.puts "Error: missing remote 'origin'"
25
+ else
26
+ STDERR.puts "Error: remote 'origin' is not a github URL"
27
+ end
28
+ exit 1
29
+ end
30
+ repo.chomp('.git')
31
+ end
32
+
33
+ helper :url_for do |remote|
34
+ `git config --get remote.#{remote}.url`.chomp
35
+ end
36
+
37
+ helper :remotes do
38
+ regexp = '^remote\.(.+)\.url$'
39
+ `git config --get-regexp '#{regexp}'`.split(/\n/).inject({}) do |memo, line|
40
+ name_string, url = line.split(/ /, 2)
41
+ m, name = *name_string.match(/#{regexp}/)
42
+ memo[name.to_sym] = url
43
+ memo
44
+ end
45
+ end
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|
52
+ hash, head = line.split(/\t/, 2)
53
+ head = line.scan(/refs\/heads\/(.+)$/)[0][0]
54
+ memo[head] = hash
55
+ memo
56
+ end
57
+ end
58
+
59
+ helper :heads? do |user, branch|
60
+ heads(user).key?(branch)
61
+ end
62
+
63
+ helper :tracking do
64
+ remotes.inject({}) do |memo, (name, url)|
65
+ if ur = user_and_repo_from(url)
66
+ memo[name] = ur.first
67
+ else
68
+ memo[name] = url
69
+ end
70
+ memo
71
+ end
72
+ end
73
+
74
+ helper :tracking? do |user|
75
+ tracking.values.include?(user)
76
+ end
77
+
78
+ helper :owner do
79
+ user_for(:origin)
80
+ end
81
+
82
+ helper :user_and_branch do
83
+ raw_branch = `git rev-parse --symbolic-full-name HEAD`.chomp.sub(/^refs\/heads\//, '')
84
+ user, branch = raw_branch.split(/\//, 2)
85
+ if branch
86
+ [user, branch]
87
+ else
88
+ [owner, user]
89
+ end
90
+ end
91
+
92
+ helper :branch_user do
93
+ user_and_branch.first
94
+ end
95
+
96
+ helper :branch_name do
97
+ user_and_branch.last
98
+ end
99
+
100
+ helper :public_url_for_user_and_repo do |user, repo|
101
+ "git://github.com/#{user}/#{repo}.git"
102
+ end
103
+
104
+ helper :private_url_for_user_and_repo do |user, repo|
105
+ "git@github.com:#{user}/#{repo}.git"
106
+ end
107
+
108
+ helper :public_url_for do |user|
109
+ public_url_for_user_and_repo user, project
110
+ end
111
+
112
+ helper :private_url_for do |user|
113
+ private_url_for_user_and_repo user, project
114
+ end
115
+
116
+ helper :homepage_for do |user, branch|
117
+ "https://github.com/#{user}/#{project}/tree/#{branch}"
118
+ end
119
+
120
+ helper :network_page_for do |user|
121
+ "https://github.com/#{user}/#{project}/network"
122
+ end
123
+
124
+ helper :has_launchy? do |blk|
125
+ begin
126
+ gem 'launchy'
127
+ require 'launchy'
128
+ blk.call
129
+ rescue Gem::LoadError
130
+ STDERR.puts "Sorry, you need to install launchy: `gem install launchy`"
131
+ end
132
+ end
133
+
134
+ helper :open do |url|
135
+ has_launchy? proc {
136
+ Launchy::Browser.new.visit url
137
+ }
138
+ end
139
+
@@ -0,0 +1,82 @@
1
+ if RUBY_PLATFORM =~ /mswin|mingw/
2
+ begin
3
+ require 'win32/open3'
4
+ rescue LoadError
5
+ warn "You must 'gem install win32-open3' to use the github command on Windows"
6
+ exit 1
7
+ end
8
+ else
9
+ require 'open3'
10
+ end
11
+
12
+ module GitHub
13
+ class Command
14
+ def initialize(block)
15
+ (class << self;self end).send :define_method, :command, &block
16
+ end
17
+
18
+ def call(*args)
19
+ arity = method(:command).arity
20
+ args << nil while args.size < arity
21
+ send :command, *args
22
+ end
23
+
24
+ def helper
25
+ @helper ||= Helper.new
26
+ end
27
+
28
+ def options
29
+ GitHub.options
30
+ end
31
+
32
+ def pgit(*command)
33
+ puts git(*command)
34
+ end
35
+
36
+ def git(*command)
37
+ sh ['git', command].flatten.join(' ')
38
+ end
39
+
40
+ def git_exec(*command)
41
+ cmdstr = ['git', command].flatten.join(' ')
42
+ GitHub.debug "exec: #{cmdstr}"
43
+ `#{cmdstr}`
44
+ end
45
+
46
+ def sh(*command)
47
+ return Shell.new(*command).run
48
+ end
49
+
50
+ def die(message)
51
+ puts "=> #{message}"
52
+ exit!
53
+ end
54
+
55
+ class Shell < String
56
+ def initialize(*command)
57
+ @command = command
58
+ end
59
+
60
+ def run
61
+ GitHub.debug "sh: #{command}"
62
+
63
+ out = `#{command} 2>&1`
64
+ replace @error = $?.exitstatus.to_s if $?.exitstatus != 0
65
+ replace @out = out if out.any?
66
+ return self
67
+ end
68
+
69
+ def command
70
+ @command.join(' ')
71
+ end
72
+
73
+ def error?
74
+ !!@error
75
+ end
76
+
77
+ def out?
78
+ !!@out
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,28 @@
1
+ # define #try
2
+ class Object
3
+ def try
4
+ self
5
+ end
6
+ end
7
+
8
+ class NilClass
9
+ klass = Class.new
10
+ klass.class_eval do
11
+ instance_methods.each { |meth| undef_method meth.to_sym unless meth =~ /^__(id|send)__$/ }
12
+ def method_missing(*args)
13
+ self
14
+ end
15
+ end
16
+ NilProxy = klass.new
17
+ def try
18
+ NilProxy
19
+ end
20
+ end
21
+
22
+ # define #tap
23
+ class Object
24
+ def tap(&block)
25
+ block.call(self)
26
+ self
27
+ end
28
+ end
@@ -0,0 +1,4 @@
1
+ module GitHub
2
+ class Helper
3
+ end
4
+ end
data/lib/github.rb ADDED
@@ -0,0 +1,127 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'github/extensions'
3
+ require 'github/command'
4
+ require 'github/helper'
5
+ require 'rubygems'
6
+
7
+ ##
8
+ # Starting simple.
9
+ #
10
+ # $ github <command> <args>
11
+ #
12
+ # GitHub.command <command> do |*args|
13
+ # whatever
14
+ # end
15
+ #
16
+ # We'll probably want to use the `choice` gem for concise, tasty DSL
17
+ # arg parsing action.
18
+ #
19
+
20
+ module GitHub
21
+ extend self
22
+
23
+ BasePath = File.expand_path(File.dirname(__FILE__) + '/..')
24
+
25
+ def command(command, &block)
26
+ debug "Registered `#{command}`"
27
+ descriptions[command] = @next_description if @next_description
28
+ @next_description = nil
29
+ flag_descriptions[command].update @next_flags if @next_flags
30
+ @next_flags = nil
31
+ commands[command.to_s] = Command.new(block)
32
+ end
33
+
34
+ def desc(str)
35
+ @next_description = str
36
+ end
37
+
38
+ def flags(hash)
39
+ @next_flags ||= {}
40
+ @next_flags.update hash
41
+ end
42
+
43
+ def helper(command, &block)
44
+ debug "Helper'd `#{command}`"
45
+ Helper.send :define_method, command, &block
46
+ end
47
+
48
+ def activate(args)
49
+ @options = parse_options(args)
50
+ @debug = @options[:debug]
51
+ load 'helpers.rb'
52
+ load 'commands.rb'
53
+ invoke(args.shift, *args)
54
+ end
55
+
56
+ def invoke(command, *args)
57
+ block = commands[command.to_s] || commands['default']
58
+ debug "Invoking `#{command}`"
59
+ block.call(*args)
60
+ end
61
+
62
+ def commands
63
+ @commands ||= {}
64
+ end
65
+
66
+ def descriptions
67
+ @descriptions ||= {}
68
+ end
69
+
70
+ def flag_descriptions
71
+ @flagdescs ||= Hash.new { |h, k| h[k] = {} }
72
+ end
73
+
74
+ def options
75
+ @options
76
+ end
77
+
78
+ def parse_options(args)
79
+ idx = 0
80
+ args.clone.inject({}) do |memo, arg|
81
+ case arg
82
+ when /^--(.+?)=(.*)/
83
+ args.delete_at(idx)
84
+ memo.merge($1.to_sym => $2)
85
+ when /^--(.+)/
86
+ args.delete_at(idx)
87
+ memo.merge($1.to_sym => true)
88
+ when "--"
89
+ args.delete_at(idx)
90
+ return memo
91
+ else
92
+ idx += 1
93
+ memo
94
+ end
95
+ end
96
+ end
97
+
98
+ def load(file)
99
+ file[0] == ?/ ? path = file : path = BasePath + "/commands/#{file}"
100
+ data = File.read(path)
101
+ GitHub.module_eval data, path
102
+ end
103
+
104
+ def debug(*messages)
105
+ puts *messages.map { |m| "== #{m}" } if debug?
106
+ end
107
+
108
+ def debug?
109
+ !!@debug
110
+ end
111
+ end
112
+
113
+ GitHub.command :default do
114
+ puts "Usage: github command <space separated arguments>", ''
115
+ puts "Available commands:", ''
116
+ longest = GitHub.descriptions.map { |d,| d.to_s.size }.max
117
+ GitHub.descriptions.each do |command, desc|
118
+ cmdstr = "%-#{longest}s" % command
119
+ puts " #{cmdstr} => #{desc}"
120
+ flongest = GitHub.flag_descriptions[command].map { |d,| "--#{d}".size }.max
121
+ GitHub.flag_descriptions[command].each do |flag, fdesc|
122
+ flagstr = "#{" " * longest} %-#{flongest}s" % "--#{flag}"
123
+ puts " #{flagstr}: #{fdesc}"
124
+ end
125
+ end
126
+ puts
127
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe GitHub::Command do
4
+ before(:each) do
5
+ @command = GitHub::Command.new(proc { |x| puts x })
6
+ end
7
+
8
+ it "should return a GitHub::Helper" do
9
+ @command.helper.should be_instance_of(GitHub::Helper)
10
+ end
11
+
12
+ it "should call successfully" do
13
+ @command.should_receive(:puts).with("test").once
14
+ @command.call("test")
15
+ end
16
+
17
+ it "should return options" do
18
+ GitHub.should_receive(:options).with().once.and_return({:ssh => true})
19
+ @command.options.should == {:ssh => true}
20
+ end
21
+
22
+ it "should successfully call out to the shell" do
23
+ unguard(Kernel, :fork)
24
+ unguard(Kernel, :exec)
25
+ hi = @command.sh("echo hi")
26
+ hi.should == "hi"
27
+ hi.out?.should be(true)
28
+ hi.error?.should be(false)
29
+ hi.command.should == "echo hi"
30
+ bye = @command.sh("echo bye >&2")
31
+ bye.should == "bye"
32
+ bye.out?.should be(false)
33
+ bye.error?.should be(true)
34
+ bye.command.should == "echo bye >&2"
35
+ end
36
+
37
+ it "should return the results of a git operation" do
38
+ GitHub::Command::Shell.should_receive(:new).with("git rev-parse master").once.and_return do |*cmds|
39
+ s = mock("GitHub::Commands::Shell")
40
+ s.should_receive(:run).once.and_return("sha1")
41
+ s
42
+ end
43
+ @command.git("rev-parse master").should == "sha1"
44
+ end
45
+
46
+ it "should print the results of a git operation" do
47
+ @command.should_receive(:puts).with("sha1").once
48
+ GitHub::Command::Shell.should_receive(:new).with("git rev-parse master").once.and_return do |*cmds|
49
+ s = mock("GitHub::Commands::Shell")
50
+ s.should_receive(:run).once.and_return("sha1")
51
+ s
52
+ end
53
+ @command.pgit("rev-parse master")
54
+ end
55
+
56
+ it "should exec a git command" do
57
+ @command.should_receive(:exec).with("git rev-parse master").once
58
+ @command.git_exec "rev-parse master"
59
+ end
60
+
61
+ it "should die" do
62
+ @command.should_receive(:puts).once.with("=> message")
63
+ @command.should_receive(:exit!).once
64
+ @command.die "message"
65
+ end
66
+ end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe "When calling #try" do
4
+ specify "objects should return themselves" do
5
+ obj = 1; obj.try.should equal(obj)
6
+ obj = "foo"; obj.try.should equal(obj)
7
+ obj = { :foo => "bar" }; obj.try.should equal(obj)
8
+ end
9
+
10
+ specify "objects should behave as if #try wasn't called" do
11
+ "foo".try.size.should == 3
12
+ { :foo => :bar }.try.fetch(:foo).should == :bar
13
+ [1, 2, 3].try.map { |x| x + 1 }.should == [2, 3, 4]
14
+ end
15
+
16
+ specify "nil should return the singleton NilClass::NilProxy" do
17
+ nil.try.should equal(NilClass::NilProxy)
18
+ end
19
+
20
+ specify "nil should ignore any calls made past #try" do
21
+ nil.try.size.should equal(NilClass::NilProxy)
22
+ nil.try.sdlfj.should equal(NilClass::NilProxy)
23
+ nil.try.one.two.three.should equal(NilClass::NilProxy)
24
+ end
25
+
26
+ specify "classes should respond just like objects" do
27
+ String.try.should equal(String)
28
+ end
29
+ end
30
+
31
+ describe "When calling #tap" do
32
+ specify "objects should behave like Ruby 1.9's #tap" do
33
+ obj = "foo"
34
+ obj.tap { |obj| obj.size.should == 3 }.should equal(obj)
35
+ end
36
+ end