socialcast-git-extensions 0.4.0
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/bin/git-integrate +17 -0
- data/bin/git-promote +19 -0
- data/bin/git-release +20 -0
- data/bin/git-wtf +364 -0
- data/lib/socialcast-git-extensions.rb +87 -0
- data/socialcast-git-extensions.gemspec +71 -0
- data/test/helper.rb +10 -0
- data/test/test_socialcast-git-extensions.rb +7 -0
- metadata +156 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Ryan Sonnek
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= socialcast-git-extensions
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Ryan Sonnek. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "socialcast-git-extensions"
|
8
|
+
gem.summary = %Q{git extension scripts for socialcast workflow}
|
9
|
+
gem.description = %Q{git extension scripts for socialcast workflow}
|
10
|
+
gem.email = "ryan@socialcast.com"
|
11
|
+
gem.homepage = "http://github.com/wireframe/socialcast-git-extensions"
|
12
|
+
gem.authors = ["Ryan Sonnek"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
gem.add_runtime_dependency "grit", ">= 0"
|
15
|
+
gem.add_runtime_dependency "jira4r", ">= 0"
|
16
|
+
gem.add_runtime_dependency "soap4r", ">= 0"
|
17
|
+
gem.add_runtime_dependency "activesupport", ">= 2.3.5"
|
18
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
19
|
+
end
|
20
|
+
Jeweler::GemcutterTasks.new
|
21
|
+
rescue LoadError
|
22
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'rake/testtask'
|
26
|
+
Rake::TestTask.new(:test) do |test|
|
27
|
+
test.libs << 'lib' << 'test'
|
28
|
+
test.pattern = 'test/**/test_*.rb'
|
29
|
+
test.verbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'rcov/rcovtask'
|
34
|
+
Rcov::RcovTask.new do |test|
|
35
|
+
test.libs << 'test'
|
36
|
+
test.pattern = 'test/**/test_*.rb'
|
37
|
+
test.verbose = true
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
task :rcov do
|
41
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
task :test => :check_dependencies
|
46
|
+
|
47
|
+
task :default => :test
|
48
|
+
|
49
|
+
require 'rake/rdoctask'
|
50
|
+
Rake::RDocTask.new do |rdoc|
|
51
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
52
|
+
|
53
|
+
rdoc.rdoc_dir = 'rdoc'
|
54
|
+
rdoc.title = "socialcast-git-extensions #{version}"
|
55
|
+
rdoc.rdoc_files.include('README*')
|
56
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
57
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
data/bin/git-integrate
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'grit'
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'socialcast-git-extensions.rb')
|
5
|
+
include Socialcast
|
6
|
+
|
7
|
+
repo = Grit::Repo.new(Dir.pwd)
|
8
|
+
branch = Grit::Head.current(repo).name
|
9
|
+
|
10
|
+
update(branch)
|
11
|
+
integrate(branch, 'staging')
|
12
|
+
|
13
|
+
ticket = ARGV.shift
|
14
|
+
if ticket
|
15
|
+
update_ticket ticket, {:branch => branch, :in_staging => true}
|
16
|
+
start_ticket ticket
|
17
|
+
end
|
data/bin/git-promote
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'grit'
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'socialcast-git-extensions.rb')
|
5
|
+
include Socialcast
|
6
|
+
|
7
|
+
repo = Grit::Repo.new(Dir.pwd)
|
8
|
+
branch = Grit::Head.current(repo).name
|
9
|
+
|
10
|
+
ticket = ARGV.shift
|
11
|
+
raise 'JIRA ticket is required in order to move into next_release'
|
12
|
+
|
13
|
+
update(branch)
|
14
|
+
integrate(branch, 'next_release')
|
15
|
+
|
16
|
+
update_ticket ticket, {:branch => branch, :in_staging => true}
|
17
|
+
resolve_ticket ticket
|
18
|
+
|
19
|
+
integrate(branch, 'staging')
|
data/bin/git-release
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'grit'
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'socialcast-git-extensions.rb')
|
5
|
+
require 'readline'
|
6
|
+
include Socialcast
|
7
|
+
|
8
|
+
repo = Grit::Repo.new(Dir.pwd)
|
9
|
+
branch = Grit::Head.current(repo).name
|
10
|
+
|
11
|
+
raise "Cannot release reserved branch" if %w{master staging}.include?(branch)
|
12
|
+
|
13
|
+
exit unless Readline.readline("This will release #{branch} to production. Are you sure (y/n)? ") == 'y'
|
14
|
+
run_cmd 'git checkout master'
|
15
|
+
run_cmd 'git pull origin master'
|
16
|
+
run_cmd "git pull . #{branch}"
|
17
|
+
run_cmd 'git push origin HEAD'
|
18
|
+
run_cmd "grb rm #{branch}"
|
19
|
+
|
20
|
+
run_cmd "git integrate"
|
data/bin/git-wtf
ADDED
@@ -0,0 +1,364 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
HELP = <<EOS
|
4
|
+
git-wtf displays the state of your repository in a readable, easy-to-scan
|
5
|
+
format. It's useful for getting a summary of how a branch relates to a remote
|
6
|
+
server, and for wrangling many topic branches.
|
7
|
+
|
8
|
+
git-wtf can show you:
|
9
|
+
- How a branch relates to the remote repo, if it's a tracking branch.
|
10
|
+
- How a branch relates to integration branches, if it's a feature branch.
|
11
|
+
- How a branch relates to the feature branches, if it's an integration
|
12
|
+
branch.
|
13
|
+
|
14
|
+
git-wtf is best used before a git push, or between a git fetch and a git
|
15
|
+
merge. Be sure to set color.ui to auto or yes for maximum viewing pleasure.
|
16
|
+
EOS
|
17
|
+
|
18
|
+
KEY = <<EOS
|
19
|
+
KEY:
|
20
|
+
() branch only exists locally
|
21
|
+
{} branch only exists on a remote repo
|
22
|
+
[] branch exists locally and remotely
|
23
|
+
|
24
|
+
x merge occurs both locally and remotely
|
25
|
+
~ merge occurs only locally
|
26
|
+
(space) branch isn't merged in
|
27
|
+
|
28
|
+
(It's possible for merges to occur remotely and not locally, of course, but
|
29
|
+
that's a less common case and git-wtf currently doesn't display anything
|
30
|
+
special for it.)
|
31
|
+
EOS
|
32
|
+
|
33
|
+
USAGE = <<EOS
|
34
|
+
Usage: git wtf [branch+] [options]
|
35
|
+
|
36
|
+
If [branch] is not specified, git-wtf will use the current branch. The possible
|
37
|
+
[options] are:
|
38
|
+
|
39
|
+
-l, --long include author info and date for each commit
|
40
|
+
-a, --all show all branches across all remote repos, not just
|
41
|
+
those from origin
|
42
|
+
-A, --all-commits show all commits, not just the first 5
|
43
|
+
-s, --short don't show commits
|
44
|
+
-k, --key show key
|
45
|
+
-r, --relations show relation to features / integration branches
|
46
|
+
--dump-config print out current configuration and exit
|
47
|
+
|
48
|
+
git-wtf uses some heuristics to determine which branches are integration
|
49
|
+
branches, and which are feature branches. (Specifically, it assumes the
|
50
|
+
integration branches are named "master", "next" and "edge".) If it guesses
|
51
|
+
incorrectly, you will have to create a .git-wtfrc file.
|
52
|
+
|
53
|
+
To start building a configuration file, run "git-wtf --dump-config >
|
54
|
+
.git-wtfrc" and edit it. The config file is a YAML file that specifies the
|
55
|
+
integration branches, any branches to ignore, and the max number of commits to
|
56
|
+
display when --all-commits isn't used. git-wtf will look for a .git-wtfrc file
|
57
|
+
starting in the current directory, and recursively up to the root.
|
58
|
+
|
59
|
+
IMPORTANT NOTE: all local branches referenced in .git-wtfrc must be prefixed
|
60
|
+
with heads/, e.g. "heads/master". Remote branches must be of the form
|
61
|
+
remotes/<remote>/<branch>.
|
62
|
+
EOS
|
63
|
+
|
64
|
+
COPYRIGHT = <<EOS
|
65
|
+
git-wtf Copyright 2008--2009 William Morgan <wmorgan at the masanjin dot nets>.
|
66
|
+
This program is free software: you can redistribute it and/or modify it
|
67
|
+
under the terms of the GNU General Public License as published by the Free
|
68
|
+
Software Foundation, either version 3 of the License, or (at your option)
|
69
|
+
any later version.
|
70
|
+
|
71
|
+
This program is distributed in the hope that it will be useful, but WITHOUT
|
72
|
+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
73
|
+
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
74
|
+
more details.
|
75
|
+
|
76
|
+
You can find the GNU General Public License at: http://www.gnu.org/licenses/
|
77
|
+
EOS
|
78
|
+
|
79
|
+
require 'yaml'
|
80
|
+
CONFIG_FN = ".git-wtfrc"
|
81
|
+
|
82
|
+
class Numeric; def pluralize s; "#{to_s} #{s}" + (self != 1 ? "s" : "") end end
|
83
|
+
|
84
|
+
if ARGV.delete("--help") || ARGV.delete("-h")
|
85
|
+
puts USAGE
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
|
89
|
+
## poor man's trollop
|
90
|
+
$long = ARGV.delete("--long") || ARGV.delete("-l")
|
91
|
+
$short = ARGV.delete("--short") || ARGV.delete("-s")
|
92
|
+
$all = ARGV.delete("--all") || ARGV.delete("-a")
|
93
|
+
$all_commits = ARGV.delete("--all-commits") || ARGV.delete("-A")
|
94
|
+
$dump_config = ARGV.delete("--dump-config")
|
95
|
+
$key = ARGV.delete("--key") || ARGV.delete("-k")
|
96
|
+
$show_relations = ARGV.delete("--relations") || ARGV.delete("-r")
|
97
|
+
ARGV.each { |a| abort "Error: unknown argument #{a}." if a =~ /^--/ }
|
98
|
+
|
99
|
+
## search up the path for a file
|
100
|
+
def find_file fn
|
101
|
+
while true
|
102
|
+
return fn if File.exist? fn
|
103
|
+
fn2 = File.join("..", fn)
|
104
|
+
return nil if File.expand_path(fn2) == File.expand_path(fn)
|
105
|
+
fn = fn2
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
want_color = `git config color.wtf`
|
110
|
+
want_color = `git config color.ui` if want_color.empty?
|
111
|
+
$color = case want_color.chomp
|
112
|
+
when "true"; true
|
113
|
+
when "auto"; $stdout.tty?
|
114
|
+
end
|
115
|
+
|
116
|
+
def red s; $color ? "\033[31m#{s}\033[0m" : s end
|
117
|
+
def green s; $color ? "\033[32m#{s}\033[0m" : s end
|
118
|
+
def yellow s; $color ? "\033[33m#{s}\033[0m" : s end
|
119
|
+
def cyan s; $color ? "\033[36m#{s}\033[0m" : s end
|
120
|
+
def grey s; $color ? "\033[1;30m#{s}\033[0m" : s end
|
121
|
+
def purple s; $color ? "\033[35m#{s}\033[0m" : s end
|
122
|
+
|
123
|
+
## the set of commits in 'to' that aren't in 'from'.
|
124
|
+
## if empty, 'to' has been merged into 'from'.
|
125
|
+
def commits_between from, to
|
126
|
+
if $long
|
127
|
+
`git log --pretty=format:"- %s [#{yellow "%h"}] (#{purple "%ae"}; %ar)" #{from}..#{to}`
|
128
|
+
else
|
129
|
+
`git log --pretty=format:"- %s [#{yellow "%h"}]" #{from}..#{to}`
|
130
|
+
end.split(/[\r\n]+/)
|
131
|
+
end
|
132
|
+
|
133
|
+
def show_commits commits, prefix=" "
|
134
|
+
if commits.empty?
|
135
|
+
puts "#{prefix} none"
|
136
|
+
else
|
137
|
+
max = $all_commits ? commits.size : $config["max_commits"]
|
138
|
+
max -= 1 if max == commits.size - 1 # never show "and 1 more"
|
139
|
+
commits[0 ... max].each { |c| puts "#{prefix}#{c}" }
|
140
|
+
puts grey("#{prefix}... and #{commits.size - max} more (use -A to see all).") if commits.size > max
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def ahead_behind_string ahead, behind
|
145
|
+
[ahead.empty? ? nil : "#{ahead.size.pluralize 'commit'} ahead",
|
146
|
+
behind.empty? ? nil : "#{behind.size.pluralize 'commit'} behind"].
|
147
|
+
compact.join("; ")
|
148
|
+
end
|
149
|
+
|
150
|
+
def widget merged_in, remote_only=false, local_only=false, local_only_merge=false
|
151
|
+
left, right = case
|
152
|
+
when remote_only; %w({ })
|
153
|
+
when local_only; %w{( )}
|
154
|
+
else %w([ ])
|
155
|
+
end
|
156
|
+
middle = case
|
157
|
+
when merged_in && local_only_merge; green("~")
|
158
|
+
when merged_in; green("x")
|
159
|
+
else " "
|
160
|
+
end
|
161
|
+
print left, middle, right
|
162
|
+
end
|
163
|
+
|
164
|
+
def show b
|
165
|
+
have_both = b[:local_branch] && b[:remote_branch]
|
166
|
+
|
167
|
+
pushc, pullc, oosync = if have_both
|
168
|
+
[x = commits_between(b[:remote_branch], b[:local_branch]),
|
169
|
+
y = commits_between(b[:local_branch], b[:remote_branch]),
|
170
|
+
!x.empty? && !y.empty?]
|
171
|
+
end
|
172
|
+
|
173
|
+
if b[:local_branch]
|
174
|
+
puts "Local branch: " + green(b[:local_branch].sub(/^heads\//, ""))
|
175
|
+
|
176
|
+
if have_both
|
177
|
+
if pushc.empty?
|
178
|
+
puts "#{widget true} in sync with remote"
|
179
|
+
else
|
180
|
+
action = oosync ? "push after rebase / merge" : "push"
|
181
|
+
puts "#{widget false} NOT in sync with remote (you should #{action})"
|
182
|
+
show_commits pushc unless $short
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
if b[:remote_branch]
|
188
|
+
puts "Remote branch: #{cyan b[:remote_branch]} (#{b[:remote_url]})"
|
189
|
+
|
190
|
+
if have_both
|
191
|
+
if pullc.empty?
|
192
|
+
puts "#{widget true} in sync with local"
|
193
|
+
else
|
194
|
+
action = pushc.empty? ? "merge" : "rebase / merge"
|
195
|
+
puts "#{widget false} NOT in sync with local (you should #{action})"
|
196
|
+
show_commits pullc unless $short
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
puts "\n#{red "WARNING"}: local and remote branches have diverged. A merge will occur unless you rebase." if oosync
|
202
|
+
end
|
203
|
+
|
204
|
+
def show_relations b, all_branches
|
205
|
+
ibs, fbs = all_branches.partition { |name, br| $config["integration-branches"].include?(br[:local_branch]) || $config["integration-branches"].include?(br[:remote_branch]) }
|
206
|
+
if $config["integration-branches"].include? b[:local_branch]
|
207
|
+
puts "\nFeature branches:" unless fbs.empty?
|
208
|
+
fbs.each do |name, br|
|
209
|
+
next if $config["ignore"].member?(br[:local_branch]) || $config["ignore"].member?(br[:remote_branch])
|
210
|
+
next if br[:ignore]
|
211
|
+
local_only = br[:remote_branch].nil?
|
212
|
+
remote_only = br[:local_branch].nil?
|
213
|
+
name = if local_only
|
214
|
+
purple br[:name]
|
215
|
+
elsif remote_only
|
216
|
+
cyan br[:name]
|
217
|
+
else
|
218
|
+
green br[:name]
|
219
|
+
end
|
220
|
+
|
221
|
+
## for remote_only branches, we'll compute wrt the remote branch head. otherwise, we'll
|
222
|
+
## use the local branch head.
|
223
|
+
head = remote_only ? br[:remote_branch] : br[:local_branch]
|
224
|
+
|
225
|
+
remote_ahead = b[:remote_branch] ? commits_between(b[:remote_branch], head) : []
|
226
|
+
local_ahead = b[:local_branch] ? commits_between(b[:local_branch], head) : []
|
227
|
+
|
228
|
+
if local_ahead.empty? && remote_ahead.empty?
|
229
|
+
puts "#{widget true, remote_only, local_only} #{name} #{local_only ? "(local-only) " : ""}is merged in"
|
230
|
+
elsif local_ahead.empty?
|
231
|
+
puts "#{widget true, remote_only, local_only, true} #{name} merged in (only locally)"
|
232
|
+
else
|
233
|
+
behind = commits_between head, (br[:local_branch] || br[:remote_branch])
|
234
|
+
ahead = remote_only ? remote_ahead : local_ahead
|
235
|
+
puts "#{widget false, remote_only, local_only} #{name} #{local_only ? "(local-only) " : ""}is NOT merged in (#{ahead_behind_string ahead, behind})"
|
236
|
+
show_commits ahead unless $short
|
237
|
+
end
|
238
|
+
end
|
239
|
+
else
|
240
|
+
puts "\nIntegration branches:" unless ibs.empty? # unlikely
|
241
|
+
ibs.sort_by { |v, br| v }.each do |v, br|
|
242
|
+
next if $config["ignore"].member?(br[:local_branch]) || $config["ignore"].member?(br[:remote_branch])
|
243
|
+
next if br[:ignore]
|
244
|
+
local_only = br[:remote_branch].nil?
|
245
|
+
remote_only = br[:local_branch].nil?
|
246
|
+
name = remote_only ? cyan(br[:name]) : green(br[:name])
|
247
|
+
|
248
|
+
ahead = commits_between v, (b[:local_branch] || b[:remote_branch])
|
249
|
+
if ahead.empty?
|
250
|
+
puts "#{widget true, local_only} merged into #{name}"
|
251
|
+
else
|
252
|
+
#behind = commits_between b[:local_branch], v
|
253
|
+
puts "#{widget false, local_only} NOT merged into #{name} (#{ahead.size.pluralize 'commit'} ahead)"
|
254
|
+
show_commits ahead unless $short
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
#### EXECUTION STARTS HERE ####
|
261
|
+
|
262
|
+
## find config file and load it
|
263
|
+
$config = { "integration-branches" => %w(heads/master heads/next heads/edge), "ignore" => [], "max_commits" => 5 }.merge begin
|
264
|
+
fn = find_file CONFIG_FN
|
265
|
+
if fn && (h = YAML::load_file(fn)) # yaml turns empty files into false
|
266
|
+
h["integration-branches"] ||= h["versions"] # support old nomenclature
|
267
|
+
h
|
268
|
+
else
|
269
|
+
{}
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
if $dump_config
|
274
|
+
puts $config.to_yaml
|
275
|
+
exit
|
276
|
+
end
|
277
|
+
|
278
|
+
## first, index registered remotes
|
279
|
+
remotes = `git config --get-regexp ^remote\.\*\.url`.split(/[\r\n]+/).inject({}) do |hash, l|
|
280
|
+
l =~ /^remote\.(.+?)\.url (.+)$/ or next hash
|
281
|
+
hash[$1] ||= $2
|
282
|
+
hash
|
283
|
+
end
|
284
|
+
|
285
|
+
## next, index followed branches
|
286
|
+
branches = `git config --get-regexp ^branch\.`.split(/[\r\n]+/).inject({}) do |hash, l|
|
287
|
+
case l
|
288
|
+
when /branch\.(.*?)\.remote (.+)/
|
289
|
+
name, remote = $1, $2
|
290
|
+
|
291
|
+
hash[name] ||= {}
|
292
|
+
hash[name].merge! :remote => remote, :remote_url => remotes[remote]
|
293
|
+
when /branch\.(.*?)\.merge ((refs\/)?heads\/)?(.+)/
|
294
|
+
name, remote_branch = $1, $4
|
295
|
+
hash[name] ||= {}
|
296
|
+
hash[name].merge! :remote_mergepoint => remote_branch
|
297
|
+
end
|
298
|
+
hash
|
299
|
+
end
|
300
|
+
|
301
|
+
## finally, index all branches
|
302
|
+
remote_branches = {}
|
303
|
+
`git show-ref`.split(/[\r\n]+/).each do |l|
|
304
|
+
sha1, ref = l.chomp.split " refs/"
|
305
|
+
|
306
|
+
if ref =~ /^heads\/(.+)$/ # local branch
|
307
|
+
name = $1
|
308
|
+
next if name == "HEAD"
|
309
|
+
branches[name] ||= {}
|
310
|
+
branches[name].merge! :name => name, :local_branch => ref
|
311
|
+
elsif ref =~ /^remotes\/(.+?)\/(.+)$/ # remote branch
|
312
|
+
remote, name = $1, $2
|
313
|
+
remote_branches["#{remote}/#{name}"] = true
|
314
|
+
next if name == "HEAD"
|
315
|
+
ignore = !($all || remote == "origin")
|
316
|
+
|
317
|
+
branch = name
|
318
|
+
if branches[name] && branches[name][:remote] == remote
|
319
|
+
# nothing
|
320
|
+
else
|
321
|
+
name = "#{remote}/#{branch}"
|
322
|
+
end
|
323
|
+
|
324
|
+
branches[name] ||= {}
|
325
|
+
branches[name].merge! :name => name, :remote => remote, :remote_branch => "#{remote}/#{branch}", :remote_url => remotes[remote], :ignore => ignore
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
## assemble remotes
|
330
|
+
branches.each do |k, b|
|
331
|
+
next unless b[:remote] && b[:remote_mergepoint]
|
332
|
+
b[:remote_branch] = if b[:remote] == "."
|
333
|
+
b[:remote_mergepoint]
|
334
|
+
else
|
335
|
+
t = "#{b[:remote]}/#{b[:remote_mergepoint]}"
|
336
|
+
remote_branches[t] && t # only if it's still alive
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
show_dirty = ARGV.empty?
|
341
|
+
targets = if ARGV.empty?
|
342
|
+
[`git symbolic-ref HEAD`.chomp.sub(/^refs\/heads\//, "")]
|
343
|
+
else
|
344
|
+
ARGV.map { |x| x.sub(/^heads\//, "") }
|
345
|
+
end.map { |t| branches[t] or abort "Error: can't find branch #{t.inspect}." }
|
346
|
+
|
347
|
+
targets.each do |t|
|
348
|
+
show t
|
349
|
+
show_relations t, branches if $show_relations || t[:remote_branch].nil?
|
350
|
+
end
|
351
|
+
|
352
|
+
modified = show_dirty && `git ls-files -m` != ""
|
353
|
+
uncommitted = show_dirty && `git diff-index --cached HEAD` != ""
|
354
|
+
|
355
|
+
if $key
|
356
|
+
puts
|
357
|
+
puts KEY
|
358
|
+
end
|
359
|
+
|
360
|
+
puts if modified || uncommitted
|
361
|
+
puts "#{red "NOTE"}: working directory contains modified files." if modified
|
362
|
+
puts "#{red "NOTE"}: staging area contains staged but uncommitted files." if uncommitted
|
363
|
+
|
364
|
+
# the end!
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'jira4r'
|
2
|
+
require 'activesupport'
|
3
|
+
|
4
|
+
module Socialcast
|
5
|
+
GIT_BRANCH_FIELD = 'customfield_10010'
|
6
|
+
IN_STAGING_FIELD = 'customfield_10020'
|
7
|
+
JIRA_CREDENTIALS_FILE = File.expand_path('~/.jira_key')
|
8
|
+
|
9
|
+
def jira_credentials
|
10
|
+
@credentials ||= YAML.load_file(JIRA_CREDENTIALS_FILE).symbolize_keys!
|
11
|
+
@credentials
|
12
|
+
end
|
13
|
+
def jira_server
|
14
|
+
#make sure soap4r is installed
|
15
|
+
require 'jira4r'
|
16
|
+
require "highline/import.rb"
|
17
|
+
|
18
|
+
return @jira if @jira
|
19
|
+
if !File.exists?(JIRA_CREDENTIALS_FILE)
|
20
|
+
input = {}
|
21
|
+
input[:username] = HighLine.ask("JIRA username: ")
|
22
|
+
input[:password] = HighLine.ask("JIRA password: ") { |q| q.echo = "*" }
|
23
|
+
|
24
|
+
File.open(JIRA_CREDENTIALS_FILE, "w") do |f|
|
25
|
+
f.write input.to_yaml
|
26
|
+
end
|
27
|
+
end
|
28
|
+
File.chmod 0600, JIRA_CREDENTIALS_FILE
|
29
|
+
credentials = jira_credentials
|
30
|
+
|
31
|
+
begin
|
32
|
+
@jira = Jira4R::JiraTool.new 2, "https://issues.socialcast.com"
|
33
|
+
@jira.login credentials[:username], credentials[:password]
|
34
|
+
return @jira
|
35
|
+
rescue => e
|
36
|
+
puts "Error: #{e.message}"
|
37
|
+
File.delete config_file
|
38
|
+
raise e
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_ticket(ticket, options = {})
|
43
|
+
fields = []
|
44
|
+
fields << Jira4R::V2::RemoteFieldValue.new(GIT_BRANCH_FIELD, [options[:branch]]) if options[:branch]
|
45
|
+
fields << Jira4R::V2::RemoteFieldValue.new(IN_STAGING_FIELD, ['true']) if options[:in_staging]
|
46
|
+
jira_server.updateIssue ticket, fields
|
47
|
+
end
|
48
|
+
def start_ticket(ticket)
|
49
|
+
issue = jira_server.getIssue ticket
|
50
|
+
if issue.status == '1'
|
51
|
+
puts "Transitioning ticket from 'Open' to 'In Progress'"
|
52
|
+
start_work_action = '11'
|
53
|
+
jira_server.progressWorkflowAction ticket, start_work_action, []
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def resolve_ticket(ticket)
|
57
|
+
issue = jira_server.getIssue ticket
|
58
|
+
if issue.status == '3'
|
59
|
+
puts 'Transitioning ticket from "In Progress" to "Resolved"'
|
60
|
+
finish_work_action = '21'
|
61
|
+
jira_server.progressWorkflowAction ticket, finish_work_action, []
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def run_cmd(cmd)
|
66
|
+
puts "\nRunning: #{cmd}"
|
67
|
+
raise "#{cmd} failed" unless system cmd
|
68
|
+
end
|
69
|
+
|
70
|
+
def update(branch)
|
71
|
+
puts "updating #{branch} to have most recent changes from master"
|
72
|
+
run_cmd "git pull origin #{branch}" rescue nil
|
73
|
+
run_cmd 'git pull origin master'
|
74
|
+
run_cmd 'git push origin HEAD'
|
75
|
+
end
|
76
|
+
def integrate(branch, destination_branch = 'staging')
|
77
|
+
puts "integrating #{branch} into #{destination_branch}"
|
78
|
+
run_cmd "git remote prune origin"
|
79
|
+
run_cmd "git branch -D #{destination_branch}" rescue nil
|
80
|
+
run_cmd "grb track #{destination_branch}"
|
81
|
+
run_cmd "git checkout #{destination_branch}"
|
82
|
+
run_cmd "git pull . #{branch}"
|
83
|
+
run_cmd "git push origin HEAD"
|
84
|
+
|
85
|
+
run_cmd "git checkout #{branch}"
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{socialcast-git-extensions}
|
8
|
+
s.version = "0.4.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ryan Sonnek"]
|
12
|
+
s.date = %q{2010-07-02}
|
13
|
+
s.description = %q{git extension scripts for socialcast workflow}
|
14
|
+
s.email = %q{ryan@socialcast.com}
|
15
|
+
s.executables = ["git-integrate", "git-promote", "git-release", "git-wtf"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE",
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
".gitignore",
|
23
|
+
"LICENSE",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"bin/git-integrate",
|
28
|
+
"bin/git-promote",
|
29
|
+
"bin/git-release",
|
30
|
+
"bin/git-wtf",
|
31
|
+
"lib/socialcast-git-extensions.rb",
|
32
|
+
"socialcast-git-extensions.gemspec",
|
33
|
+
"test/helper.rb",
|
34
|
+
"test/test_socialcast-git-extensions.rb"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/wireframe/socialcast-git-extensions}
|
37
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.3.7}
|
40
|
+
s.summary = %q{git extension scripts for socialcast workflow}
|
41
|
+
s.test_files = [
|
42
|
+
"test/helper.rb",
|
43
|
+
"test/test_socialcast-git-extensions.rb"
|
44
|
+
]
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
52
|
+
s.add_runtime_dependency(%q<grit>, [">= 0"])
|
53
|
+
s.add_runtime_dependency(%q<jira4r>, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<soap4r>, [">= 0"])
|
55
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 2.3.5"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
58
|
+
s.add_dependency(%q<grit>, [">= 0"])
|
59
|
+
s.add_dependency(%q<jira4r>, [">= 0"])
|
60
|
+
s.add_dependency(%q<soap4r>, [">= 0"])
|
61
|
+
s.add_dependency(%q<activesupport>, [">= 2.3.5"])
|
62
|
+
end
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
65
|
+
s.add_dependency(%q<grit>, [">= 0"])
|
66
|
+
s.add_dependency(%q<jira4r>, [">= 0"])
|
67
|
+
s.add_dependency(%q<soap4r>, [">= 0"])
|
68
|
+
s.add_dependency(%q<activesupport>, [">= 2.3.5"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: socialcast-git-extensions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ryan Sonnek
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-02 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: thoughtbot-shoulda
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: grit
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: jira4r
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: soap4r
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :runtime
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: activesupport
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 9
|
86
|
+
segments:
|
87
|
+
- 2
|
88
|
+
- 3
|
89
|
+
- 5
|
90
|
+
version: 2.3.5
|
91
|
+
type: :runtime
|
92
|
+
version_requirements: *id005
|
93
|
+
description: git extension scripts for socialcast workflow
|
94
|
+
email: ryan@socialcast.com
|
95
|
+
executables:
|
96
|
+
- git-integrate
|
97
|
+
- git-promote
|
98
|
+
- git-release
|
99
|
+
- git-wtf
|
100
|
+
extensions: []
|
101
|
+
|
102
|
+
extra_rdoc_files:
|
103
|
+
- LICENSE
|
104
|
+
- README.rdoc
|
105
|
+
files:
|
106
|
+
- .document
|
107
|
+
- .gitignore
|
108
|
+
- LICENSE
|
109
|
+
- README.rdoc
|
110
|
+
- Rakefile
|
111
|
+
- VERSION
|
112
|
+
- bin/git-integrate
|
113
|
+
- bin/git-promote
|
114
|
+
- bin/git-release
|
115
|
+
- bin/git-wtf
|
116
|
+
- lib/socialcast-git-extensions.rb
|
117
|
+
- socialcast-git-extensions.gemspec
|
118
|
+
- test/helper.rb
|
119
|
+
- test/test_socialcast-git-extensions.rb
|
120
|
+
has_rdoc: true
|
121
|
+
homepage: http://github.com/wireframe/socialcast-git-extensions
|
122
|
+
licenses: []
|
123
|
+
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options:
|
126
|
+
- --charset=UTF-8
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
hash: 3
|
135
|
+
segments:
|
136
|
+
- 0
|
137
|
+
version: "0"
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
hash: 3
|
144
|
+
segments:
|
145
|
+
- 0
|
146
|
+
version: "0"
|
147
|
+
requirements: []
|
148
|
+
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 1.3.7
|
151
|
+
signing_key:
|
152
|
+
specification_version: 3
|
153
|
+
summary: git extension scripts for socialcast workflow
|
154
|
+
test_files:
|
155
|
+
- test/helper.rb
|
156
|
+
- test/test_socialcast-git-extensions.rb
|