werk 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +16 -0
- data/Rakefile +7 -0
- data/bin/werk +5 -0
- data/lib/werk.rb +1 -0
- data/lib/werk/base.rb +63 -0
- data/lib/werk/cli.rb +43 -0
- data/lib/werk/commands.rb +97 -0
- data/lib/werk/git.rb +36 -0
- data/lib/werk/trac.rb +49 -0
- data/templates/ticket_show.erb +13 -0
- metadata +90 -0
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
I Go To Werk
|
2
|
+
============
|
3
|
+
|
4
|
+
Werk is a wrapper that combines the ability to query and manipulate tickets in
|
5
|
+
Trac with the ability to create and manage repository branches in git. Behaviour is defined largely via .werkrc files, which are scanned in order up the filesystem from your current location. This makes it possible to define workflow via .werkrc files located in-project, with sensible per-user definitions for defaults.
|
6
|
+
|
7
|
+
TODO
|
8
|
+
====
|
9
|
+
|
10
|
+
- start using trac actions
|
11
|
+
- come up with a better way of baking branch / ticket names
|
12
|
+
- start using optparse
|
13
|
+
- add more workflows to wrap add
|
14
|
+
- add documentation for the various commands (possibly generate this from doc?)
|
15
|
+
- move definitions out to .werkrc files
|
16
|
+
- factor trac and git out from global namespace
|
data/Rakefile
ADDED
data/bin/werk
ADDED
data/lib/werk.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'werk/base'
|
data/lib/werk/base.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'werk/trac'
|
5
|
+
require 'werk/git'
|
6
|
+
require 'werk/commands'
|
7
|
+
|
8
|
+
module Werk
|
9
|
+
class Base
|
10
|
+
include Werk::Trac
|
11
|
+
include Werk::Git
|
12
|
+
include Werk::Commands
|
13
|
+
|
14
|
+
def initialize(runtime_options = {})
|
15
|
+
if runtime_options[:rcfile] && File.file?(runtime_options[:rcfile])
|
16
|
+
# If the user explicitly specified an rcfile, just use that instead
|
17
|
+
options = YAML.load_file(File.expand_path(runtime_options[:rcfile]))
|
18
|
+
else
|
19
|
+
# Go all the way to the root looking for .werkrcs, then apply them in
|
20
|
+
# reverse order to ensure local changes trump more global ones
|
21
|
+
current = Dir.pwd
|
22
|
+
options = {}
|
23
|
+
|
24
|
+
loop do
|
25
|
+
%w( .werkrc ).each do |file|
|
26
|
+
if File.file?(file)
|
27
|
+
# Do this backwards so we give more nested files precendece
|
28
|
+
puts "Loading options from #{File.join(Dir.pwd,file)}" if runtime_options[:verbose]
|
29
|
+
options = YAML.load_file(file).merge(options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
pwd = Dir.pwd
|
33
|
+
Dir.chdir("..")
|
34
|
+
break if pwd == Dir.pwd # if changing the directory made no difference, then we're at the top
|
35
|
+
end
|
36
|
+
Dir.chdir(current)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Now read in any explicitly specified arguments
|
40
|
+
options.merge!(runtime_options)
|
41
|
+
options.each do |option, value|
|
42
|
+
self.instance_variable_set "@#{option.to_s}", value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def requires_variables(vars)
|
47
|
+
vars.each do |option|
|
48
|
+
if !self.instance_variable_defined? "@#{option}"
|
49
|
+
puts(<<-EOM)
|
50
|
+
|
51
|
+
You need to specify a value for #{option}. To do so, add a line like
|
52
|
+
|
53
|
+
#{option}: <value>
|
54
|
+
|
55
|
+
to your ~/.werkrc file (or any other .werkrc up the directory hierarchy)
|
56
|
+
|
57
|
+
EOM
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/werk/cli.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'werk/base'
|
3
|
+
|
4
|
+
module Werk
|
5
|
+
module CLI
|
6
|
+
module Options
|
7
|
+
def self.order!(args)
|
8
|
+
options = {}
|
9
|
+
optparse = OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: werk [options] <command> [arguments]"
|
11
|
+
|
12
|
+
opts.on( '-f', '--rcfile FILE', 'Load the specified werkrc file (in lieu of .werkrc files)' ) do |file|
|
13
|
+
options[:rcfile] = file
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on( '-v', '--verbose', 'Be verbose' ) do
|
17
|
+
options[:verbose] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
21
|
+
puts opts
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
end
|
25
|
+
begin
|
26
|
+
optparse.order!(args)
|
27
|
+
raise "You need to specify a command" if args.empty?
|
28
|
+
rescue
|
29
|
+
puts $!
|
30
|
+
puts optparse
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
options
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.start
|
38
|
+
inst = Werk::Base.new(Options.order!(ARGV))
|
39
|
+
result = inst.send ARGV.shift, *ARGV
|
40
|
+
puts result unless result.nil?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Werk
|
2
|
+
module Commands
|
3
|
+
def list
|
4
|
+
ticket_list
|
5
|
+
end
|
6
|
+
|
7
|
+
def show(branch=nil)
|
8
|
+
branch = repo_current_head unless branch
|
9
|
+
|
10
|
+
# This'll throw if we can't get a ticket out
|
11
|
+
ticket = branch2ticket(branch)
|
12
|
+
system "open #{@trac_url.sub('xmlrpc', 'ticket/' + ticket)}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def start(ticket, *args)
|
16
|
+
# Are we starting a hotfix?
|
17
|
+
hotfix = args.include? "--hotfix"
|
18
|
+
|
19
|
+
# Start a ticket named 'ticket', branching from the correct place if needed
|
20
|
+
repo_branch(ticket2branch(ticket, hotfix), (hotfix)? "production" : "master")
|
21
|
+
|
22
|
+
# Push the branch up to origin
|
23
|
+
repo_push
|
24
|
+
|
25
|
+
# Annotate the ticket
|
26
|
+
take_action(branch2ticket(ticket), "start")
|
27
|
+
end
|
28
|
+
|
29
|
+
def markfortest(branch = nil)
|
30
|
+
take_action(branch, "testing")
|
31
|
+
end
|
32
|
+
|
33
|
+
def markrejected(branch = nil)
|
34
|
+
take_action(branch, "reject")
|
35
|
+
end
|
36
|
+
|
37
|
+
def markaccepted(branch = nil)
|
38
|
+
take_action(branch, "pass")
|
39
|
+
end
|
40
|
+
|
41
|
+
def mergeandclose(branch = nil)
|
42
|
+
branch = repo_current_head unless branch
|
43
|
+
|
44
|
+
|
45
|
+
# This'll throw if we can't get a ticket out
|
46
|
+
ticket = branch2ticket(branch)
|
47
|
+
|
48
|
+
# Push the branch
|
49
|
+
repo_checkout(branch)
|
50
|
+
repo_pull
|
51
|
+
repo_push
|
52
|
+
|
53
|
+
# Pull a new master / production
|
54
|
+
targets = ["master"]
|
55
|
+
targets << "production" if branch.include? "hotfix"
|
56
|
+
targets.each do |target|
|
57
|
+
repo_checkout(target)
|
58
|
+
repo_pull
|
59
|
+
repo_merge(branch)
|
60
|
+
repo_push
|
61
|
+
end
|
62
|
+
|
63
|
+
# Annotate the ticket
|
64
|
+
take_action(ticket, "merged")
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def take_action(branch, action)
|
69
|
+
branch = repo_current_head unless branch
|
70
|
+
|
71
|
+
# This'll throw if we can't get a ticket out
|
72
|
+
ticket = branch2ticket(branch)
|
73
|
+
|
74
|
+
# Push the branch
|
75
|
+
repo_push
|
76
|
+
|
77
|
+
# Annotate the ticket according to the action
|
78
|
+
actions = ticket_actions(ticket).map { |x| x[0] }
|
79
|
+
|
80
|
+
raise "#{action} is not a valid action for ticket #{ticket}. Valid actions are #{actions.join ', '}" unless actions.include? action
|
81
|
+
ticket_annotate(ticket, "Changed via werk", :action => action)
|
82
|
+
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def ticket2branch(name, hotfix = false)
|
87
|
+
prefix = (hotfix)? "hotfix" : "ticket"
|
88
|
+
"#{prefix}_#{branch2ticket(name)}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def branch2ticket(name)
|
92
|
+
result = /\D*(\d*).*/.match(name)[1]
|
93
|
+
raise "Not on a ticket branch" if result.nil?
|
94
|
+
result
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/werk/git.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Werk
|
2
|
+
module Git
|
3
|
+
def repo_checkout(new_branch, *args)
|
4
|
+
system "git co #{new_branch}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def repo_branch(new_branch, source_branch = "HEAD", *args)
|
8
|
+
require_clean
|
9
|
+
system "git co -b #{new_branch} #{source_branch}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def repo_merge(from_branch, *args)
|
13
|
+
require_clean
|
14
|
+
system "git merge --no-ff #{from_branch}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def repo_push(remote = "origin")
|
18
|
+
require_clean
|
19
|
+
system "git push #{remote} HEAD"
|
20
|
+
end
|
21
|
+
|
22
|
+
def repo_pull(remote = "origin")
|
23
|
+
require_clean
|
24
|
+
system "git pull #{remote}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def repo_current_head
|
28
|
+
`git symbolic-ref HEAD`.gsub("refs/heads/", "")
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def require_clean
|
33
|
+
raise "Your working copy isn't clean" unless system "git diff --quiet"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/werk/trac.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'trac4r'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Werk
|
5
|
+
module Trac
|
6
|
+
def ticket_list(*args)
|
7
|
+
setup_trac
|
8
|
+
conditions = {}
|
9
|
+
conditions[:owner] = @trac_user unless args.include? "--everyone"
|
10
|
+
conditions[:status] = "!closed" unless args.include? "--all"
|
11
|
+
@trac.tickets.query(conditions).each do |ticket_id|
|
12
|
+
ticket_show(ticket_id, :oneline)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def ticket_show(ticket_id, format = :long)
|
17
|
+
setup_trac
|
18
|
+
ticket = ticket_get(ticket_id)
|
19
|
+
case format
|
20
|
+
when :long
|
21
|
+
template = File.read(File.join(File.dirname(__FILE__), '..', '..', 'templates', 'ticket_show.erb'))
|
22
|
+
ERB.new(template).result(ticket.send :binding)
|
23
|
+
when :oneline
|
24
|
+
"#{ticket.id}: #{ticket.summary} (#{ticket.status})"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def ticket_actions(ticket_id)
|
29
|
+
setup_trac
|
30
|
+
@trac.tickets.actions(ticket_id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ticket_annotate(ticket_id, comment, attributes = {})
|
34
|
+
setup_trac
|
35
|
+
@trac.tickets.update ticket_id.to_i, comment, attributes, false
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def ticket_get(ticket_id)
|
40
|
+
setup_trac
|
41
|
+
@trac.tickets.get ticket_id
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_trac
|
45
|
+
requires_variables %w( trac_url trac_user trac_password )
|
46
|
+
@trac = ::Trac.new(@trac_url, @trac_user, @trac_password) unless defined? @trac
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
ID: <%= id %>
|
2
|
+
Owner: <%= owner %>
|
3
|
+
Reported By: <%= reporter %>
|
4
|
+
Created: <%= created_at.to_time %>
|
5
|
+
Last Updated: <%= updated_at.to_time %>
|
6
|
+
Status: <%= status %>
|
7
|
+
Summary: <%= summary %>
|
8
|
+
Type: <%= type %>
|
9
|
+
Priority: <%= priority %>
|
10
|
+
|
11
|
+
Description:
|
12
|
+
|
13
|
+
<%= description %>
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: werk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mat Trudel
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-12-30 00:00:00 -05:00
|
19
|
+
default_executable: werk
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: trac4r
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: "werk combines ticketing and repos into definable workflows. I go to werk. "
|
36
|
+
email:
|
37
|
+
- mat@geeky.net
|
38
|
+
executables:
|
39
|
+
- werk
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- README.md
|
44
|
+
files:
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- bin/werk
|
48
|
+
- lib/werk.rb
|
49
|
+
- lib/werk/base.rb
|
50
|
+
- lib/werk/cli.rb
|
51
|
+
- lib/werk/commands.rb
|
52
|
+
- lib/werk/git.rb
|
53
|
+
- lib/werk/trac.rb
|
54
|
+
- templates/ticket_show.erb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/mtrudel/werk
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 3
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 1.3.7
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: werk is a developer workflow tool that makes teams happy
|
89
|
+
test_files: []
|
90
|
+
|