git-whistles 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Fast-forward all local branches to their remote counterparts.
4
+ #
5
+ # Inspired from http://stackoverflow.com/questions/5147537/how-do-i-fast-forward-other-tracking-branches-in-git
6
+ #
7
+ require 'ostruct'
8
+ require 'optparse'
9
+ require 'logger'
10
+ require 'term/ansicolor'
11
+
12
+ ############################################################################
13
+
14
+
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
+
44
+
45
+ def initialize
46
+ @options = OpenStruct.new(DEFAULTS)
47
+ @local_branches = {}
48
+ @remote_branches = {}
49
+ @current_branch = nil
50
+ @log = Logger.new($stderr)
51
+ end
52
+
53
+
54
+ def main(args)
55
+ parse_args!(args)
56
+ log.level = options.loglevel
57
+
58
+ if options.fetch
59
+ run "git fetch"
60
+ end
61
+
62
+ load_refs
63
+ local_branches.each_pair do |branch_name, _|
64
+ process_branch(branch_name)
65
+ end
66
+ end
67
+
68
+
69
+
70
+ private
71
+
72
+ attr :options
73
+ attr :local_branches
74
+ attr :remote_branches
75
+ attr :current_branch
76
+ attr :log
77
+
78
+
79
+ def process_branch(branch_name)
80
+ unless remote_branches[branch_name]
81
+ log.debug("skipping #{branch_name} (not on remote)")
82
+ return
83
+ end
84
+
85
+ old_head = local_branches[branch_name]
86
+ new_head = remote_branches[branch_name]
87
+ if old_head == new_head
88
+ log.debug("skipping #{branch_name} (up-to-date)")
89
+ return
90
+ end
91
+
92
+ if branch_name == current_branch
93
+ if run('git status --porcelain').strip.empty?
94
+ flag = (options.loglevel > Logger::INFO) ? '-q' : ''
95
+ run("git merge --ff-only #{flag} #{new_head}") unless options.dry_run
96
+ else
97
+ log.warn('not merging current branch as it has local changes')
98
+ return
99
+ end
100
+ else
101
+ merge_base = run("git merge-base #{old_head} #{new_head}").strip
102
+ if merge_base != old_head
103
+ log.warn("cannot fast-forward #{branch_name}")
104
+ return
105
+ end
106
+ run("git update-ref refs/heads/#{branch_name} #{new_head} #{old_head}")
107
+ end
108
+
109
+ log.info("#{branch_name}: #{short_sha old_head} -> #{short_sha new_head}")
110
+ end
111
+
112
+
113
+ def option_parser
114
+ @option_parser ||= OptionParser.new do |op|
115
+ op.banner = "Usage: git ff-all-branches [options]"
116
+
117
+ op.on("-v", "--verbose", "Run verbosely (add twice for more verbosity)") do |v|
118
+ options.loglevel -= 1
119
+ end
120
+
121
+ op.on("-q", "--quiet", "Run silently") do |v|
122
+ options.loglevel = Logger::UNKNOWN
123
+ end
124
+
125
+ op.on("-r", "--remote REMOTE", "Set the remote [origin]") do |remote|
126
+ options.remote = remote
127
+ end
128
+
129
+ op.on("-f", "--fetch", "Run git fetch beforehand") do |v|
130
+ options.fetch = v
131
+ end
132
+
133
+ op.on("-p", "--dry-run", "Don't actually do anything") do |v|
134
+ options.dry_run = v
135
+ end
136
+ end
137
+ end
138
+
139
+
140
+ def parse_args!(args)
141
+ begin
142
+ option_parser.parse!(args)
143
+ rescue OptionParser::InvalidOption => error
144
+ die error.message, :usage => true
145
+ end
146
+
147
+ if ARGV.any?
148
+ die "this command does not take any argument besides flags", :usage => true
149
+ end
150
+ end
151
+
152
+
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
+ def load_refs
171
+ @current_branch = run("git symbolic-ref HEAD").strip.gsub(%r(^refs/heads/), '')
172
+
173
+ run('git show-ref').strip.split(/\n+/).each do |line|
174
+ line =~ %r(([a-f0-9]{40}) refs/(remotes/#{options.remote}|heads)/(.*)) or next
175
+ if $2 == 'heads'
176
+ @local_branches[$3] = $1
177
+ else
178
+ @remote_branches[$3] = $1
179
+ end
180
+ end
181
+ end
182
+
183
+ def short_sha(sha)
184
+ sha[0..7]
185
+ end
186
+ end
187
+
188
+ ############################################################################
189
+
190
+ App.new.main(ARGV)
data/git-whistles.gemspec CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.add_development_dependency "bundler", ">= 1.0.0"
18
18
  gem.add_development_dependency "rake"
19
19
  gem.add_development_dependency "pry"
20
+ gem.add_development_dependency "pry-nav"
20
21
 
21
22
  gem.add_dependency "pivotal-tracker", "~> 0.5.6"
22
23
  gem.add_dependency "term-ansicolor"
@@ -2,7 +2,7 @@ require 'pathname'
2
2
 
3
3
  module Git
4
4
  module Whistles
5
- VERSION = "0.4.4"
5
+ VERSION = "0.5.0"
6
6
  GEMDIR = Pathname.new(__FILE__).parent.parent.parent
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-whistles
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-11 00:00:00.000000000 Z
12
+ date: 2012-10-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -59,6 +59,22 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: pry-nav
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
62
78
  - !ruby/object:Gem::Dependency
63
79
  name: pivotal-tracker
64
80
  requirement: !ruby/object:Gem::Requirement
@@ -96,6 +112,7 @@ email:
96
112
  - julien.letessier@gmail.com
97
113
  executables:
98
114
  - git-chop
115
+ - git-ff-all-branches
99
116
  - git-list-branches
100
117
  - git-merge-po
101
118
  - git-outstanding-features
@@ -110,6 +127,7 @@ files:
110
127
  - README.md
111
128
  - Rakefile
112
129
  - bin/git-chop
130
+ - bin/git-ff-all-branches
113
131
  - bin/git-list-branches
114
132
  - bin/git-merge-po
115
133
  - bin/git-outstanding-features
@@ -138,7 +156,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
138
156
  version: '0'
139
157
  segments:
140
158
  - 0
141
- hash: 2003402627353035389
159
+ hash: 1186482442266727224
142
160
  required_rubygems_version: !ruby/object:Gem::Requirement
143
161
  none: false
144
162
  requirements: