git-whistles 0.4.4 → 0.5.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.
@@ -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: