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 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 the In Progress list to the QA list.
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
- ## Usage
19
+ ## Setup
20
20
 
21
- Create a trellish.yml file in your current directory or home directory. Set it up like this:
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
- `git checkout` the topic branch for the card you are finishing. Then do this:
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 https://trello.com/c/a3Wbcde4
62
+ trellish finish [Trello card id or URL]
34
63
 
35
- or alternately:
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 to the beginning of the card description
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
 
@@ -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
- if ARGV[0].nil?
8
- Trellish.logger.error "Please provide a Trello card id or URL."
9
- exit
10
- end
9
+ # process config file
11
10
 
12
- config_file = if File.exists?('trellish.yml')
13
- YAML.load(File.read('trellish.yml'))
14
- elsif File.exists?(File.expand_path('~/trellish.yml'))
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 trellish.yml file in current directory or home directory."
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
- trello_api_key: config_file['trello_api_key'],
25
- trello_oauth_secret: config_file['trello_oauth_secret'],
26
- trello_oauth_token: config_file['trello_oauth_token'],
27
- github_oauth_token: config_file['github_oauth_token'],
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
- card = Trellish::Card.new(ARGV[0])
32
- card.finish
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
@@ -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: 'GIT_BASE_BRANCH',
16
- qa_list_name: 'QA'
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
@@ -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
@@ -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
- @card.description = "[Pull Request] (#{github_pull_request_url})\n\n#{@card.description}"
15
- @card.save
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
- qa_list = @card.board.lists.find { |list| list.name == Trellish.config[:qa_list_name] }
26
- if qa_list
27
- @card.move_to_list(qa_list)
28
- else
29
- Trellish.logger.warn "Unable to move card to #{Trellish.config[:qa_list_name]} list. No list named #{Trellish.config[:qa_list_name]} found."
30
- end
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
@@ -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
- base: Trellish.config[:git_base_branch],
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 ||= matches = remote_url.match(%r|^git@github.com:([^/]*)\/([^\.]*)\.git$|)
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
@@ -1,3 +1,3 @@
1
1
  module Trellish
2
- VERSION = "0.0.15"
2
+ VERSION = "0.0.18"
3
3
  end
@@ -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 QA list in Trello
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
@@ -17,4 +17,5 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.add_dependency 'oauth2', '>=0.7.0'
19
19
  gem.add_dependency 'ruby-trello-wgibbs', '>=0.4.4'
20
+ gem.add_dependency 'tinder', '>=1.9.1'
20
21
  end
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.15
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: 2012-12-17 00:00:00.000000000 Z
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