snowblink-github 0.3.4

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,22 @@
1
+ History.txt
2
+ LICENSE
3
+ Manifest
4
+ README
5
+ Rakefile
6
+ bin/gh
7
+ bin/github
8
+ commands/commands.rb
9
+ commands/helpers.rb
10
+ commands/network.rb
11
+ github-gem.gemspec
12
+ lib/github/command.rb
13
+ lib/github/extensions.rb
14
+ lib/github/helper.rb
15
+ lib/github.rb
16
+ spec/command_spec.rb
17
+ spec/extensions_spec.rb
18
+ spec/github_spec.rb
19
+ spec/helper_spec.rb
20
+ spec/spec_helper.rb
21
+ spec/ui_spec.rb
22
+ spec/windoze_spec.rb
data/README ADDED
@@ -0,0 +1,164 @@
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 Upstream Changes
21
+ =============
22
+
23
+ Let's say you just forked `github-gem` on GitHub from defunkt.
24
+
25
+ $ github clone YOU/github-gem
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
+ $ github checkout master
40
+ $ github 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
+ ==========
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
+ $ github merge master
71
+
72
+ Test/analyze again and if everything is ok:
73
+
74
+ $ github checkout master
75
+ $ github 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
+ ==========
81
+ Network Patch Queue
82
+ ==========
83
+
84
+ The github gem can also show you all of the commits that exist on any fork of your
85
+ project (your network) that you don't have in your branch yet. In order to see
86
+ the list of the projects that have commits you do not, you can run:
87
+
88
+ $ github network list
89
+
90
+ Which will show you all the forks that have changes. If you want to see what those
91
+ changes are, you can run:
92
+
93
+ $ github network commits
94
+
95
+ which will show you something like this:
96
+
97
+ 9582b9 (jchris/gist) kevin@sb.org Add gist binary 4 months ago
98
+ c1a6f9 (jchris/gist~1) kevin@sb.org Tweak Rakefile spec tasks to be a bi 4 months ago
99
+ d3c332 (jchris/gist~2) kevin@sb.org Pull out two helpers into the shared 4 months ago
100
+ 8f65ab (jchris/gist~3) kevin@sb.org Extract command/helper spec assistan 4 months ago
101
+ 389dbf (jchris/gist~4) kevin@sb.org Rename ui_spec to command_spec 4 months ago
102
+ 670a1a (jchris/gist~5) kevin@sb.org Hoist the specs into a per-binary sp 4 months ago
103
+ 6aa18e (jchris/gist~6) kevin@sb.org Hoist commands/helpers into a per-co 4 months ago
104
+ ee013a (luislavena/master) luislavena@gmail.com Replaced STDOUT by $stdout in specs. 2 weeks ago
105
+ d543c4 (luislavena/master~3) luislavena@gmail.com Exclude package folder. 8 weeks ago
106
+ a8c3eb (luislavena/master~5) luislavena@gmail.com Fixed specs for open under Windows. 5 months ago
107
+ 33d003 (riquedafreak/master) enrique.osuna@gmail. Make sure it exists on the remote an 5 weeks ago
108
+ 157155 (riquedafreak/master~1) enrique.osuna@gmail. Updated specs. 5 weeks ago
109
+ f44e99 (riquedafreak/master~3) enrique.osuna@gmail. Only work with a clean branch. 3 months ago
110
+
111
+ These are all the commits that you don't have in your current branch that have been
112
+ pushed to other forks of your project. If you want to incorporate them, you can use:
113
+
114
+ $ github cherry-pick ee013a
115
+
116
+ for example to apply that single patch to your branch. You can also merge a branch,
117
+ if you want all the changes introduced in another branch:
118
+
119
+ $ github merge jchris/gist
120
+
121
+ The next time you run the 'github network commits' command, you won't see any of the
122
+ patches you have cherry-picked or merged (or rebased). If you want to ignore a
123
+ commit, you can simply run:
124
+
125
+ $ github ignore a8c3eb
126
+
127
+ Then you won't ever see that commit again. Or, if you want to ignore a range of commits,
128
+ you can use the normal Git revision selection shorthands - for example, if you want
129
+ to ignore all 7 jchris/gist commits there, you can run:
130
+
131
+ $ github ignore ..jchris/gist
132
+
133
+ You can also filter the output, if you want to see some subset. You can filter by project,
134
+ author and date range, or (one of the cooler things) you can filter by whether the patch
135
+ applies cleanly to your branch head or not. For instance, I can do this:
136
+
137
+ $ ./bin/github network commits --applies
138
+
139
+ ca15af (jchris/master~1) jchris@grabb.it fixed github gemspecs broken referen 8 weeks ago
140
+ ee013a (luislavena/master) luislavena@gmail.com Replaced STDOUT by $stdout in specs. 2 weeks ago
141
+ 157155 (riquedafreak/master~1) enrique.osuna@gmail. Updated specs. 5 weeks ago
142
+ f44e99 (riquedafreak/master~3) enrique.osuna@gmail. Only work with a clean branch. 3 months ago
143
+
144
+ $ ./bin/github network commits --applies --project=riq
145
+
146
+ 157155 (riquedafreak/master~1) enrique.osuna@gmail. Updated specs. 5 weeks ago
147
+ f44e99 (riquedafreak/master~3) enrique.osuna@gmail. Only work with a clean branch. 3 months ago
148
+
149
+ Pretty freaking sweet. Also, you can supply the --shas option to just get a list of
150
+ the shas instead of the pretty printout here, so you can pipe that into other
151
+ scripts (like 'github ignore' for instance).
152
+
153
+
154
+ ==========
155
+ Contributors
156
+ ==========
157
+
158
+ - defunkt
159
+ - maddox
160
+ - halorgium
161
+ - kballard
162
+ - mojombo
163
+ - schacon
164
+
data/bin/gh ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
4
+ $LOAD_PATH.unshift lib_dir if File.directory?(lib_dir)
5
+
6
+ require 'github'
7
+
8
+ GitHub.activate ARGV
data/bin/github ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
4
+ $LOAD_PATH.unshift lib_dir if File.directory?(lib_dir)
5
+
6
+ require 'github'
7
+
8
+ GitHub.activate ARGV
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "github"
3
+ s.version = "0.3.4"
4
+
5
+ s.specification_version = 2 if s.respond_to? :specification_version=
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Chris Wanstrath, Kevin Ballard, Scott Chacon"]
9
+ s.date = %q{2008-05-18}
10
+ s.default_executable = %q{gh}
11
+ s.description = %q{The official `github` command line helper for simplifying your GitHub experience.}
12
+ s.email = %q{chris@ozmm.org}
13
+ s.executables = ["github", "gh"]
14
+ s.extra_rdoc_files = ["bin/github", "bin/gh", "lib/github/extensions.rb", "lib/github/command.rb", "lib/github/helper.rb", "lib/github.rb", "LICENSE", "README"]
15
+ s.files = ["bin/github", "lib/commands/network.rb", "lib/commands/commands.rb", "lib/commands/helpers.rb", "lib/github/extensions.rb", "lib/github/command.rb", "lib/github/helper.rb", "lib/github.rb", "LICENSE", "Manifest", "README", "spec/command_spec.rb", "spec/extensions_spec.rb", "spec/github_spec.rb", "spec/helper_spec.rb", "spec/spec_helper.rb", "spec/ui_spec.rb", "spec/windoze_spec.rb", "github-gem.gemspec"]
16
+ s.has_rdoc = true
17
+ s.homepage = %q{http://github.com/}
18
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Github", "--main", "README"]
19
+ s.require_paths = ["lib"]
20
+ s.rubyforge_project = %q{github}
21
+ s.rubygems_version = %q{1.1.1}
22
+ s.summary = %q{The official `github` command line helper for simplifying your GitHub experience.}
23
+
24
+ # s.add_dependency(%q<launchy>, [">= 0"])
25
+ s.add_dependency('json_pure', [">= 0"])
26
+ end
@@ -0,0 +1,191 @@
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 "Automatically set configuration info, or pass args to specify."
9
+ usage "github config [my_username] [my_repo_name]"
10
+ command :config do |user, repo|
11
+ user ||= ENV['USER']
12
+ repo ||= File.basename(FileUtils.pwd)
13
+ git "config --global github.user #{user}"
14
+ git "config github.repo #{repo}"
15
+ puts "Configured with github.user #{user}, github.repo #{repo}"
16
+ end
17
+
18
+ desc "Open this repo in a web browser."
19
+ usage "github browse [user] [branch]"
20
+ command :browse do |user, branch|
21
+ if helper.project
22
+ # if one arg given, treat it as a branch name
23
+ # unless it maches user/branch, then split it
24
+ # if two args given, treat as user branch
25
+ # if no args given, use defaults
26
+ user, branch = user.split("/", 2) if branch.nil? unless user.nil?
27
+ branch = user and user = nil if branch.nil?
28
+ user ||= helper.branch_user
29
+ branch ||= helper.branch_name
30
+ helper.open helper.homepage_for(user, branch)
31
+ end
32
+ end
33
+
34
+
35
+ desc "Info about this project."
36
+ command :info do
37
+ puts "== Info for #{helper.project}"
38
+ puts "You are #{helper.owner}"
39
+ puts "Currently tracking:"
40
+ helper.tracking.sort { |(a,),(b,)| a == helper.origin ? -1 : b == helper.origin ? 1 : a.to_s <=> b.to_s }.each do |(name,user_or_url)|
41
+ puts " - #{user_or_url} (as #{name})"
42
+ end
43
+ end
44
+
45
+ desc "Track another user's repository."
46
+ usage "github track remote [user]"
47
+ usage "github track remote [user/repo]"
48
+ usage "github track [user]"
49
+ usage "github track [user/repo]"
50
+ flags :private => "Use git@github.com: instead of git://github.com/."
51
+ flags :ssh => 'Equivalent to --private'
52
+ command :track do |remote, user|
53
+ # track remote user
54
+ # track remote user/repo
55
+ # track user
56
+ # track user/repo
57
+ user, remote = remote, nil if user.nil?
58
+ die "Specify a user to track" if user.nil?
59
+ user, repo = user.split("/", 2)
60
+ die "Already tracking #{user}" if helper.tracking?(user)
61
+ repo = @helper.project if repo.nil?
62
+ repo.chomp!(".git")
63
+ remote ||= user
64
+
65
+ if options[:private] || options[:ssh]
66
+ git "remote add #{remote} #{helper.private_url_for_user_and_repo(user, repo)}"
67
+ else
68
+ git "remote add #{remote} #{helper.public_url_for_user_and_repo(user, repo)}"
69
+ end
70
+ end
71
+
72
+ desc "Fetch all refs from a user"
73
+ command :fetch_all do |user|
74
+ GitHub.invoke(:track, user) unless helper.tracking?(user)
75
+ git "fetch #{user}"
76
+ end
77
+
78
+ desc "Fetch from a remote to a local branch."
79
+ command :fetch do |user, branch|
80
+ die "Specify a user to pull from" if user.nil?
81
+ user, branch = user.split("/", 2) if branch.nil?
82
+ branch ||= 'master'
83
+ GitHub.invoke(:track, user) unless helper.tracking?(user)
84
+
85
+ die "Unknown branch (#{branch}) specified" unless helper.remote_branch?(user, branch)
86
+ die "Unable to switch branches, your current branch has uncommitted changes" if helper.branch_dirty?
87
+
88
+ puts "Fetching #{user}/#{branch}"
89
+ git "fetch #{user} #{branch}:refs/remotes/#{user}/#{branch}"
90
+ git "update-ref refs/heads/#{user}/#{branch} refs/remotes/#{user}/#{branch}"
91
+ git_exec "checkout #{user}/#{branch}"
92
+ end
93
+
94
+ desc "Pull from a remote."
95
+ usage "github pull [user] [branch]"
96
+ flags :merge => "Automatically merge remote's changes into your master."
97
+ command :pull do |user, branch|
98
+ die "Specify a user to pull from" if user.nil?
99
+ user, branch = user.split("/", 2) if branch.nil?
100
+
101
+ if !helper.network_members.include?(user)
102
+ git_exec "#{helper.argv.join(' ')}".strip
103
+ end
104
+
105
+ branch ||= 'master'
106
+ GitHub.invoke(:track, user) unless helper.tracking?(user)
107
+
108
+ if options[:merge]
109
+ git_exec "pull #{user} #{branch}"
110
+ else
111
+ puts "Switching to #{user}-#{branch}"
112
+ git "fetch #{user}"
113
+ git_exec "checkout -b #{user}/#{branch} #{user}/#{branch}"
114
+ end
115
+ end
116
+
117
+ desc "Clone a repo. Uses ssh if current user is "
118
+ usage "github clone [user] [repo] [dir]"
119
+ flags :ssh => "Clone using the git@github.com style url."
120
+ command :clone do |user, repo, dir|
121
+ die "Specify a user to pull from" if user.nil?
122
+ if user.include?('/') && !user.include?('@') && !user.include?(':')
123
+ die "Expected user/repo dir, given extra argument" if dir
124
+ (user, repo), dir = [user.split('/', 2), repo]
125
+ end
126
+
127
+ if options[:ssh] || current_user?(user)
128
+ git_exec "clone git@github.com:#{user}/#{repo}.git" + (dir ? " #{dir}" : "")
129
+ elsif repo
130
+ git_exec "clone git://github.com/#{user}/#{repo}.git" + (dir ? " #{dir}" : "")
131
+ else
132
+ git_exec "#{helper.argv.join(' ')}".strip
133
+ end
134
+ end
135
+
136
+ desc "Generate the text for a pull request."
137
+ usage "github pull-request [user] [branch]"
138
+ command 'pull-request' do |user, branch|
139
+ if helper.project
140
+ die "Specify a user for the pull request" if user.nil?
141
+ user, branch = user.split('/', 2) if branch.nil?
142
+ branch ||= 'master'
143
+ GitHub.invoke(:track, user) unless helper.tracking?(user)
144
+
145
+ git_exec "request-pull #{user}/#{branch} #{helper.origin}"
146
+ end
147
+ end
148
+
149
+ desc "Create a new, empty GitHub repository"
150
+ usage "github create [repo]"
151
+ flags :markdown => 'Create README.markdown'
152
+ flags :mdown => 'Create README.mdown'
153
+ flags :textile => 'Create README.textile'
154
+ flags :rdoc => 'Create README.rdoc'
155
+ flags :rst => 'Create README.rst'
156
+ command :create do |repo|
157
+ sh "curl -F 'repository[name]=#{repo}' -F 'login=#{github_user}' -F 'token=#{github_token}' http://github.com/repositories"
158
+ mkdir repo
159
+ cd repo
160
+ git "init"
161
+ extension = options.keys.first
162
+ touch extension ? "README.#{extension}" : "README"
163
+ git "add *"
164
+ git "commit -m 'First commit!'"
165
+ git "remote add origin git@github.com:#{github_user}/#{repo}.git"
166
+ git_exec "push origin master"
167
+ end
168
+
169
+ desc "Forks a GitHub repository"
170
+ usage "github fork [user]/[repo]"
171
+ command :fork do |user, repo|
172
+ if repo.nil?
173
+ user, repo = user.split('/')
174
+ end
175
+
176
+ sh "curl -F 'login=#{github_user}' -F 'token=#{github_token}' http://github.com/#{user}/#{repo}/fork"
177
+ puts "Giving GitHub a moment to create the fork..."
178
+ sleep 3
179
+ git_exec "clone git@github.com:#{github_user}/#{repo}.git"
180
+ end
181
+
182
+ desc "Create a new GitHub repository from the current local repository"
183
+ command 'create-from-local' do
184
+ cwd = sh "pwd"
185
+ repo = File.basename(cwd)
186
+ is_repo = !git("status").match(/fatal/)
187
+ raise "Not a git repository. Use gh create instead" unless is_repo
188
+ sh "curl -F 'repository[name]=#{repo}' -F 'login=#{github_user}' -F 'token=#{github_token}' http://github.com/repositories"
189
+ git "remote add origin git@github.com:#{github_user}/#{repo}.git"
190
+ git_exec "push origin master"
191
+ end
@@ -0,0 +1,397 @@
1
+ DEV_NULL = File.exist?("/dev/null") ? "/dev/null" : "nul:"
2
+
3
+ helper :user_and_repo_from do |url|
4
+ case url
5
+ when %r|^git://github\.com/([^/]+/[^/]+)$|: $1.split('/')
6
+ when %r|^(?:ssh://)?(?:git@)?github\.com:([^/]+/[^/]+)$|: $1.split('/')
7
+ end
8
+ end
9
+
10
+ helper :user_and_repo_for do |remote|
11
+ user_and_repo_from(url_for(remote))
12
+ end
13
+
14
+ helper :user_for do |remote|
15
+ user_and_repo_for(remote).try.first
16
+ end
17
+
18
+ helper :repo_for do |remote|
19
+ user_and_repo_for(remote).try.last
20
+ end
21
+
22
+ helper :origin do
23
+ orig = `git config --get github.origin`.chomp
24
+ orig = nil if orig.empty?
25
+ orig || 'origin'
26
+ end
27
+
28
+ helper :project do
29
+ repo = repo_for(origin)
30
+ if repo.nil?
31
+ if url_for(origin) == ""
32
+ STDERR.puts "Error: missing remote 'origin'"
33
+ else
34
+ STDERR.puts "Error: remote 'origin' is not a github URL"
35
+ end
36
+ exit 1
37
+ end
38
+ repo.chomp('.git')
39
+ end
40
+
41
+ helper :url_for do |remote|
42
+ `git config --get remote.#{remote}.url`.chomp
43
+ end
44
+
45
+ helper :local_heads do
46
+ `git show-ref --heads --hash`.split("\n")
47
+ end
48
+
49
+ helper :has_commit? do |sha|
50
+ `git show #{sha} >#{DEV_NULL} 2>#{DEV_NULL}`
51
+ $?.exitstatus == 0
52
+ end
53
+
54
+ helper :resolve_commits do |treeish|
55
+ if treeish
56
+ if treeish.match(/\.\./)
57
+ commits = `git rev-list #{treeish}`.split("\n")
58
+ else
59
+ commits = `git rev-parse #{treeish}`.split("\n")
60
+ end
61
+ else
62
+ # standard in
63
+ puts 'reading from stdin...'
64
+ commits = $stdin.read.split("\n")
65
+ end
66
+ commits.select { |a| a.size == 40 } # only the shas, not the ^SHAs
67
+ end
68
+
69
+ helper :ignore_file_path do
70
+ dir = `git rev-parse --git-dir`.chomp
71
+ File.join(dir, 'ignore-shas')
72
+ end
73
+
74
+ helper :ignore_sha_array do
75
+ File.open( ignore_file_path ) { |yf| YAML::load( yf ) } rescue {}
76
+ end
77
+
78
+ helper :remove_ignored do |array, ignore_array|
79
+ array.reject { |id| ignore_array[id] }
80
+ end
81
+
82
+ helper :ignore_shas do |shas|
83
+ ignores = ignore_sha_array
84
+ shas.each do |sha|
85
+ puts 'ignoring ' + sha
86
+ ignores[sha] = true
87
+ end
88
+ File.open( ignore_file_path, 'w' ) do |out|
89
+ YAML.dump( ignores, out )
90
+ end
91
+ end
92
+
93
+ helper :get_commits do |rev_array|
94
+ list = rev_array.select { |a| has_commit?(a) }.join(' ')
95
+ `git log --pretty=format:"%H::%ae::%s::%ar::%ad" --no-merges #{list}`.split("\n").map { |a| a.split('::') }
96
+ end
97
+
98
+ helper :get_cherry do |branch|
99
+ `git cherry HEAD #{branch} | git name-rev --stdin`.split("\n").map { |a| a.split(' ') }
100
+ end
101
+
102
+ helper :get_common do |branch|
103
+ `git rev-list ..#{branch} --boundary | tail -1 | git name-rev --stdin`.split(' ')[1] rescue 'unknown'
104
+ end
105
+
106
+ helper :print_commits do |our_commits, options|
107
+ ignores = ignore_sha_array
108
+
109
+ case options[:sort]
110
+ when 'branch'
111
+ our_commits.sort! { |a, b| a[0][2] <=> b[0][2] }
112
+ when 'author'
113
+ our_commits.sort! { |a, b| a[1][1] <=> b[1][1] }
114
+ else
115
+ our_commits.sort! { |a, b| Date.parse(a[1][4]) <=> Date.parse(b[1][4]) } rescue 'cant parse dates'
116
+ end
117
+
118
+ shown_commits = {}
119
+ before = Date.parse(options[:before]) if options[:before] rescue puts 'cant parse before date'
120
+ after = Date.parse(options[:after]) if options[:after] rescue puts 'cant parse after date'
121
+ our_commits.each do |cherry, commit|
122
+ status, sha, ref_name = cherry
123
+ next if shown_commits[sha] || ignores[sha]
124
+ next if options[:project] && !ref_name.match(Regexp.new(options[:project]))
125
+ ref_name = ref_name.gsub('remotes/', '')
126
+ if status == '+' && commit
127
+ next if options[:author] && !commit[1].match(Regexp.new(options[:author]))
128
+ next if options[:before] && before && (before < Date.parse(commit[4])) rescue false
129
+ next if options[:after] && after && (after > Date.parse(commit[4])) rescue false
130
+ applies = applies_cleanly(sha)
131
+ next if options[:applies] && !applies
132
+ next if options[:noapply] && applies
133
+ if options[:shas]
134
+ puts sha
135
+ else
136
+ common = options[:common] ? get_common(sha) : ''
137
+ puts [sha[0,6], ref_name.ljust(25), commit[1][0,20].ljust(21),
138
+ commit[2][0, 36].ljust(38), commit[3][0,15], common].join(" ")
139
+ end
140
+ end
141
+ shown_commits[sha] = true
142
+ end
143
+ end
144
+
145
+ helper :applies_cleanly do |sha|
146
+ `git diff ...#{sha} | git apply --check >#{DEV_NULL} 2>#{DEV_NULL}`
147
+ $?.exitstatus == 0
148
+ end
149
+
150
+ helper :remotes do
151
+ regexp = '^remote\.(.+)\.url$'
152
+ `git config --get-regexp '#{regexp}'`.split("\n").inject({}) do |memo, line|
153
+ name_string, url = line.split(/ /, 2)
154
+ m, name = *name_string.match(/#{regexp}/)
155
+ memo[name.to_sym] = url
156
+ memo
157
+ end
158
+ end
159
+
160
+ helper :remote_branches_for do |user|
161
+ `git ls-remote -h #{user} 2> #{DEV_NULL}`.split(/\n/).inject({}) do |memo, line|
162
+ hash, head = line.split(/\t/, 2)
163
+ head = head[%r{refs/heads/(.+)$},1] unless head.nil?
164
+ memo[head] = hash unless head.nil?
165
+ memo
166
+ end if !(user.nil? || user.strip.empty?)
167
+ end
168
+
169
+ helper :remote_branch? do |user, branch|
170
+ remote_branches_for(user).key?(branch)
171
+ end
172
+
173
+ helper :branch_dirty? do
174
+ # see if there are any cached or tracked files that have been modified
175
+ # originally, we were going to use git-ls-files but that could only
176
+ # report modified track files...not files that have been staged
177
+ # for committal
178
+ !(system("git diff --quiet 2>#{DEV_NULL}") or !system("git diff --cached --quiet 2>#{DEV_NULL}"))
179
+ end
180
+
181
+ helper :tracking do
182
+ remotes.inject({}) do |memo, (name, url)|
183
+ if ur = user_and_repo_from(url)
184
+ memo[name] = ur.first
185
+ else
186
+ memo[name] = url
187
+ end
188
+ memo
189
+ end
190
+ end
191
+
192
+ helper :tracking? do |user|
193
+ tracking.values.include?(user)
194
+ end
195
+
196
+ helper :owner do
197
+ user_for(origin)
198
+ end
199
+
200
+ helper :current_branch do
201
+ `git rev-parse --symbolic-full-name HEAD`.chomp.sub(/^refs\/heads\//, '')
202
+ end
203
+
204
+ helper :user_and_branch do
205
+ raw_branch = current_branch
206
+ user, branch = raw_branch.split(/\//, 2)
207
+ if branch
208
+ [user, branch]
209
+ else
210
+ [owner, user]
211
+ end
212
+ end
213
+
214
+ helper :branch_user do
215
+ user_and_branch.first
216
+ end
217
+
218
+ helper :branch_name do
219
+ user_and_branch.last
220
+ end
221
+
222
+ helper :public_url_for_user_and_repo do |user, repo|
223
+ "git://github.com/#{user}/#{repo}.git"
224
+ end
225
+
226
+ helper :private_url_for_user_and_repo do |user, repo|
227
+ "git@github.com:#{user}/#{repo}.git"
228
+ end
229
+
230
+ helper :public_url_for do |user|
231
+ public_url_for_user_and_repo user, project
232
+ end
233
+
234
+ helper :private_url_for do |user|
235
+ private_url_for_user_and_repo user, project
236
+ end
237
+
238
+ helper :homepage_for do |user, branch|
239
+ "https://github.com/#{user}/#{project}/tree/#{branch}"
240
+ end
241
+
242
+ helper :network_page_for do |user|
243
+ "https://github.com/#{user}/#{project}/network"
244
+ end
245
+
246
+ helper :network_meta_for do |user|
247
+ "http://github.com/#{user}/#{project}/network_meta"
248
+ end
249
+
250
+ helper :network_members_for do |user|
251
+ "http://github.com/#{user}/#{project}/network/members.json"
252
+ end
253
+
254
+ helper :has_launchy? do |blk|
255
+ begin
256
+ gem 'launchy'
257
+ require 'launchy'
258
+ blk.call
259
+ rescue Gem::LoadError
260
+ STDERR.puts "Sorry, you need to install launchy: `gem install launchy`"
261
+ end
262
+ end
263
+
264
+ helper :open do |url|
265
+ has_launchy? proc {
266
+ Launchy::Browser.new.visit url
267
+ }
268
+ end
269
+
270
+ helper :print_network_help do
271
+ puts "
272
+ You have to provide a command :
273
+
274
+ web [user] - opens your web browser to the network graph page for this
275
+ project, or for the graph page for [user] if provided
276
+
277
+ list - shows the projects in your network that have commits
278
+ that you have not pulled in yet, and branch names
279
+
280
+ fetch - adds all projects in your network as remotes and fetches
281
+ any objects from them that you don't have yet
282
+
283
+ commits - will show you a list of all commits in your network that
284
+ you have not ignored or have not merged or cherry-picked.
285
+ This will automatically fetch objects you don't have yet.
286
+
287
+ --project (user/branch) - only show projects that match string
288
+ --author (email) - only show projects that match string
289
+ --after (date) - only show commits after date
290
+ --before (date) - only show commits before date
291
+ --shas - only print shas (can pipe through 'github ignore')
292
+ --applies - filter to patches that still apply cleanly
293
+ --sort - how to sort the commits (date, branch, author)
294
+ "
295
+ end
296
+
297
+ helper :print_network_cherry_help do
298
+ $stderr.puts "
299
+ =========================================================================================
300
+ These are all the commits that other people have pushed that you have not
301
+ applied or ignored yet (see 'github ignore'). Some things you might want to do:
302
+
303
+ * You can run 'github fetch user/branch' (sans '~N') to pull into a local branch for testing
304
+ * You can run 'github cherry-pick [SHA]' to apply a single patch
305
+ * You can run 'github merge user/branch' to merge a commit and all the '~N' variants.
306
+ * You can ignore all of a projects commits with 'github ignore ..user/branch'
307
+ =========================================================================================
308
+
309
+ "
310
+ end
311
+
312
+ helper :argv do
313
+ GitHub.original_args
314
+ end
315
+
316
+ helper :network_members do
317
+ get_network_members(owner, {}).map {|member| member['owner']['login'] }
318
+ end
319
+
320
+
321
+ helper :get_network_data do |user, options|
322
+ if options[:cache] && has_cache?
323
+ return get_cache
324
+ end
325
+ if cache_network_data(options)
326
+ begin
327
+ return cache_data(user)
328
+ rescue SocketError
329
+ STDERR.puts "*** Warning: There was a problem accessing the network."
330
+ rv = get_cache
331
+ STDERR.puts "Using cached data."
332
+ rv
333
+ end
334
+ else
335
+ return get_cache
336
+ end
337
+ end
338
+
339
+ helper :get_network_members do |user, options|
340
+ json = Kernel.open(network_members_for(user)).read
341
+ JSON.parse(json)["users"]
342
+ end
343
+
344
+ helper :cache_commits do |commits|
345
+ File.open( commits_cache_path, 'w' ) do |out|
346
+ out.write(commits.to_yaml)
347
+ end
348
+ end
349
+
350
+ helper :commits_cache do
351
+ YAML.load(File.open(commits_cache_path))
352
+ end
353
+
354
+ helper :cache_commits_data do |options|
355
+ cache_expired? || options[:nocache] || !has_commits_cache?
356
+ end
357
+
358
+ helper :cache_network_data do |options|
359
+ cache_expired? || options[:nocache] || !has_cache?
360
+ end
361
+
362
+ helper :network_cache_path do
363
+ dir = `git rev-parse --git-dir`.chomp
364
+ File.join(dir, 'network-cache')
365
+ end
366
+
367
+ helper :commits_cache_path do
368
+ dir = `git rev-parse --git-dir`.chomp
369
+ File.join(dir, 'commits-cache')
370
+ end
371
+
372
+ helper :cache_data do |user|
373
+ raw_data = Kernel.open(network_meta_for(user)).read
374
+ File.open( network_cache_path, 'w' ) do |out|
375
+ out.write(raw_data)
376
+ end
377
+ data = JSON.parse(raw_data)
378
+ end
379
+
380
+ helper :cache_expired? do
381
+ return true if !has_cache?
382
+ age = Time.now - File.stat(network_cache_path).mtime
383
+ return true if age > (60 * 60) # 1 hour
384
+ false
385
+ end
386
+
387
+ helper :has_cache? do
388
+ File.file?(network_cache_path)
389
+ end
390
+
391
+ helper :has_commits_cache? do
392
+ File.file?(commits_cache_path)
393
+ end
394
+
395
+ helper :get_cache do
396
+ JSON.parse(File.read(network_cache_path))
397
+ end