trellish 0.0.15 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +40 -9
- data/bin/trellish +33 -19
- data/lib/trellish.rb +8 -2
- data/lib/trellish/auth.rb +6 -1
- data/lib/trellish/campfire.rb +24 -0
- data/lib/trellish/card.rb +97 -8
- data/lib/trellish/git.rb +60 -6
- data/lib/trellish/version.rb +1 -1
- data/trellish.example.yml +11 -1
- data/trellish.gemspec +1 -0
- metadata +19 -2
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Trellish
|
2
2
|
|
3
|
-
Trellish is used to finish a Trello card. It does everything necessary to move a development card from
|
3
|
+
Trellish is used to start and finish a Trello card. It does everything necessary to move a development card from "Next up" to "In Progress" when you start work on a card and again from "In Progress" to "QA" when you finish a card.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -16,9 +16,9 @@ Or install it yourself as:
|
|
16
16
|
|
17
17
|
$ gem install trellish
|
18
18
|
|
19
|
-
##
|
19
|
+
## Setup
|
20
20
|
|
21
|
-
Create a trellish.yml
|
21
|
+
Create a `./trellish.yml`, `~/trellish.yml` or `~/.trellish` file with the contents of [trellish.example.yml](trellish/blob/master/trellish.example.yml). Set it up like this:
|
22
22
|
|
23
23
|
1. Sign in to Trello and go to https://trello.com/1/appKey/generate.
|
24
24
|
1. Copy "Key" from that page to trello\_api\_key.
|
@@ -28,18 +28,49 @@ Create a trellish.yml file in your current directory or home directory. Set it u
|
|
28
28
|
1. Run: `curl -u 'username' -d '{"scopes":["repo"],"note":"Trellish"}' https://api.github.com/authorizations`
|
29
29
|
1. Copy the token parameter from the response to github\_oauth\_token.
|
30
30
|
|
31
|
-
|
31
|
+
Optionally, Trellish can announce the starting and finishing of cards on 37signal's [Campfire](http://campfirenow.com/). To enable this:
|
32
|
+
|
33
|
+
1. Sign in to Campfire and go to your "my info" page. You can find the link in the upper-right corner.
|
34
|
+
1. Copy your subdomain name to campfire\_subdomain.
|
35
|
+
1. Copy your API authentication token to campfire\_token.
|
36
|
+
1. Specify the room name in campfire\_room.
|
37
|
+
|
38
|
+
## Usage
|
39
|
+
|
40
|
+
By default, Trellish expects a Trello board named `Current` with 3 lists: `Next up`, `In progress`, and `QA`. You can change these defaults using the Trellish config file.
|
41
|
+
|
42
|
+
To start work on a card:
|
43
|
+
|
44
|
+
trellish start [Trello card id or URL]
|
45
|
+
|
46
|
+
This will:
|
47
|
+
|
48
|
+
- move the card from `Next up` to `In progress`,
|
49
|
+
- add you as a member,
|
50
|
+
- create a local git branch named using your git initials and the card's title.
|
51
|
+
|
52
|
+
If you don't provide a card id or URL on the command line, trellish shows you the cards in `Next up` and prompts you to select one, like so:
|
53
|
+
|
54
|
+
Select a card:
|
55
|
+
1. BUG: crash adding a comment
|
56
|
+
2. users can select an avatar
|
57
|
+
3. add iPad integration tests
|
58
|
+
>
|
59
|
+
|
60
|
+
When you're done working on a card, finish it using:
|
32
61
|
|
33
|
-
trellish
|
62
|
+
trellish finish [Trello card id or URL]
|
34
63
|
|
35
|
-
|
64
|
+
Like:
|
36
65
|
|
37
|
-
trellish a3Wbcde4
|
66
|
+
trellish finish https://trello.com/c/a3Wbcde4
|
67
|
+
trellish finish a3Wbcde4
|
68
|
+
trellish finish
|
38
69
|
|
39
70
|
This will:
|
40
71
|
|
41
|
-
- create a pull request to merge your topic branch into master
|
42
|
-
- add a link to the pull request
|
72
|
+
- create a pull request to merge your topic branch into master (with a description linking back to the card)
|
73
|
+
- add a link to the pull request from the card's description
|
43
74
|
- remove everyone from the card
|
44
75
|
- move the card to the QA list
|
45
76
|
|
data/bin/trellish
CHANGED
@@ -1,32 +1,46 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
2
3
|
|
4
|
+
require 'readline'
|
3
5
|
require 'trellish'
|
4
6
|
require 'trellish/card'
|
5
7
|
require 'yaml'
|
6
8
|
|
7
|
-
|
8
|
-
Trellish.logger.error "Please provide a Trello card id or URL."
|
9
|
-
exit
|
10
|
-
end
|
9
|
+
# process config file
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
YAML.load(File.read(File.expand_path('~/trellish.yml')))
|
16
|
-
end
|
11
|
+
config_paths = %w(trellish.yml ~/trellish.yml ~/.trellish)
|
12
|
+
config_filepath = config_paths.collect { |e| File.expand_path(e) }.find { |e| File.exists?(e) }
|
13
|
+
config_file = YAML.load(File.read(config_filepath)) if config_filepath
|
17
14
|
|
18
15
|
unless config_file
|
19
|
-
Trellish.logger.error "Could not locate
|
16
|
+
Trellish.logger.error "Could not locate your settings in ./trellish.yml, ~/trellish.yml, or ~/.trellish"
|
20
17
|
exit
|
21
18
|
end
|
22
19
|
|
23
|
-
Trellish.configure(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
git_base_branch: config_file['git_base_branch'] || 'master',
|
29
|
-
qa_list_name: config_file['qa_list_name'] || 'QA')
|
20
|
+
Trellish.configure(config_file)
|
21
|
+
|
22
|
+
# parse command-line arguments
|
23
|
+
|
24
|
+
case ARGV[0]
|
30
25
|
|
31
|
-
|
32
|
-
card
|
26
|
+
when 'start'
|
27
|
+
card =
|
28
|
+
if ARGV[1]
|
29
|
+
Trellish::Card.new(ARGV[1])
|
30
|
+
else
|
31
|
+
Trellish::Card.select_from_list(Trellish.config[:next_up_list_name], false)
|
32
|
+
end
|
33
|
+
puts %Q(Starting card “#{card.name}”)
|
34
|
+
card.start
|
35
|
+
|
36
|
+
when 'finish'
|
37
|
+
card =
|
38
|
+
if ARGV[1]
|
39
|
+
Trellish::Card.new(ARGV[1])
|
40
|
+
else
|
41
|
+
Trellish::Card.select_from_list(Trellish.config[:in_progress_list_name], true)
|
42
|
+
end
|
43
|
+
puts %Q(Finishing card “#{card.name}”)
|
44
|
+
card.finish
|
45
|
+
|
46
|
+
end
|
data/lib/trellish.rb
CHANGED
@@ -12,8 +12,14 @@ module Trellish
|
|
12
12
|
trello_oauth_secret: 'TRELLO_OAUTH_SECRET',
|
13
13
|
trello_oauth_token: 'TRELLO_OAUTH_TOKEN',
|
14
14
|
github_oauth_token: 'GITHUB_OAUTH_TOKEN',
|
15
|
-
git_base_branch: '
|
16
|
-
|
15
|
+
git_base_branch: 'master',
|
16
|
+
board_name: 'Current',
|
17
|
+
next_up_list_name: 'Next up',
|
18
|
+
in_progress_list_name: 'In progress',
|
19
|
+
qa_list_name: 'QA',
|
20
|
+
campfire_subdomain: nil,
|
21
|
+
campfire_token: nil,
|
22
|
+
campfire_room: nil
|
17
23
|
}
|
18
24
|
|
19
25
|
@valid_config_keys = @config.keys
|
data/lib/trellish/auth.rb
CHANGED
@@ -8,7 +8,11 @@ module Trellish
|
|
8
8
|
|
9
9
|
Trello::Authorization.const_set :AuthPolicy, OAuthPolicy
|
10
10
|
|
11
|
+
@@member = nil
|
12
|
+
|
11
13
|
def self.authorize
|
14
|
+
return @@member if @@member
|
15
|
+
|
12
16
|
OAuthPolicy.consumer_credential = OAuthCredential.new(
|
13
17
|
Trellish.config[:trello_api_key],
|
14
18
|
Trellish.config[:trello_oauth_secret])
|
@@ -17,7 +21,8 @@ module Trellish
|
|
17
21
|
# Test that the user is authorized
|
18
22
|
begin
|
19
23
|
member_id = Trello::Token.find(OAuthPolicy.token.key).member_id
|
20
|
-
Member.find(member_id)
|
24
|
+
@@member = Member.find(member_id)
|
25
|
+
@@member
|
21
26
|
rescue
|
22
27
|
Trellish.logger.error "Unable to authorize access to Trello API."
|
23
28
|
exit
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'tinder'
|
2
|
+
|
3
|
+
module Trellish
|
4
|
+
module Campfire
|
5
|
+
|
6
|
+
def announce(message)
|
7
|
+
room.speak message if room
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def room
|
13
|
+
@room ||=
|
14
|
+
if [:campfire_subdomain, :campfire_token, :campfire_room].all? { |s| Trellish.config[s] }
|
15
|
+
campfire = Tinder::Campfire.new Trellish.config[:campfire_subdomain], :token => Trellish.config[:campfire_token]
|
16
|
+
campfire.rooms.find { |room| room.name == Trellish.config[:campfire_room] }
|
17
|
+
end
|
18
|
+
rescue
|
19
|
+
Trellish.logger.error "Unable to access Campfire. Is your subdomain, token, and room name correct?"
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/trellish/card.rb
CHANGED
@@ -1,41 +1,130 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'trellish/campfire'
|
1
4
|
require 'trellish/git'
|
2
5
|
|
3
6
|
module Trellish
|
4
7
|
class Card
|
5
8
|
include Trello
|
6
9
|
include Trellish::Git
|
10
|
+
include Trellish::Campfire
|
11
|
+
|
12
|
+
def self.select_from_list(list_name, assigned_to_me = false)
|
13
|
+
me = Trellish::Auth.authorize
|
14
|
+
boards = me.boards(filter: :open)
|
15
|
+
current_board = boards.find { |board| board.name == Trellish.config[:board_name] }
|
16
|
+
if current_board.nil?
|
17
|
+
Trellish.logger.error "Unable to find a board named `#{Trellish.config[:board_name]}`."
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
|
21
|
+
list = current_board.lists.find { |list| list.name == list_name }
|
22
|
+
if list.nil?
|
23
|
+
Trellish.logger.error "Unable to find a list named `#{list_name}` on board `#{Trellish.config[:board_name]}`."
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
cards = list.cards
|
28
|
+
|
29
|
+
if assigned_to_me
|
30
|
+
cards = cards.find_all { |card| card.member_ids.include?(me.id) }
|
31
|
+
end
|
32
|
+
|
33
|
+
case cards.length
|
34
|
+
when 0
|
35
|
+
if assigned_to_me
|
36
|
+
Trellish.logger.error "There are no cards assigned to you in the list `#{list_name}`"
|
37
|
+
else
|
38
|
+
Trellish.logger.error "There are no cards in the list `#{list_name}`"
|
39
|
+
end
|
40
|
+
exit
|
41
|
+
when 1
|
42
|
+
index = 0
|
43
|
+
else
|
44
|
+
puts "\nSelect a card:"
|
45
|
+
cards.each_with_index { |card, index| puts "#{index+1}. #{card.name}" }
|
46
|
+
index = Readline.readline("> ")
|
47
|
+
exit if index.blank?
|
48
|
+
index = index.to_i - 1
|
49
|
+
end
|
50
|
+
|
51
|
+
Trellish::Card.new( cards[index].id )
|
52
|
+
end
|
7
53
|
|
8
54
|
def initialize(card_id_or_url)
|
9
55
|
@member = Trellish::Auth.authorize
|
10
56
|
@card = Trello::Card.find(parse_card_id(card_id_or_url))
|
11
57
|
end
|
12
58
|
|
59
|
+
def add_me_as_member
|
60
|
+
@card.add_member(@member) unless @card.member_ids.include?(@member.id)
|
61
|
+
end
|
62
|
+
|
13
63
|
def add_pull_request_link
|
14
|
-
|
15
|
-
|
64
|
+
# Unfortunately, changing the card's description changes the card's URL, which breaks
|
65
|
+
# the link from the pull request, and our announcements to Campfire. Instead, add
|
66
|
+
# a comment to the pull request.
|
67
|
+
# @card.description = "[Pull Request](#{github_pull_request_url})\n\n#{@card.description}"
|
68
|
+
# @card.update!
|
69
|
+
message = "Pull request is at #{github_pull_request_url}"
|
70
|
+
@card.add_comment message
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_local_branch
|
74
|
+
branch_name = "#{git_user_initials}-#{short_name}"
|
75
|
+
git_create_local_branch(branch_name)
|
16
76
|
end
|
17
77
|
|
18
78
|
def finish
|
79
|
+
if !current_git_branch_is_up_to_date?
|
80
|
+
finish = Readline.readline("Your remote branch isn’t up-to-date. Finish anyway? [y,N] ")
|
81
|
+
exit unless ['y','yes'].include?(finish.strip.downcase)
|
82
|
+
end
|
83
|
+
|
19
84
|
add_pull_request_link
|
20
85
|
remove_all
|
21
86
|
move_to_qa
|
87
|
+
announce %Q([Trellish] Finished card “#{@card.name}” #{@card.url}. The pull request is at #{github_pull_request_url})
|
22
88
|
end
|
23
89
|
|
24
90
|
def move_to_qa
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
91
|
+
move_to_list(Trellish.config[:qa_list_name])
|
92
|
+
end
|
93
|
+
|
94
|
+
def move_to_in_progress
|
95
|
+
move_to_list(Trellish.config[:in_progress_list_name])
|
96
|
+
end
|
97
|
+
|
98
|
+
def name
|
99
|
+
@card.name
|
31
100
|
end
|
32
101
|
|
33
102
|
def remove_all
|
34
103
|
@card.remove_all_members
|
35
104
|
end
|
36
105
|
|
106
|
+
def short_name
|
107
|
+
name.strip.downcase.tr(' ','-').gsub(/[^0-9A-Za-z\-]/, '')[0..30]
|
108
|
+
end
|
109
|
+
|
110
|
+
def start
|
111
|
+
move_to_in_progress
|
112
|
+
add_me_as_member
|
113
|
+
create_local_branch
|
114
|
+
announce %Q([Trellish] Starting card “#{@card.name}” #{@card.url})
|
115
|
+
end
|
116
|
+
|
37
117
|
private
|
38
118
|
|
119
|
+
def move_to_list(list_name)
|
120
|
+
list = @card.board.lists.find { |list| list.name == list_name }
|
121
|
+
if list
|
122
|
+
@card.move_to_list(list)
|
123
|
+
else
|
124
|
+
Trellish.logger.warn "Unable to move card to #{list_name} list. No list named #{list_name} found."
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
39
128
|
def parse_card_id(card_id_or_url)
|
40
129
|
card_id_or_url.match(/[A-Za-z0-9]*$/)[0]
|
41
130
|
end
|
data/lib/trellish/git.rb
CHANGED
@@ -3,10 +3,6 @@ require 'faraday'
|
|
3
3
|
module Trellish
|
4
4
|
module Git
|
5
5
|
|
6
|
-
def current_git_branch
|
7
|
-
@current_git_branch ||= `cat .git/head`.split('/').last.strip
|
8
|
-
end
|
9
|
-
|
10
6
|
def github_pull_request_url
|
11
7
|
return @github_pull_request_url if @github_pull_request_url
|
12
8
|
conn = Faraday.new(:url => 'https://api.github.com', :ssl => {:ca_file => '/System/Library/OpenSSL/certs/ca-certificates.crt'}) do |faraday|
|
@@ -20,7 +16,8 @@ module Trellish
|
|
20
16
|
req.headers['Authorization'] = "token #{Trellish.config[:github_oauth_token]}"
|
21
17
|
req.body = {
|
22
18
|
title: @card.name,
|
23
|
-
|
19
|
+
body: "[Trello card](#{@card.url})",
|
20
|
+
base: git_base_branch,
|
24
21
|
head: "#{git_repository_owner}:#{current_git_branch}"
|
25
22
|
}.to_json
|
26
23
|
end
|
@@ -43,8 +40,65 @@ module Trellish
|
|
43
40
|
@git_repository_owner ||= matches[1]
|
44
41
|
end
|
45
42
|
|
43
|
+
def git_create_local_branch(branch_name)
|
44
|
+
`git checkout -b #{branch_name} #{git_base_branch}`
|
45
|
+
rescue
|
46
|
+
Trellish.logger.warn "Failed to create a local git branch named #{branch_name}."
|
47
|
+
end
|
48
|
+
|
49
|
+
def current_git_branch_is_up_to_date?
|
50
|
+
git_remote_up_to_date?(current_git_branch)
|
51
|
+
end
|
52
|
+
|
53
|
+
def git_user_initials
|
54
|
+
return @user_initials if @user_initials
|
55
|
+
username = presence(`git config github.user`) || presence(`git config user.email`) || presence(`whoami`)
|
56
|
+
@user_initials = username[0..2]
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def current_git_branch
|
62
|
+
@current_git_branch ||= `cat #{git_dir}/head`.split('/').last.strip
|
63
|
+
end
|
64
|
+
|
65
|
+
def git_base_branch
|
66
|
+
Trellish.config[:git_base_branch]
|
67
|
+
end
|
68
|
+
|
69
|
+
def git_dir
|
70
|
+
return @git_dir if @git_dir
|
71
|
+
path = `git rev-parse --git-dir`.strip
|
72
|
+
if path[/^fatal/]
|
73
|
+
Trellish.logger.error "Failed to find your git repository."
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
@git_dir = path
|
77
|
+
end
|
78
|
+
|
79
|
+
def git_hash_for_ref(ref)
|
80
|
+
`git show-ref --hash #{ref}`.strip
|
81
|
+
end
|
82
|
+
|
83
|
+
def git_remote_up_to_date?(local_branch_name)
|
84
|
+
remote_branch_name = git_remote_branch_for_local_branch(local_branch_name)
|
85
|
+
|
86
|
+
local_hash = git_hash_for_ref("heads/#{local_branch_name}")
|
87
|
+
remote_hash = git_hash_for_ref("remotes/#{remote_branch_name}")
|
88
|
+
|
89
|
+
local_hash == remote_hash
|
90
|
+
end
|
91
|
+
|
92
|
+
def git_remote_branch_for_local_branch(local_branch_name)
|
93
|
+
`git for-each-ref --format='%(upstream:short)' refs/heads/#{local_branch_name}`.strip
|
94
|
+
end
|
95
|
+
|
46
96
|
def matches
|
47
|
-
@matches ||=
|
97
|
+
@matches ||= remote_url.match(%r|^git@github.com:([^/]*)\/([^\.]*)\.git$|)
|
98
|
+
end
|
99
|
+
|
100
|
+
def presence(s)
|
101
|
+
s.strip if s && !s.strip.empty?
|
48
102
|
end
|
49
103
|
|
50
104
|
def remote_url
|
data/lib/trellish/version.rb
CHANGED
data/trellish.example.yml
CHANGED
@@ -18,5 +18,15 @@ github_oauth_token: numbers_and_letters_and_stuff
|
|
18
18
|
# The branch you want your changes merged into
|
19
19
|
git_base_branch: master
|
20
20
|
|
21
|
-
# Name of your
|
21
|
+
# Name of your Trello board
|
22
|
+
board_name: Current
|
23
|
+
|
24
|
+
# Names of your Next up, In progress, and QA lists in Trello
|
25
|
+
next_up_list_name: Next up
|
26
|
+
in_progress_list_name: In progress
|
22
27
|
qa_list_name: QA
|
28
|
+
|
29
|
+
# Campfire subdomain, room & your token
|
30
|
+
campfire_subdomain: subdomain
|
31
|
+
campfire_token: 1111111111111111111111111111111111111111
|
32
|
+
campfire_room: Development
|
data/trellish.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trellish
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.18
|
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:
|
12
|
+
date: 2013-01-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: oauth2
|
@@ -43,6 +43,22 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: 0.4.4
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: tinder
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.9.1
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.9.1
|
46
62
|
description: Create a pull request, put link to it on the card, remove everyone and
|
47
63
|
move the card to the QA list
|
48
64
|
email:
|
@@ -61,6 +77,7 @@ files:
|
|
61
77
|
- bin/trellish
|
62
78
|
- lib/trellish.rb
|
63
79
|
- lib/trellish/auth.rb
|
80
|
+
- lib/trellish/campfire.rb
|
64
81
|
- lib/trellish/card.rb
|
65
82
|
- lib/trellish/git.rb
|
66
83
|
- lib/trellish/version.rb
|