Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mpalmer committed May 6, 2014
0 parents commit 8504f9b
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pkg/
.bundle/
Gemfile.lock
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org/'

gemspec
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
This very simple gem provides a `git release` command, which will
automatically fill out any and all "release tags" into fully-blown "Github
Releases", complete with release notes, a heading, and all the other good
things in life.

Using this gem, you can turn the following tag annotation:

First Release

It is with much fanfare and blowing of horns that I bequeath the
awesomeness of `git release` upon the world.

Features in this release include:

* Ability to create a release from a tag annotation or commit message;
* Automatically generates an OAuth token if needed;
* Feeds your cat while you're hacking(*)

You should install it now! `gem install github-release`

Into [this](https://github.com/mpalmer/github-release/releases/tag/v0.1.0)
simply by running

git release


# Installation

Simply install the gem:

gem install github-release


# Usage

Using `git release` is very simple. Just make sure that your `origin`
remote points to your Github repo, and then run `git release`. All tags
that look like a "version tag" (see "Configuration", below) will be created
as Github releases (if they don't already exist) and the message from the
tag will be used as the release notes.

The format of the release notes is quite straightforward -- the first line
of the message associated with the commit will be used as the "name" of the
release, with the rest of the message used as the "body" of the release.
The body will be interpreted as Github-flavoured markdown, so if you'd like
to get fancy, go for your life.

The message associated with the "release tag" is either the tag's annotation
message (if it is an annotated tag) or else the commit log of the commit on
which the tag is placed. I *strongly* recommend annotated tags (but then
again, [I'm biased...](http://theshed.hezmatt.org/git-version-bump))

The first time you use `git release`, it will ask you for your Github
username and password. This is used to request an OAuth token to talk to
the Github API, which is then stored in your global git config. Hence you
*shouldn't* be asked for your credentials every time you use `git release`.
If you need to use multiple github accounts for different repos, you can
override the `release.api-token` config parameter in your repo configuration
(but you'll have to get your own OAuth token).


# Configuration

There are a few things you can configure to make `git release` work slightly
differently. None of them should be required for normal, sane use.

* `release.remote` (default `origin`) -- The name of the remote which is
used to determine what github repository to send release notes to.

* `release.api-token` (default is runtime generated) -- The OAuth token
to use to authenticate access to the Github API. When you first run `git
release`, you'll be prompted for a username and password to use to
generate an initial token; if you need to override it on a per-repo
basis, this is the key you'll use.

* `release.tag-regex` (default `v\d+\.\d+(\.\d+)?$`) -- The regular
expression to filter which tags denote releases, as opposed to other tags
you might have decided to make. Only tags which match this regular
expression will be pushed up by `git release`, and only those tags will
be marked as releases.
20 changes: 20 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'rubygems'
require 'bundler'

begin
Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
$stderr.puts e.message
$stderr.puts "Run `bundle install` to install missing gems"
exit e.status_code
end

Bundler::GemHelper.install_tasks

require 'rdoc/task'

Rake::RDocTask.new do |rd|
rd.main = "README.md"
rd.title = 'github-release'
rd.rdoc_files.include("README.md", "lib/**/*.rb")
end
5 changes: 5 additions & 0 deletions bin/git-release
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env ruby

require 'github-release'

GithubRelease.new.run
25 changes: 25 additions & 0 deletions github-release.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'git-version-bump'

Gem::Specification.new do |s|
s.name = "github-release"

s.version = GVB.version
s.date = GVB.date

s.platform = Gem::Platform::RUBY

s.homepage = "http://theshed.hezmatt.org/github-release"
s.summary = "Upload tag annotations to github"
s.authors = ["Matt Palmer"]

s.extra_rdoc_files = ["README.md"]
s.files = `git ls-files`.split("\n")
s.executables = ["git-release"]

s.add_dependency 'octokit'
s.add_dependency 'git-version-bump'

s.add_development_dependency 'rake'
s.add_development_dependency 'bundler'
s.add_development_dependency 'rdoc'
end
135 changes: 135 additions & 0 deletions lib/github-release.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
require 'octokit'
require 'io/console'

class GithubRelease
def run
new_releases = tagged_releases.select { |r| !github_releases.include?(r) }

if new_releases.empty?
puts "No new release tags to push."
end

new_releases.each { |t| create_release(t) }

puts "All done!"
end

private
def api
@api ||= Octokit::Client.new(:access_token => token)
end

def token
@token ||= begin
# We cannot use the 'defaults' functionality of git_config here,
# because get_new_token would be evaluated before git_config ran
git_config("release.api-token") || get_new_token
end

log_val(@token)
end

def get_new_token
puts "Requesting a new OAuth token from Github..."
print "Github username: "
user = $stdin.gets.chomp
print "Github password: "
pass = $stdin.noecho(&:gets).chomp
puts

api = Octokit::Client.new(:login => user, :password => pass)
begin
res = api.create_authorization(:scopes => [:repo], :note => "git release")
rescue Octokit::Unauthorized
puts "Username or password incorrect. Please try again."
return get_new_token
end

token = res[:token]

system("git config --global release.api-token '#{token}'")

log_val(token)
end

def tag_regex
@tag_regex ||= `git config --get release.tag-regex`.strip
@tag_regex = /^v\d+\.\d+(\.\d+)?$/ if @tag_regex.empty?
log_val(@tag_regex)
end

def tagged_releases
@tagged_releases ||= `git tag`.split("\n").map(&:strip).grep tag_regex
log_val(@tagged_releases)
end

def repo_name
@repo_name ||= begin
case repo_url
when %r{^https://github.com/([^/]+/[^/]+)}
return $1.gsub(/\.git$/, '')
when %r{^(?:git@)?github\.com:([^/]+/[^/]+)}
return $1.gsub(/\.git$/, '')
else
raise RuntimeError,
"I cannot recognise the format of the push URL for remote #{remote_name} (#{repo_url})"
end
end
log_val(@repo_name)
end

def repo_url
@repo_url ||= begin
git_config("remote.#{remote_name}.pushurl") || git_config("remote.#{remote_name}.url")
end
log_val(@repo_url)
end

def remote_name
@remote_name ||= git_config("release.remote", "origin")
log_val(@remote_name)
end

def github_releases
@github_releases ||= api.repo(repo_name).rels[:releases].get.data.map(&:tag_name)
log_val(@github_releases)
end

def git_config(item, default = nil)
@config_cache ||= {}

@config_cache[item] ||= begin
v = `git config #{item}`.strip
v.empty? ? default : v
end

log_val(@config_cache[item], item)
end

def create_release(tag)
print "Creating a release for #{tag}..."
system("git push #{remote_name} tag #{tag} >/dev/null")

msg = `git tag -l -n1000 '#{tag}'`

# Ye ghods is is a horrific format to parse
name, body = msg.split("\n", 2)
name = name.gsub(/^#{tag}/, '').strip
body = body.split("\n").map { |l| l.sub(/^ /, '') }.join("\n")

api.create_release(repo_name, tag, :name => name, :body => body)

puts " done!"
end

def log_val(v, note = nil)
return v unless $DEBUG

calling_func = caller[0].split("`")[-1].sub(/'$/, '')

print "#{note}: " if note
puts "#{calling_func} => #{v.inspect}"

v
end
end

0 comments on commit 8504f9b

Please sign in to comment.