├── README.md └── release.rake /README.md: -------------------------------------------------------------------------------- 1 | # Release.rake 2 | 3 | [![Latestver](https://lv.binarybabel.org/catalog-api/release.rake/latest.svg?label=latest+version)](https://lv.binarybabel.org/catalog/release.rake/latest) [![Join the chat at https://gitter.im/binarybabel/release.rake](https://badges.gitter.im/binarybabel/release.rake.svg)](https://gitter.im/binarybabel/release.rake?utm_source=badge&utm_medium=badge&utm_content=badge) 4 | 5 | Single-file customizable **release + changelog** tool for Ruby projects. 6 | Designed with Rails in mind, but easily tweaked for any type of project. 7 | 8 | **Example Changelog** — Commits since last Git tag are used to help amend your changelog. 9 | Messages are parsed for common words to generate ASCII or Emoji line indicators. 10 | 11 | ``` 12 | ### 1.1 (2017-01-01) 13 | 14 | * __`NEW`__ This commit added something 15 | * __`~~~`__ This commit changed something 16 | * __`!!!`__ Fixed a bug here 17 | * __`-!-`__ Removed a feature 18 | ``` 19 | 20 | ## Installation 21 | 22 | ``` 23 | $ cd lib/tasks 24 | $ curl -OL https://git.io/release.rake 25 | ``` 26 | 27 | Review the "Configuration" section of the new rake file. 28 | * __`version_file`__ points to the hard-coded location of your project's version (sample below) 29 | * __`version_lambda`__ provides a method for retrieving the current version at runtime 30 | 31 | **Need a version file?** — __`config/version.rb`__ 32 | ```ruby 33 | module MyApp 34 | VERSION = '1.0.0' 35 | end 36 | ``` 37 | 38 | **Test the installation** 39 | 40 | ``` 41 | $ rake release:current 42 | 1.0.0 43 | ``` 44 | 45 | ## Usage 46 | 47 | __`release.rake`__ is designed as a single-file, and directly installed in your project, to encourage users read, understand, and modify it to meet their needs. Most of the commands are summarized below, but for best results consult the source file. 48 | 49 | ### Shortcut Commands 50 | 51 | The shortcut task section is a great place to start tweaking your install. For example the `:next` task prereqs determine which version segment should be bumped, or adjust the commit message in the `:prep` task. 52 | 53 | ``` 54 | $ rake release:next 55 | ``` 56 | 57 | * Bumps minor, generates changelog, prints further instructions. 58 | * Does NOT automatically commit changes. 59 | 60 | ``` 61 | $ rake release:prep 62 | ``` 63 | 64 | * Bumps minor with "pre" suffix. 65 | * Commits version file change automatically. 66 | 67 | ### Main Commands 68 | 69 | ``` 70 | $ rake release:changelog 71 | ``` 72 | 73 | * Generate changelog from Git events since last tag. 74 | * Changes are added to the top of the configured `changelog_file` 75 | * Above any previous release, but below other information you add to the top of the file. 76 | 77 | ``` 78 | $ rake release:bump # patch, full-release 79 | # 1.0.0 -> 1.0.1 80 | 81 | $ rake release:bump[2, pre] # minor, "pre"-release 82 | # 1.0.0 -> 1.1.pre 83 | ``` 84 | 85 | * Updates configured `version_file`. 86 | * Does NOT automatically commit changes. 87 | 88 | ``` 89 | $ rake release:commit # commit + tag 90 | ``` 91 | 92 | * Commits changed version/changelog files... 93 | * Tags release, uses configured `commit_message` 94 | * Will be signed if `user.signingkey` is set on local repo 95 | * Tag or message can be customized as command parameters. 96 | 97 | ## Integrating with Versioneer 98 | 99 | [Versioneer](https://github.com/binarybabel/gem-versioneer) is a gem for dynamically generating prerelease versions based on commits since last tag, thus avoiding any need to "prep" for future releases. 100 | 101 | Adjust your project's version file to manage the hardcoded release in a separate constant. 102 | 103 | __`config/version.rb`__ 104 | ```ruby 105 | require 'versioneer' 106 | 107 | module MyApp 108 | RELEASE = '1.0' 109 | begin 110 | VERSION = Versioneer::Config.new(File.expand_path('../../', __FILE__)).to_s 111 | rescue Versioneer::InvalidRepoError 112 | VERSION = self::RELEASE 113 | end 114 | end 115 | ``` 116 | 117 | __`lib/tasks/release.rake`__ 118 | ```ruby 119 | version_lambda = lambda { Object.const_get(Rails.application.class.name.sub(/::.+$/, ''))::RELEASE } 120 | ``` 121 | -------------------------------------------------------------------------------- /release.rake: -------------------------------------------------------------------------------- 1 | namespace :release do 2 | 3 | ### Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | # File updated by [release:bump] 6 | version_file = 'config/version.rb' 7 | # How to determine current project version programmatically 8 | version_lambda = lambda { Object.const_get(Rails.application.class.name.sub(/::.+$/, ''))::VERSION } 9 | # Default commit message (%s replaced by version) 10 | commit_message = 'Release %s' 11 | # Changelog file to read/write 12 | changelog_file = 'CHANGELOG.md' 13 | # List of shell commands to try to determine previously released commit hash... 14 | # - the first non-empty command result is accepted 15 | # - changelog will be built from this reference to current HEAD 16 | changelog_ref_sources = ['git describe --abbrev=0 --tags', 'git rev-list HEAD | tail -n 1'] 17 | # Filter and format commit subjects for the changelog 18 | changelog_line_filter = lambda do |line| 19 | case line 20 | when /release|merge/i 21 | nil 22 | when /bug|fix/i 23 | '* __`!!!`__ ' + line 24 | when /add|support|ability/i 25 | '* __`NEW`__ ' + line 26 | when /remove|deprecate/i 27 | '* __`-!-`__ ' + line 28 | else 29 | '* __`~~~`__ ' + line 30 | end 31 | end 32 | 33 | ### Shortcuts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 | 35 | task :next => [:minor, :changelog] do 36 | puts "** Next steps to release #{version_lambda.call}" 37 | puts '1. %-35s # %s' % ["nano #{changelog_file}", 'Review'] 38 | puts '2. %-35s # %s' % ['rake release:commit', 'Commit + Tag'] 39 | puts '3. %-35s # %s' % ['git push --tags origin master', 'Publish'] 40 | end 41 | task :prep do 42 | Rake::Task['release:bump'].invoke(2, 'pre') 43 | Rake::Task['release:commit'].invoke(false, 'Prepare for next release') 44 | end 45 | 46 | task(:major) { Rake::Task['release:bump'].invoke(1) } 47 | task(:minor) { Rake::Task['release:bump'].invoke(2) } 48 | task(:patch) { Rake::Task['release:bump'].invoke(3) } 49 | 50 | ### Tasks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 51 | 52 | desc 'Bump release to next version.' 53 | task :bump, [:version_length, :prerelease_suffix] => :environment do |t, args| 54 | args.with_defaults(:version_length => 3, :prerelease_suffix => nil) 55 | raise ArgumentError, 'Invalid bump version length.' if args[:version_length].to_s.empty? 56 | 57 | version = Gem::Version.new(version_lambda.call) 58 | length = args[:version_length].to_i 59 | (suffix = args[:prerelease_suffix]) and suffix << '1' 60 | 61 | if version.prerelease? 62 | if suffix.to_s.chop == version.segments[-2] 63 | suffix = suffix.chop + (version.segments[-1].to_i + 1).to_s 64 | end 65 | next_version = (version.release.segments + [suffix]).compact.join('.') 66 | else 67 | segments = version.release.segments.slice(0, length) 68 | while segments.size < length + 1 # Version.bump strips last segment 69 | segments << 0 70 | end 71 | next_version = [Gem::Version.new(segments.join('.')).bump.to_s, suffix].compact.join('.') 72 | end 73 | 74 | puts "** Version bump #{version} -> #{next_version}" 75 | system "sed -i '' 's/#{version}/#{next_version}/g' #{version_file}" 76 | system "git diff -U0 #{version_file}" 77 | Kernel.silence_warnings do 78 | load Rails.root.join(*version_file.split('/')) 79 | end 80 | end 81 | 82 | desc 'Generate changelog for current version (from last tagged release)' 83 | task :changelog => :environment do 84 | changelog_ref = '' 85 | changelog_ref_sources.each do |cmd| 86 | changelog_ref = %x{#{cmd} 2> /dev/null}.chomp 87 | break unless changelog_ref.empty? 88 | end 89 | raise 'Failed to determine base git reference for changelog.' if changelog_ref.empty? 90 | 91 | header = '' 92 | footer = '' 93 | if File.file?(changelog_file) 94 | File.open(changelog_file, 'r') do |f| 95 | in_header = true 96 | f.each_line do |line| 97 | if !in_header or line.match(/^##/) 98 | in_header = false 99 | footer << line 100 | else 101 | header << line 102 | end 103 | end 104 | end 105 | end 106 | 107 | current_version = version_lambda.call 108 | content = "### #{current_version} (#{DateTime.now.strftime('%Y-%m-%d')})\n\n" 109 | gitlog = %x{git log #{changelog_ref}...HEAD --pretty=format:'%s' --reverse} 110 | raise gitlog.to_s if $?.exitstatus > 0 111 | gitlog.chomp.split("\n").each do |line| 112 | line = changelog_line_filter.call(line.chomp) 113 | content << line + "\n" if line 114 | end 115 | content << "\n\n" 116 | 117 | puts "** Writing to #{changelog_file}" 118 | puts 119 | puts content 120 | File.write(changelog_file, header + content + footer) 121 | end 122 | 123 | desc 'Commit/tag current release' 124 | task :commit, [:tag, :message] do |t, args| 125 | puts '** Committing release' 126 | current_version = version_lambda.call 127 | files = [version_file, changelog_file].join(' ') 128 | args.with_defaults(:tag => true, :message => commit_message % [current_version]) 129 | system "git reset > /dev/null && git add #{files}" 130 | system "git commit -m '#{args[:message]}'" 131 | if args[:tag] === true or args[:tag] == 'true' 132 | Rake::Task['release:tag'].invoke 133 | end 134 | end 135 | 136 | desc 'Tag current release' 137 | task :tag do 138 | puts '** Tagging release' 139 | current_version = version_lambda.call 140 | tag_mode = `[ -n "$(git config --local --get user.signingkey)" ] && echo "-s" || echo "-a"`.chomp 141 | system "git tag #{tag_mode} v#{current_version} -m ' #{commit_message % [current_version]}'" 142 | system 'git tag -n | tail -n 1' 143 | end 144 | 145 | task :current do 146 | puts version_lambda.call 147 | end 148 | 149 | ### Rake Template Author + Updates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 150 | # 151 | # 0101010 152 | # 0010011 153 | # _ _ 110101 154 | # | | | | 0011 155 | # _ __ ___| | ___ __ _ ___ ___ _ __ __ _| | _____ 0100010 156 | # | '__/ _ \ |/ _ \/ _` / __|/ _ \ | '__/ _` | |/ / _ \ 1010 0010101000001 157 | # | | | __/ | __/ (_| \__ \ __/ _ | | | (_| | < __/ 010101110100111101010010 158 | # |_| \___|_|\___|\__,_|___/\___| (_) |_| \__,_|_|\_\___| 01 0011000100 159 | # 2017.01.01 from 160 | # 0100 161 | # 01001001 binarybabel.org 162 | # 0100111001 000001010001110 163 | # 101 0010010000010100100101 164 | # 00111 0010011110100011001010 165 | # 0110 10000010100111001000100 166 | # 167 | # github.com/binarybabel 168 | # 169 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 170 | 171 | end 172 | --------------------------------------------------------------------------------