git-bump 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/git-bump +7 -0
  3. data/lib/git_bump.rb +257 -0
  4. metadata +90 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b5a25a12af0749f43938f0f73f72058606826ccb
4
+ data.tar.gz: 3e33b5cc7c489af869f1f9948658fd8678252cc3
5
+ SHA512:
6
+ metadata.gz: 5d6c775156eb71ab824bd7a2185359cfaf4d84555a921fa7d1e21b1182b5a281bea751e465925d3690573a854bf45222de30ca6f22db1e7018cdeb5d9343e906
7
+ data.tar.gz: 0e1968dd66dfab8357336e102977e3d3f697d4915f3bb38d207098b0c684e9135d254ad6054cbd42b1d6156540c2b648c599ba53f1c6c0a44b07d293c8868b47
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path('../../lib', __FILE__))
4
+
5
+ require 'git_bump'
6
+
7
+ GitBump.start
@@ -0,0 +1,257 @@
1
+ # encoding: utf-8
2
+
3
+ require 'thor'
4
+
5
+ class GitBump < Thor
6
+
7
+ INITIAL = <<-EOS
8
+ Looks like this is your first release. Please add the version number to the
9
+ work tree (e.g., in your Makefile), stage your changes, and run git bump again.
10
+
11
+ If this isn't your first release, tag your most recent prior release so that
12
+ git bump can find it:
13
+
14
+ git tag -s v1.2.3 8675309
15
+ EOS
16
+
17
+ def self.start
18
+ ARGV.unshift('release') if ARGV.first =~ /^v?\d/ || %w(major minor point).include?(ARGV.first)
19
+ super
20
+ end
21
+
22
+ class Release
23
+ attr_reader :tag, :sha1, :name, :version
24
+
25
+ def initialize(tag, sha1, name, version)
26
+ @tag, @sha1, @name, @version = tag, sha1, name, Version.new(version)
27
+ end
28
+
29
+ def body
30
+ @body ||= %x{git log -1 --pretty=format:%b #{sha1}}
31
+ end
32
+
33
+ def format
34
+ body[/(?:\n |.)*/].sub(/\A([-* ]*)(.*?)(\.?)\z/m, '\1%s\3') unless body.empty?
35
+ end
36
+
37
+ def inverse_diff(context = 1)
38
+ unless defined?(@inverse_diff)
39
+ @inverse_diff =
40
+ if !%x{git rev-parse --verify -q #{sha1}^}.empty?
41
+ %x{git diff -U#{context} #{sha1}..#{sha1}^}
42
+ end
43
+ end
44
+ @inverse_diff
45
+ end
46
+
47
+ end
48
+
49
+ class Version
50
+ def initialize(string)
51
+ @components = string.split('.')
52
+ end
53
+
54
+ def to_s
55
+ @components.join('.')
56
+ end
57
+
58
+ def to_a
59
+ @components.dup
60
+ end
61
+ end
62
+
63
+ no_tasks do
64
+ def releases
65
+ @releases ||=
66
+ begin
67
+ out = %x{git for-each-ref "refs/tags/v[0-9]*" --sort="*committerdate" --format="%(refname:short) %(*objectname) %(subject)"}
68
+ exit 1 unless $?.success?
69
+ out.scan(/^(\S+) (\w+) (.*) (\d\S*)\s*$/).map do |args|
70
+ Release.new(*args)
71
+ end
72
+ end
73
+ end
74
+
75
+ def latest
76
+ @latest ||= releases.reverse.detect do |release|
77
+ %x{git merge-base #{release.sha1} HEAD}.chomp == release.sha1
78
+ end
79
+ end
80
+
81
+ def increment(pos, components)
82
+ components[pos].sub!(/^(\d+).*/, '\1')
83
+ components[pos].succ!
84
+ (components.size-1).downto(pos+1) do |i|
85
+ if components[i] =~ /^\d/
86
+ components[i] = '0'
87
+ else
88
+ components.delete_at(i)
89
+ end
90
+ end
91
+ end
92
+
93
+ def generate_version(request)
94
+ if request =~ /^v?(\d.*)/
95
+ $1
96
+ elsif latest
97
+ components = latest.version.to_s.split('.')
98
+ case request
99
+ when 'major' then increment(0, components)
100
+ when 'minor' then increment(1, components)
101
+ when 'point' then increment(2, components)
102
+ when nil then components.last.succ!
103
+ else
104
+ abort "Unrecognized version increment #{request}."
105
+ end
106
+ components.join('.')
107
+ else
108
+ abort "Appears to be initial release. Version number required."
109
+ end
110
+ end
111
+
112
+ def name
113
+ if latest
114
+ latest.name
115
+ else
116
+ File.basename(Dir.getwd)
117
+ end
118
+ end
119
+
120
+ def patch(version, force = false)
121
+ diff = latest.inverse_diff(force ? 0 : 1)
122
+ return unless diff
123
+ deletion = /^-(.*)(#{Regexp.escape(latest.version.to_s)})(.*)\n/
124
+ patch = diff.gsub(/#{deletion}\+\1(.*)\3\n/) do
125
+ "-#$1#$2#$3\n+#$1#{version}#$3\n"
126
+ end.gsub(/^(@@ -\d+,\d+ \+\d+,)(\d+) @@\n( .*\n)?#{deletion}(?![+-])/) do
127
+ "#$1#{$2.succ} @@\n#$3-#$4#$5#$6\n+#$4#{version}#$6\n "
128
+ end.scan(/^[d@].*\n(?:[^d@].*\n)+/).reject do |v|
129
+ v[0] == ?@ && !v.include?(version)
130
+ end.join.gsub(/^diff.*\n([^+-].*\n)*---.*\n\+\+\+.*\n(\Z|diff)/, '\1')
131
+ patch unless patch =~ /\Aindex.*\Z/
132
+ end
133
+
134
+ def logs
135
+ if (releases.size < 2 || latest.format) && !@logs
136
+ @logs = %x{git log --no-merges --reverse --pretty=format:"#{latest.format || '* %s.'}" #{latest.sha1}..}
137
+ abort unless $?.success?
138
+ end
139
+ @logs
140
+ end
141
+
142
+ def tag!(name)
143
+ subject = %x{git log -1 --pretty=format:%s}.chomp
144
+ system!('git', 'tag', '-f', '-s', name, '-m', subject)
145
+ puts <<-EOS
146
+ Successfully created #{name}. If you made a mistake, use `git bump redo` to
147
+ try again. Once you are satisfied with the result, run
148
+
149
+ git push origin master #{name}
150
+ EOS
151
+ end
152
+
153
+ def system!(*args)
154
+ system(*args)
155
+ abort "Error running Git." unless $?.success?
156
+ end
157
+ end
158
+
159
+ def self.basename
160
+ 'git bump'
161
+ end
162
+
163
+ default_task 'release'
164
+ desc '[version]', 'Create and tag a release for the given version'
165
+ method_options %w(force -f) => :boolean
166
+ def release(request=nil)
167
+ version = generate_version(request)
168
+ unless %x{git rev-parse --verify -q v#{version}}.empty? || options[:force]
169
+ abort "Tag already exists. If it hasn't been pushed yet, use --force to override."
170
+ end
171
+ initial_commit = %x{git rev-parse --verify -q HEAD}.empty?
172
+ if !initial_commit && %x{git diff HEAD}.empty?
173
+ abort INITIAL unless latest
174
+ failure = "Couldn't patch. Update the version number in the work tree and try again."
175
+ abort failure unless patch = patch(version, options[:force])
176
+ IO.popen(['git', 'apply', '--unidiff-zero', '--index'], 'w') do |o|
177
+ o.write patch
178
+ end
179
+ abort failure unless $?.success?
180
+ hard = true
181
+ elsif %x{git diff --cached}.empty?
182
+ # TODO: what happens on initial with some unstaged changes?
183
+ abort "Discard or stage your changes."
184
+ end
185
+ require 'tempfile'
186
+ Tempfile.open('git-commit') do |f|
187
+ f.puts [name, version].join(' ')
188
+ f.puts
189
+ f.write logs if latest
190
+ f.flush
191
+ system('git', 'commit', '--file', f.path, '--edit', * initial_commit ? [] : ['--verbose'])
192
+ unless $?.success?
193
+ system('git', 'reset', '-q', '--hard', 'HEAD') if hard
194
+ abort
195
+ end
196
+ end
197
+ tag!("v#{version}")
198
+ end
199
+
200
+ desc 'redo', 'amend the previous release and retag'
201
+ method_options %w(force -f) => :boolean
202
+ def redo
203
+ unless %x{git diff}.empty?
204
+ abort "Discard or stage your changes."
205
+ end
206
+ unless latest.sha1 == %x{git rev-parse HEAD}.chomp
207
+ abort "Can only amend the top-most commit."
208
+ end
209
+ system!('git', 'commit', '--amend', '--verbose', '--reset-author')
210
+ tag!(latest.tag)
211
+ end
212
+
213
+ desc 'log', 'Show the git log since the last release'
214
+ def log(*args)
215
+ if latest
216
+ exec('git', 'log', "#{latest.sha1}..", *args)
217
+ else
218
+ exec('git', 'log', *args)
219
+ end
220
+ end
221
+
222
+ desc 'show [version]', 'Show the most recent or given release'
223
+ method_options :version_only => :boolean
224
+ def show(version = latest ? latest.version.to_s : nil)
225
+ release = releases.detect do |r|
226
+ r.version.to_s == version || r.tag == version
227
+ end
228
+ if release
229
+ if options[:version_only]
230
+ puts release.version
231
+ else
232
+ exec('git', 'log', '-1', '--pretty=format:%B', release.sha1)
233
+ end
234
+ else
235
+ exit 1
236
+ end
237
+ end
238
+
239
+ desc 'next', 'Show the version number that would be released'
240
+ def next(specifier = nil)
241
+ puts generate_version(specifier)
242
+ end
243
+
244
+ def self.help(shell, *)
245
+ super
246
+ shell.say <<-EOS
247
+ With no arguments, git bump defaults to creating a release with the least
248
+ significant component of the version number incremented. For example,
249
+ 1.2.3-rc4 becomes 1.2.3-rc5, while 6.7 becomes 6.8. To override, provide a
250
+ version number argument, or one of the following keywords:
251
+
252
+ major: bump the most significant component
253
+ minor: bump the second most significant component
254
+ point: bump the third most significant component
255
+ EOS
256
+ end
257
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-bump
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tim Pope
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Git based release management
56
+ email:
57
+ - code@tpope.net
58
+ executables:
59
+ - git-bump
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - bin/git-bump
64
+ - lib/git_bump.rb
65
+ homepage: https://tpo.pe/git-bump
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.0.3
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Create Git release commits and tags with changelogs
89
+ test_files: []
90
+ has_rdoc: