git-whistles 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +0 -1
- data/Gemfile.lock +48 -0
- data/README.md +2 -1
- data/bin/git-ff-all-branches +24 -65
- data/bin/git-pull-request +131 -85
- data/bin/git-select +74 -0
- data/lib/git-whistles/app.rb +75 -0
- data/lib/git-whistles/helpers.rb +4 -0
- data/lib/git-whistles/logger.rb +25 -0
- data/lib/git-whistles/version.rb +1 -1
- metadata +117 -101
data/.gitignore
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
git-whistles (0.6.1)
|
5
|
+
pivotal-tracker (~> 0.5.6)
|
6
|
+
term-ansicolor
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
builder (3.1.3)
|
12
|
+
coderay (1.0.7)
|
13
|
+
happymapper (0.4.0)
|
14
|
+
libxml-ruby (~> 2.0)
|
15
|
+
libxml-ruby (2.3.3)
|
16
|
+
method_source (0.8)
|
17
|
+
mime-types (1.19)
|
18
|
+
nokogiri (1.5.5)
|
19
|
+
pivotal-tracker (0.5.6)
|
20
|
+
builder
|
21
|
+
builder
|
22
|
+
happymapper (>= 0.3.2)
|
23
|
+
happymapper (>= 0.3.2)
|
24
|
+
nokogiri (>= 1.4.3)
|
25
|
+
nokogiri (~> 1.4)
|
26
|
+
rest-client (~> 1.6.0)
|
27
|
+
rest-client (~> 1.6.0)
|
28
|
+
pry (0.9.10)
|
29
|
+
coderay (~> 1.0.5)
|
30
|
+
method_source (~> 0.8)
|
31
|
+
slop (~> 3.3.1)
|
32
|
+
pry-nav (0.2.2)
|
33
|
+
pry (~> 0.9.10)
|
34
|
+
rake (0.9.2.2)
|
35
|
+
rest-client (1.6.7)
|
36
|
+
mime-types (>= 1.16)
|
37
|
+
slop (3.3.2)
|
38
|
+
term-ansicolor (1.0.7)
|
39
|
+
|
40
|
+
PLATFORMS
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
bundler (>= 1.0.0)
|
45
|
+
git-whistles!
|
46
|
+
pry
|
47
|
+
pry-nav
|
48
|
+
rake
|
data/README.md
CHANGED
@@ -12,11 +12,12 @@ Use it with:
|
|
12
12
|
|
13
13
|
- `git ff-all-branches [-f] [-p] [-v]`. Fast-forward all local tracking branches to their remote counterpart (where possible). Very useful on big projects.
|
14
14
|
- `git stash-and-checkout [branch]` As the name implies: stash and checkout another branch.
|
15
|
-
- `git-pull-request [your-branch] [target-branch]` Open your browser at a Github pull-request page for the specified branch (defaults to the current `head`). If you're using Pivotal Tracker and your branch has a story number in its name, populates your pull request with story details.
|
15
|
+
- `git-pull-request [--from your-branch] [--to target-branch]` Open your browser at a Github pull-request page for the specified branch (defaults to the current `head`). If you're using Pivotal Tracker and your branch has a story number in its name, populates your pull request with story details.
|
16
16
|
- `git outstanding-features [from-branch] [to-branch]` List the pull requests merged in `[to-branch]` but not in `[from-branch]`. Useful to prepare a list of stuff you're oging to deploy. Defaults to listing what's on `origin/master` but not on `origin/production`.
|
17
17
|
- `git chop [branch]` Delete the local and origin copy of a branch. Useful to close feature branches once a feature is completed.
|
18
18
|
- `git list-branches [-l] [-r] [-i integration-branch]` Colourful listing of all local or origin branches, and their distance to an integration branch (`master` by default).
|
19
19
|
- `git merge-po <ancestor> <left> <right>` Merge engine for GetText PO files.
|
20
|
+
- `git select <story-id>` Checkout a local branch with the matching number. If not found, lists remote branches
|
20
21
|
|
21
22
|
### More details on some of the commands
|
22
23
|
|
data/bin/git-ff-all-branches
CHANGED
@@ -6,53 +6,25 @@
|
|
6
6
|
#
|
7
7
|
require 'ostruct'
|
8
8
|
require 'optparse'
|
9
|
-
require 'logger'
|
10
9
|
require 'term/ansicolor'
|
10
|
+
require 'git-whistles/logger'
|
11
|
+
require 'git-whistles/app'
|
11
12
|
|
12
13
|
############################################################################
|
13
14
|
|
14
15
|
|
15
|
-
class App
|
16
|
-
|
17
|
-
DEFAULTS = {
|
18
|
-
:fetch => false,
|
19
|
-
:dry_run => false,
|
20
|
-
:remote => 'origin',
|
21
|
-
:loglevel => Logger::WARN
|
22
|
-
}
|
23
|
-
|
24
|
-
class Logger < ::Logger
|
25
|
-
Colors = {
|
26
|
-
'DEBUG' => :reset,
|
27
|
-
'INFO' => :green,
|
28
|
-
'WARN' => :yellow,
|
29
|
-
'ERROR' => :red,
|
30
|
-
'FATAL' => :red,
|
31
|
-
'UNKNOWN' => :red
|
32
|
-
}
|
33
|
-
|
34
|
-
def initialize(*args)
|
35
|
-
super
|
36
|
-
self.formatter = self.method(:custom_formatter)
|
37
|
-
end
|
38
|
-
|
39
|
-
def custom_formatter(severity, time, progname, msg)
|
40
|
-
Term::ANSIColor.send(Colors[severity], "#{msg}\n")
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
16
|
+
class App < Git::Whistles::App
|
44
17
|
|
45
18
|
def initialize
|
46
|
-
|
19
|
+
super
|
47
20
|
@local_branches = {}
|
48
21
|
@remote_branches = {}
|
49
22
|
@current_branch = nil
|
50
|
-
@log = Logger.new($stderr)
|
51
23
|
end
|
52
24
|
|
53
25
|
|
54
26
|
def main(args)
|
55
|
-
|
27
|
+
super
|
56
28
|
log.level = options.loglevel
|
57
29
|
|
58
30
|
if options.fetch
|
@@ -69,11 +41,19 @@ class App
|
|
69
41
|
|
70
42
|
private
|
71
43
|
|
72
|
-
|
44
|
+
def defaults
|
45
|
+
{
|
46
|
+
:fetch => false,
|
47
|
+
:dry_run => false,
|
48
|
+
:remote => 'origin',
|
49
|
+
:loglevel => Logger::WARN
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
|
73
54
|
attr :local_branches
|
74
55
|
attr :remote_branches
|
75
56
|
attr :current_branch
|
76
|
-
attr :log
|
77
57
|
|
78
58
|
|
79
59
|
def process_branch(branch_name)
|
@@ -90,20 +70,20 @@ class App
|
|
90
70
|
end
|
91
71
|
|
92
72
|
if branch_name == current_branch
|
93
|
-
if run('git status --porcelain').strip.empty?
|
73
|
+
if run!('git status --porcelain').strip.empty?
|
94
74
|
flag = (options.loglevel > Logger::INFO) ? '-q' : ''
|
95
|
-
run("git merge --ff-only #{flag} #{new_head}") unless options.dry_run
|
75
|
+
run!("git merge --ff-only #{flag} #{new_head}") unless options.dry_run
|
96
76
|
else
|
97
77
|
log.warn('not merging current branch as it has local changes')
|
98
78
|
return
|
99
79
|
end
|
100
80
|
else
|
101
|
-
merge_base = run("git merge-base #{old_head} #{new_head}").strip
|
81
|
+
merge_base = run!("git merge-base #{old_head} #{new_head}").strip
|
102
82
|
if merge_base != old_head
|
103
83
|
log.warn("cannot fast-forward #{branch_name}")
|
104
84
|
return
|
105
85
|
end
|
106
|
-
run("git update-ref refs/heads/#{branch_name} #{new_head} #{old_head}")
|
86
|
+
run!("git update-ref refs/heads/#{branch_name} #{new_head} #{old_head}")
|
107
87
|
end
|
108
88
|
|
109
89
|
log.info("#{branch_name}: #{short_sha old_head} -> #{short_sha new_head}")
|
@@ -138,39 +118,18 @@ class App
|
|
138
118
|
|
139
119
|
|
140
120
|
def parse_args!(args)
|
141
|
-
|
142
|
-
option_parser.parse!(args)
|
143
|
-
rescue OptionParser::InvalidOption => error
|
144
|
-
die error.message, :usage => true
|
145
|
-
end
|
121
|
+
super
|
146
122
|
|
147
|
-
if
|
123
|
+
if args.any?
|
148
124
|
die "this command does not take any argument besides flags", :usage => true
|
149
125
|
end
|
150
126
|
end
|
151
127
|
|
152
128
|
|
153
|
-
def run(command)
|
154
|
-
result = %x(#{command})
|
155
|
-
return result if $? == 0
|
156
|
-
die "command '#{command}' failed"
|
157
|
-
end
|
158
|
-
|
159
|
-
|
160
|
-
def die(message, options = {})
|
161
|
-
puts Term::ANSIColor.red(message)
|
162
|
-
if options[:usage]
|
163
|
-
puts
|
164
|
-
puts option_parser.help
|
165
|
-
end
|
166
|
-
exit 1
|
167
|
-
end
|
168
|
-
|
169
|
-
|
170
129
|
def load_refs
|
171
|
-
@current_branch = run("git symbolic-ref HEAD").strip.gsub(%r(^refs/heads/), '')
|
130
|
+
@current_branch = run!("git symbolic-ref HEAD").strip.gsub(%r(^refs/heads/), '')
|
172
131
|
|
173
|
-
run('git show-ref').strip.split(/\n+/).each do |line|
|
132
|
+
run!('git show-ref').strip.split(/\n+/).each do |line|
|
174
133
|
line =~ %r(([a-f0-9]{40}) refs/(remotes/#{options.remote}|heads)/(.*)) or next
|
175
134
|
if $2 == 'heads'
|
176
135
|
@local_branches[$3] = $1
|
@@ -187,4 +146,4 @@ end
|
|
187
146
|
|
188
147
|
############################################################################
|
189
148
|
|
190
|
-
App.
|
149
|
+
App.run!
|
data/bin/git-pull-request
CHANGED
@@ -7,114 +7,160 @@
|
|
7
7
|
# Assumes the branches are named
|
8
8
|
# <team>/<branch-title>-<story-id>
|
9
9
|
#
|
10
|
-
# Copyright (C) 2012 Julien Letessier
|
11
|
-
#
|
12
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
13
|
-
# this software and associated documentation files (the "Software"), to deal in
|
14
|
-
# the Software without restriction, including without limitation the rights to
|
15
|
-
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
16
|
-
# of the Software, and to permit persons to whom the Software is furnished to do
|
17
|
-
# so, subject to the following conditions:
|
18
|
-
#
|
19
|
-
# The above copyright notice and this permission notice shall be included in all
|
20
|
-
# copies or substantial portions of the Software.
|
21
|
-
#
|
22
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
23
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
24
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
25
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
26
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
27
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
28
|
-
# SOFTWARE.
|
29
|
-
#
|
30
|
-
# require 'CGI'
|
31
10
|
require 'rubygems'
|
32
11
|
require 'pivotal-tracker'
|
12
|
+
require 'optparse'
|
13
|
+
require 'cgi'
|
33
14
|
require 'term/ansicolor'
|
15
|
+
require 'git-whistles/app'
|
34
16
|
|
35
|
-
origin_url = `git config --get remote.origin.url`.strip
|
36
|
-
unless origin_url =~ /github\.com/
|
37
|
-
puts "origin does not have a Github URL !"
|
38
|
-
exit 1
|
39
|
-
end
|
40
17
|
|
41
|
-
|
18
|
+
class App < Git::Whistles::App
|
42
19
|
|
43
|
-
|
44
|
-
branch = ARGV[0]
|
45
|
-
else
|
46
|
-
branch = `git symbolic-ref HEAD`.gsub(%r(^refs/heads/), "")
|
47
|
-
exit $? unless $? == 0
|
48
|
-
end
|
20
|
+
Browsers = %w(xdg-open open firefox iceweasel)
|
49
21
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
end
|
54
25
|
|
55
|
-
against = ARGV[1] || 'master'
|
56
26
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
:base_ref => against,
|
61
|
-
:head_ref => branch
|
62
|
-
}
|
27
|
+
def main(args)
|
28
|
+
super
|
29
|
+
parse_args!(args)
|
63
30
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
31
|
+
if args.count > 0
|
32
|
+
die "Too many arguments", :usage => true
|
33
|
+
end
|
68
34
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
#
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
puts Term::ANSIColor.yellow %Q{
|
79
|
-
I don't know your Pivotal Tracker token!
|
80
|
-
Please set it with:
|
81
|
-
$ git config pivotal-tracker.token <token>
|
35
|
+
if options.from == options.to
|
36
|
+
die "You cannot issue a pull request to the same branch (#{options.from})."
|
37
|
+
end
|
38
|
+
|
39
|
+
# build the base query
|
40
|
+
query = {
|
41
|
+
:base_repo => repo,
|
42
|
+
:base_ref => options.to,
|
43
|
+
:head_ref => options.from
|
82
44
|
}
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
45
|
+
|
46
|
+
# guess team name
|
47
|
+
if options.from =~ %r{^(\w+)/.*}
|
48
|
+
team = $1.capitalize
|
49
|
+
else
|
50
|
+
team = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# guess title.
|
54
|
+
title = options.from.split('/').last.split(/[_-]/).delete_if { |word| word =~ /^\d+$/ }.join(' ').capitalize
|
55
|
+
query[:title] = team ? "#{team}: #{title}" : title
|
56
|
+
|
57
|
+
# add Pivotal infos
|
58
|
+
add_pivotal_info(query, $1.to_i) if options.from =~ /(\d+)$/
|
59
|
+
|
60
|
+
query_string = query.map { |key,value|
|
61
|
+
"#{CGI.escape key.to_s}=#{CGI.escape value}"
|
62
|
+
}.join('&')
|
63
|
+
url = "https://github.com/#{repo}/pull/new?#{query_string}"
|
64
|
+
puts "Preparing a pull request for branch #{options.from}"
|
65
|
+
|
66
|
+
unless launch_browser(url)
|
67
|
+
log.warn "Sorry, I don't know how to launch a web browser on your system. You can open it yourself and paste this URL:\n#{url}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
|
75
|
+
def defaults
|
76
|
+
{
|
77
|
+
:from => run!('git symbolic-ref HEAD').strip.gsub(%r(^refs/heads/), ""),
|
78
|
+
:to => 'master',
|
79
|
+
:remote => 'origin'
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def option_parser
|
84
|
+
@option_parser ||= OptionParser.new do |op|
|
85
|
+
op.banner = "Usage: git pull-request [options]"
|
86
|
+
|
87
|
+
op.on("-f", "--from YOUR_BRANCH", "Branch to issue pull request for [head]") do |v|
|
88
|
+
options.from = v
|
89
|
+
end
|
90
|
+
|
91
|
+
op.on("-to", "--to UPSTREAM_BRANCH", "Branch into which you want your code merged [master]") do |v|
|
92
|
+
options.to = v
|
93
|
+
end
|
94
|
+
|
95
|
+
op.on("-r", "--remote NAME", "The remote you're sending this to [origin]") do |v|
|
96
|
+
options.to = v
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def origin_url
|
103
|
+
@origin_url ||= begin
|
104
|
+
run!("git config --get remote.#{options.remote}.url").strip.tap do |url|
|
105
|
+
url =~ /github\.com/ or die "origin does not have a Github URL !"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def repo
|
112
|
+
@repo ||= origin_url.sub(/.*github\.com[\/:]/,'').sub(/\.git$/,'')
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def add_pivotal_info(query, story_id)
|
117
|
+
token = `git config pivotal-tracker.token`.strip
|
118
|
+
if token.empty?
|
119
|
+
puts Term::ANSIColor.yellow %Q{
|
120
|
+
Your branch appears to have a story ID,
|
121
|
+
but I don't know your Pivotal Tracker token!
|
122
|
+
Please set it with:
|
123
|
+
$ git config [--global] pivotal-tracker.token <token>
|
124
|
+
}
|
125
|
+
die "Aborting."
|
126
|
+
end
|
127
|
+
|
128
|
+
log.info "Finding your project and story¬"
|
87
129
|
|
88
130
|
PivotalTracker::Client.token = token
|
89
131
|
story, project = PivotalTracker::Project.all.find do |project|
|
90
|
-
|
91
|
-
$stdout.flush
|
132
|
+
log.info '.¬'
|
92
133
|
story = project.stories.find(story_id) and break story, project
|
93
134
|
end
|
94
|
-
|
135
|
+
log.info '.'
|
95
136
|
|
96
137
|
if story.nil?
|
97
|
-
|
98
|
-
|
99
|
-
|
138
|
+
log.warn "Apologies... I could not find story #{story_id}."
|
139
|
+
return
|
140
|
+
end
|
141
|
+
|
142
|
+
log.info "Found story #{story_id} in '#{project.name}'"
|
100
143
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
144
|
+
headline = "Pivotal tracker story [##{story_id}](#{story.url}) in project *#{project.name}*:"
|
145
|
+
description = story.description.split("\n").map { |line| "> #{line}" }.join("\n")
|
146
|
+
body = "#{headline}\n\n#{description}"
|
147
|
+
title = "#{project.name}: #{story.name} [##{story.id}]"
|
148
|
+
query.merge! :subject => story.name, :body => body, :title => title
|
149
|
+
return
|
150
|
+
end
|
105
151
|
|
106
|
-
|
152
|
+
def launch_browser(url)
|
153
|
+
Browsers.each do |command|
|
154
|
+
next if run("which #{command}").strip.empty?
|
155
|
+
system command, url
|
156
|
+
return true
|
107
157
|
end
|
158
|
+
return false
|
108
159
|
end
|
109
|
-
end
|
110
160
|
|
111
|
-
query_string = query.map { |key,value| "#{CGI.escape key.to_s}=#{CGI.escape value}"}.join('&')
|
112
|
-
url = "https://github.com/#{repo}/pull/new?#{query_string}"
|
113
|
-
puts "Preparing a pull request for branch #{branch}"
|
114
161
|
|
115
|
-
# Use +xdg-open+ on Linux, +open+ on Darwin
|
116
|
-
unless `which xdg-open`.strip.empty?
|
117
|
-
system "xdg-open", url
|
118
|
-
else
|
119
|
-
system "open", url
|
120
162
|
end
|
163
|
+
|
164
|
+
############################################################################
|
165
|
+
|
166
|
+
App.run!
|
data/bin/git-select
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# git-select #story-id
|
4
|
+
#
|
5
|
+
# Looks up your current branches for the first occurence of
|
6
|
+
# the story-id and checkout it
|
7
|
+
#
|
8
|
+
require 'rubygems'
|
9
|
+
require 'term/ansicolor'
|
10
|
+
|
11
|
+
# Helper functions
|
12
|
+
def git_dir?
|
13
|
+
`git config --get remote.origin.url`.strip == ''
|
14
|
+
end
|
15
|
+
|
16
|
+
def process(branches)
|
17
|
+
branches.split("\n").map { |branch| branch.strip.gsub(%r(\* ), '')}
|
18
|
+
end
|
19
|
+
|
20
|
+
def select(branches, story_id)
|
21
|
+
branches.select { |branch| branch =~ /#{story_id}/ }
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
if git_dir?
|
26
|
+
puts "Not a valid git repo"
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
if ARGV[0] && ARGV[0] =~ /^\d+$/
|
31
|
+
story_id = ARGV[0]
|
32
|
+
else
|
33
|
+
puts "story-id is not a number"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Find locally first
|
39
|
+
locals = process(`git branch`)
|
40
|
+
|
41
|
+
# Select matching locals
|
42
|
+
found = select(locals, story_id)
|
43
|
+
|
44
|
+
case found.size
|
45
|
+
when 0
|
46
|
+
puts Term::ANSIColor.yellow 'Failed lookup on local branches'
|
47
|
+
when 1
|
48
|
+
`git checkout #{found.first}`
|
49
|
+
exit 0
|
50
|
+
else
|
51
|
+
puts "Found #{found.size} matches on locals:"
|
52
|
+
found.each do |branch|
|
53
|
+
puts " #{branch}"
|
54
|
+
end
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
# Find remote branch
|
59
|
+
remotes = process(`git branch -a`)
|
60
|
+
|
61
|
+
# Select matching remotes
|
62
|
+
found = select(remotes, story_id)
|
63
|
+
|
64
|
+
case found.size
|
65
|
+
when 0
|
66
|
+
puts Term::ANSIColor.yellow 'Failed lookup on remote branches'
|
67
|
+
else
|
68
|
+
puts "Found #{found.size} matches on remotes:"
|
69
|
+
found.each do |branch|
|
70
|
+
puts " #{branch.gsub(%r(^remotes/origin/), "")}"
|
71
|
+
end
|
72
|
+
exit 1
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'optparse'
|
3
|
+
require 'term/ansicolor'
|
4
|
+
require 'git-whistles/logger'
|
5
|
+
|
6
|
+
|
7
|
+
module Git::Whistles
|
8
|
+
class App
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@options = OpenStruct.new(defaults)
|
12
|
+
@log = Git::Whistles::Logger.new($stderr)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def main(args)
|
17
|
+
parse_args!(args)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.run!
|
22
|
+
new.main(ARGV)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
# default options hash
|
29
|
+
def defaults
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
|
33
|
+
attr :options
|
34
|
+
attr :log
|
35
|
+
|
36
|
+
|
37
|
+
def option_parser
|
38
|
+
@option_parser ||= OptionParser.new do |op|
|
39
|
+
op.banner = "Usage: #{$0}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
def parse_args!(args)
|
46
|
+
begin
|
47
|
+
option_parser.parse!(args)
|
48
|
+
rescue OptionParser::InvalidOption => error
|
49
|
+
die error.message, :usage => true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def run(command)
|
55
|
+
%x(#{command})
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def run!(command)
|
60
|
+
result = %x(#{command})
|
61
|
+
return result if $? == 0
|
62
|
+
die "command '#{command}' failed"
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def die(message, options = {})
|
67
|
+
puts Term::ANSIColor.red(message)
|
68
|
+
if options[:usage]
|
69
|
+
puts
|
70
|
+
puts option_parser.help
|
71
|
+
end
|
72
|
+
exit 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'git-whistles'
|
3
|
+
|
4
|
+
module Git::Whistles
|
5
|
+
class Logger < ::Logger
|
6
|
+
Colors = {
|
7
|
+
'DEBUG' => :reset,
|
8
|
+
'INFO' => :green,
|
9
|
+
'WARN' => :yellow,
|
10
|
+
'ERROR' => :red,
|
11
|
+
'FATAL' => :red,
|
12
|
+
'UNKNOWN' => :red
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
super
|
17
|
+
self.formatter = self.method(:custom_formatter)
|
18
|
+
end
|
19
|
+
|
20
|
+
def custom_formatter(severity, time, progname, msg)
|
21
|
+
msg = msg.sub(/([^¬])$/,"\\1\n").sub(/¬$/,'')
|
22
|
+
Term::ANSIColor.send(Colors[severity], msg)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/git-whistles/version.rb
CHANGED
metadata
CHANGED
@@ -1,128 +1,131 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: git-whistles
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 5
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 6
|
9
|
+
- 1
|
10
|
+
version: 0.6.1
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Julien Letessier
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
|
18
|
+
date: 2012-10-29 00:00:00 +00:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
17
23
|
none: false
|
18
|
-
requirements:
|
19
|
-
- -
|
20
|
-
- !ruby/object:Gem::Version
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 23
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
- 0
|
21
32
|
version: 1.0.0
|
22
|
-
type: :development
|
23
33
|
prerelease: false
|
24
|
-
|
34
|
+
name: bundler
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
25
39
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
prerelease: false
|
31
48
|
name: rake
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
|
-
requirements:
|
35
|
-
- - ! '>='
|
36
|
-
- !ruby/object:Gem::Version
|
37
|
-
version: '0'
|
38
49
|
type: :development
|
39
|
-
|
40
|
-
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
53
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
prerelease: false
|
47
62
|
name: pry
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
|
-
requirements:
|
51
|
-
- - ! '>='
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '0'
|
54
63
|
type: :development
|
55
|
-
|
56
|
-
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
57
67
|
none: false
|
58
|
-
requirements:
|
59
|
-
- -
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
|
62
|
-
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
prerelease: false
|
63
76
|
name: pry-nav
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ! '>='
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
77
|
type: :development
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
requirements:
|
75
|
-
- - ! '>='
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: '0'
|
78
|
-
- !ruby/object:Gem::Dependency
|
79
|
-
name: pivotal-tracker
|
80
|
-
requirement: !ruby/object:Gem::Requirement
|
78
|
+
version_requirements: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
81
|
none: false
|
82
|
-
requirements:
|
82
|
+
requirements:
|
83
83
|
- - ~>
|
84
|
-
- !ruby/object:Gem::Version
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 7
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
- 5
|
89
|
+
- 6
|
85
90
|
version: 0.5.6
|
86
|
-
type: :runtime
|
87
91
|
prerelease: false
|
88
|
-
|
92
|
+
name: pivotal-tracker
|
93
|
+
type: :runtime
|
94
|
+
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
89
97
|
none: false
|
90
|
-
requirements:
|
91
|
-
- -
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
|
94
|
-
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
prerelease: false
|
95
106
|
name: term-ansicolor
|
96
|
-
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
|
-
requirements:
|
99
|
-
- - ! '>='
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
version: '0'
|
102
107
|
type: :runtime
|
103
|
-
|
104
|
-
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
|
-
requirements:
|
107
|
-
- - ! '>='
|
108
|
-
- !ruby/object:Gem::Version
|
109
|
-
version: '0'
|
108
|
+
version_requirements: *id006
|
110
109
|
description: A few helpers for classic Git workflows
|
111
|
-
email:
|
110
|
+
email:
|
112
111
|
- julien.letessier@gmail.com
|
113
|
-
executables:
|
112
|
+
executables:
|
114
113
|
- git-chop
|
115
114
|
- git-ff-all-branches
|
116
115
|
- git-list-branches
|
117
116
|
- git-merge-po
|
118
117
|
- git-outstanding-features
|
119
118
|
- git-pull-request
|
119
|
+
- git-select
|
120
120
|
- git-stash-and-checkout
|
121
121
|
extensions: []
|
122
|
+
|
122
123
|
extra_rdoc_files: []
|
123
|
-
|
124
|
+
|
125
|
+
files:
|
124
126
|
- .gitignore
|
125
127
|
- Gemfile
|
128
|
+
- Gemfile.lock
|
126
129
|
- LICENSE
|
127
130
|
- README.md
|
128
131
|
- Rakefile
|
@@ -132,9 +135,13 @@ files:
|
|
132
135
|
- bin/git-merge-po
|
133
136
|
- bin/git-outstanding-features
|
134
137
|
- bin/git-pull-request
|
138
|
+
- bin/git-select
|
135
139
|
- bin/git-stash-and-checkout
|
136
140
|
- git-whistles.gemspec
|
137
141
|
- lib/git-whistles.rb
|
142
|
+
- lib/git-whistles/app.rb
|
143
|
+
- lib/git-whistles/helpers.rb
|
144
|
+
- lib/git-whistles/logger.rb
|
138
145
|
- lib/git-whistles/version.rb
|
139
146
|
- libexec/git-chop.sh
|
140
147
|
- libexec/git-list-branches.sh
|
@@ -142,32 +149,41 @@ files:
|
|
142
149
|
- libexec/git-outstanding-features.sh
|
143
150
|
- libexec/git-stash-and-checkout.sh
|
144
151
|
- libexec/runner.rb
|
152
|
+
has_rdoc: true
|
145
153
|
homepage: http://github.com/mezis/git-whistles
|
146
154
|
licenses: []
|
155
|
+
|
147
156
|
post_install_message:
|
148
157
|
rdoc_options: []
|
149
|
-
|
158
|
+
|
159
|
+
require_paths:
|
150
160
|
- lib
|
151
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
161
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
162
|
none: false
|
153
|
-
requirements:
|
154
|
-
- -
|
155
|
-
- !ruby/object:Gem::Version
|
156
|
-
|
157
|
-
segments:
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
hash: 3
|
167
|
+
segments:
|
158
168
|
- 0
|
159
|
-
|
160
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
version: "0"
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
171
|
none: false
|
162
|
-
requirements:
|
163
|
-
- -
|
164
|
-
- !ruby/object:Gem::Version
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
hash: 23
|
176
|
+
segments:
|
177
|
+
- 1
|
178
|
+
- 3
|
179
|
+
- 6
|
165
180
|
version: 1.3.6
|
166
181
|
requirements: []
|
182
|
+
|
167
183
|
rubyforge_project:
|
168
|
-
rubygems_version: 1.
|
184
|
+
rubygems_version: 1.3.9.5
|
169
185
|
signing_key:
|
170
186
|
specification_version: 3
|
171
|
-
summary:
|
172
|
-
PO file handling, issuing pull requests slightly simpler.'
|
187
|
+
summary: "A few helpers for classic Git workflows: makes branching and merging, PO file handling, issuing pull requests slightly simpler."
|
173
188
|
test_files: []
|
189
|
+
|