thegarage-gitx 1.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e0cf04ff44b6aa700e06c19ef9fe78844c3c6283
4
+ data.tar.gz: 9182d96bdb216bcdf13194d726ce73df036b054c
5
+ SHA512:
6
+ metadata.gz: 6f923b1c25c8f2bbe0514abaa876fe00a6014e7521ec42ab754194edd367a2199949c69a670f5e6680dcb2cdbb84b8c1693cd6552d92da1b37b4ccd28188c8e3
7
+ data.tar.gz: a24d7e146009524bb88626b8a0ed580a25bab740720a263b6c226dfb7dc5fca9064beb3f4832da429c4ce505f7a3be86ad8c0675110b3fdf3033fff0411c6698
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## bundler
22
+ Gemfile.lock
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ thegarage-gitx
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in your gemspec file
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ryan Sonnek
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # thegarage-gitx
2
+
3
+ Useful Git eXtensions for Development workflow at The Garage.
4
+
5
+ Inspired by the [socialcast-git-extensions gem](https://github.com/socialcast/socialcast-git-extensions)
6
+
7
+ # Git Extensions for Workflow
8
+
9
+ ### Options
10
+ * `-v` = verbose for debugging commands
11
+
12
+ ## git start <new_branch_name (optional)>
13
+
14
+ update local repository with latest upstream changes and create a new feature branch
15
+
16
+ ## git update
17
+
18
+ update the local feature branch with latest remote changes plus upstream released changes.
19
+
20
+ ## git integrate <aggregate_branch_name (optional, default: staging)>
21
+
22
+ integrate the current feature branch into an aggregate branch (ex: prototype, staging)
23
+
24
+ ## git reviewrequest
25
+
26
+ create a pull request on github for peer review of the current branch.
27
+
28
+ ## git release
29
+
30
+ release the current feature branch to master. This operation will perform the following:
31
+ * pull in latest code from remote branch
32
+ * merge in latest code from master branch
33
+ * prompt user to confirm they actually want to perform the release
34
+ * merge current branch into master
35
+ * cleanup merged branches from remote server
36
+
37
+ # Extra Utility Git Extensions
38
+
39
+ ## git cleanup
40
+
41
+ delete released branches after they have been merged into master.
42
+
43
+ ## git nuke <aggregate_branch_name>
44
+
45
+ reset an aggregate branch (ex: prototype, staging) back to a known good state.
46
+
47
+
48
+ ## Note on Patches/Pull Requests
49
+
50
+ * Fork the project.
51
+ * Make your feature addition or bug fix.
52
+ * Add tests for it. This is important so I don't break it in a
53
+ future version unintentionally.
54
+ * Commit, do not mess with rakefile, version, or history.
55
+ (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)
56
+ * Send me a pull request. Bonus points for topic branches.
57
+
58
+ ## Copyright
59
+
60
+ Copyright (c) 2013 The Garage, Inc. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new('spec')
5
+ task :default => :spec
data/bin/git-cleanup ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['cleanup'] + ARGV)
data/bin/git-integrate ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['integrate'] + ARGV)
data/bin/git-nuke ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['nuke'] + ARGV)
data/bin/git-release ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['release'] + ARGV)
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['reviewrequest'] + ARGV)
data/bin/git-share ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['share'] + ARGV)
data/bin/git-start ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['start'] + ARGV)
data/bin/git-track ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['track'] + ARGV)
data/bin/git-update ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', 'cli.rb')
4
+ Thegarage::Gitx::CLI.start (['update'] + ARGV)
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!