git-pivotal-tracker 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +3 -0
- data/CHANGELOG +45 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +74 -0
- data/LICENSE +21 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/bin/git-accept +7 -0
- data/bin/git-block +7 -0
- data/bin/git-finish +7 -0
- data/bin/git-info +7 -0
- data/bin/git-start +8 -0
- data/bin/git-unblock +7 -0
- data/features/accept.feature +140 -0
- data/features/block.feature +94 -0
- data/features/finish.feature +119 -0
- data/features/info.feature +99 -0
- data/features/start.feature +113 -0
- data/features/step_definitions/steps.rb +114 -0
- data/features/support/dsl/assertions.rb +11 -0
- data/features/support/dsl/data.rb +11 -0
- data/features/support/dsl/pivotal.rb +76 -0
- data/features/support/env.rb +6 -0
- data/features/support/git-pivotal.rb +69 -0
- data/features/test_repo/origin.git/COMMIT_EDITMSG +1 -0
- data/features/test_repo/origin.git/HEAD +1 -0
- data/features/test_repo/origin.git/config +8 -0
- data/features/test_repo/origin.git/description +1 -0
- data/features/test_repo/origin.git/hooks/applypatch-msg.sample +15 -0
- data/features/test_repo/origin.git/hooks/commit-msg.sample +24 -0
- data/features/test_repo/origin.git/hooks/post-commit.sample +8 -0
- data/features/test_repo/origin.git/hooks/post-receive.sample +15 -0
- data/features/test_repo/origin.git/hooks/post-update.sample +8 -0
- data/features/test_repo/origin.git/hooks/pre-applypatch.sample +14 -0
- data/features/test_repo/origin.git/hooks/pre-commit.sample +46 -0
- data/features/test_repo/origin.git/hooks/pre-rebase.sample +169 -0
- data/features/test_repo/origin.git/hooks/prepare-commit-msg.sample +36 -0
- data/features/test_repo/origin.git/hooks/update.sample +128 -0
- data/features/test_repo/origin.git/index +0 -0
- data/features/test_repo/origin.git/info/exclude +6 -0
- data/features/test_repo/origin.git/logs/HEAD +1 -0
- data/features/test_repo/origin.git/logs/refs/heads/master +1 -0
- data/features/test_repo/origin.git/objects/0c/6f7b1384910d1a2f137590095f008a06c7e00c +0 -0
- data/features/test_repo/origin.git/objects/10/ecf2b7ce989f01f3f7266e712b48d9275f2635 +0 -0
- data/features/test_repo/origin.git/objects/a5/71d56305df09fb060f6ccb730b46080d305beb +0 -0
- data/features/test_repo/origin.git/refs/heads/master +1 -0
- data/features/test_repo/readme +1 -0
- data/features/unblock.feature +68 -0
- data/git-pivotal-tracker.gemspec +27 -0
- data/lib/commands/accept.rb +76 -0
- data/lib/commands/base.rb +128 -0
- data/lib/commands/block.rb +59 -0
- data/lib/commands/bug.rb +19 -0
- data/lib/commands/card.rb +32 -0
- data/lib/commands/chore.rb +19 -0
- data/lib/commands/feature.rb +19 -0
- data/lib/commands/finish.rb +59 -0
- data/lib/commands/info.rb +58 -0
- data/lib/commands/map.rb +10 -0
- data/lib/commands/pick.rb +76 -0
- data/lib/commands/start.rb +35 -0
- data/lib/commands/unblock.rb +55 -0
- data/lib/git-pivotal-tracker.rb +11 -0
- data/readme.markdown +95 -0
- data/spec/commands/base_spec.rb +151 -0
- data/spec/commands/bug_spec.rb +24 -0
- data/spec/commands/chore_spec.rb +24 -0
- data/spec/commands/feature_spec.rb +24 -0
- data/spec/commands/finish_spec.rb +125 -0
- data/spec/commands/map_spec.rb +14 -0
- data/spec/commands/start_spec.rb +29 -0
- data/spec/factories.rb +13 -0
- data/spec/factory.rb +26 -0
- data/spec/spec_helper.rb +24 -0
- metadata +251 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'commands/base'
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
class Block < Base
|
5
|
+
Label = "blocked"
|
6
|
+
MessagePrefix = "Blocked:"
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
@story_id = args.shift if args.first =~ /^(\d+)$/
|
10
|
+
super(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def run!
|
14
|
+
super
|
15
|
+
|
16
|
+
unless story_id
|
17
|
+
put "No story id was supplied and you aren't on a topic branch!"
|
18
|
+
return 1
|
19
|
+
end
|
20
|
+
|
21
|
+
if story.labels.to_s.include?(Label)
|
22
|
+
put "Story #{story_id} is already blocked."
|
23
|
+
return 0
|
24
|
+
end
|
25
|
+
|
26
|
+
message = options[:message].to_s
|
27
|
+
|
28
|
+
if message.empty?
|
29
|
+
loop do
|
30
|
+
put "What's the reason for blocking this story?"
|
31
|
+
message = input.gets.chomp
|
32
|
+
break unless message.empty?
|
33
|
+
put ""
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
labels = story.labels.to_s.split(",").concat([Label]).join(",")
|
38
|
+
story.update :labels => labels
|
39
|
+
story.notes.create :author => full_name, :text => "#{MessagePrefix} #{message}"
|
40
|
+
put "Story #{story_id} has been blocked."
|
41
|
+
|
42
|
+
return 0
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def on_parse(opts)
|
48
|
+
opts.on("-m [String]", "--message [String]", "The message to provide when blocking a story."){ |m| options[:message] = m }
|
49
|
+
end
|
50
|
+
|
51
|
+
def story_id
|
52
|
+
@story_id || current_branch[/\d+/]
|
53
|
+
end
|
54
|
+
|
55
|
+
def story
|
56
|
+
@story ||= project.stories.find(story_id)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/commands/bug.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'commands/pick'
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
class Card < Pick
|
5
|
+
attr_accessor :story_id
|
6
|
+
|
7
|
+
def type
|
8
|
+
"story"
|
9
|
+
end
|
10
|
+
|
11
|
+
def plural_type
|
12
|
+
"cards"
|
13
|
+
end
|
14
|
+
|
15
|
+
def branch_suffix
|
16
|
+
if story.story_type == "bug"
|
17
|
+
"bugfix"
|
18
|
+
else
|
19
|
+
story.story_type
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def story
|
26
|
+
return @story if @story
|
27
|
+
raise ArgumentError, "No story id was given!" unless story_id
|
28
|
+
project.stories.find(story_id)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'commands/base'
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
class Finish < Base
|
5
|
+
|
6
|
+
def run!
|
7
|
+
super
|
8
|
+
|
9
|
+
unless story_id
|
10
|
+
put "Branch name must contain a Pivotal Tracker story id"
|
11
|
+
return 1
|
12
|
+
end
|
13
|
+
|
14
|
+
put "Marking Story #{story_id} as finished..."
|
15
|
+
if story.update(:current_state => finished_state)
|
16
|
+
topic_branch = current_branch
|
17
|
+
|
18
|
+
put "Pushing #{topic_branch} to #{remote}"
|
19
|
+
sys "git push --set-upstream #{remote} #{topic_branch}"
|
20
|
+
|
21
|
+
put "Pulling #{acceptance_branch}..."
|
22
|
+
sys "git checkout #{acceptance_branch}"
|
23
|
+
sys "git pull"
|
24
|
+
|
25
|
+
put "Merging #{topic_branch} into #{acceptance_branch}"
|
26
|
+
sys "git merge --no-ff #{topic_branch}"
|
27
|
+
|
28
|
+
put "Pushing #{acceptance_branch} to #{remote}"
|
29
|
+
sys "git push"
|
30
|
+
|
31
|
+
put "Now on #{acceptance_branch}."
|
32
|
+
|
33
|
+
return 0
|
34
|
+
else
|
35
|
+
put "Unable to mark Story #{story_id} as finished"
|
36
|
+
|
37
|
+
return 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def finished_state
|
44
|
+
if story.story_type == "chore"
|
45
|
+
"accepted"
|
46
|
+
else
|
47
|
+
"finished"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def story_id
|
52
|
+
match = current_branch[/\d+/] and match.to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
def story
|
56
|
+
@story ||= project.stories.find(story_id)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'commands/base'
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
class Info < Base
|
5
|
+
def initialize(*args)
|
6
|
+
@story_id = args.shift if args.first =~ /^(\d+)$/
|
7
|
+
super(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def run!
|
11
|
+
super
|
12
|
+
|
13
|
+
unless story_id
|
14
|
+
put "No story id was supplied and you aren't on a topic branch!"
|
15
|
+
return 1
|
16
|
+
end
|
17
|
+
|
18
|
+
put "Story: #{story.name}"
|
19
|
+
put "URL: #{story.url}"
|
20
|
+
put "Labels: #{story.labels.split(',').join(', ')}" if story.labels
|
21
|
+
put "State: #{story.accepted_at ? 'accepted' : 'not accepted'}"
|
22
|
+
|
23
|
+
colwidth = 74
|
24
|
+
|
25
|
+
put "\nDescription:\n"
|
26
|
+
put wrap_text("#{story.description}\n", colwidth).gsub(/^/, ' ').chomp
|
27
|
+
|
28
|
+
if options[:comments]
|
29
|
+
put "\nComments:\n"
|
30
|
+
story.notes.all.each do |note|
|
31
|
+
@output.printf " %-37s%37s\n\n", note.author, note.noted_at.strftime("%b %e, %Y %k:%M%p")
|
32
|
+
put wrap_text(note.text, colwidth - 2).gsub(/^/, ' ')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
return 0
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def wrap_text(txt, col = 80)
|
42
|
+
txt.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/,
|
43
|
+
"\\1\\3\n")
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_parse(opts)
|
47
|
+
opts.on("-c", "--comments", "Display comments"){ |v| options[:comments] = v }
|
48
|
+
end
|
49
|
+
|
50
|
+
def story_id
|
51
|
+
@story_id || current_branch[/\d+/]
|
52
|
+
end
|
53
|
+
|
54
|
+
def story
|
55
|
+
@story ||= project.stories.find(story_id)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/commands/map.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'commands/base'
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
class Pick < Base
|
5
|
+
|
6
|
+
def type
|
7
|
+
raise Error("must define in subclass")
|
8
|
+
end
|
9
|
+
|
10
|
+
def plural_type
|
11
|
+
raise Error("must define in subclass")
|
12
|
+
end
|
13
|
+
|
14
|
+
def branch_suffix
|
15
|
+
raise Error("must define in subclass")
|
16
|
+
end
|
17
|
+
|
18
|
+
def run!
|
19
|
+
response = super
|
20
|
+
return response if response > 0
|
21
|
+
|
22
|
+
msg = "Retrieving latest #{plural_type} from Pivotal Tracker"
|
23
|
+
if options[:only_mine]
|
24
|
+
msg += " for #{options[:full_name]}"
|
25
|
+
end
|
26
|
+
put "#{msg}..."
|
27
|
+
|
28
|
+
unless story
|
29
|
+
put "No #{plural_type} available!"
|
30
|
+
return 0
|
31
|
+
end
|
32
|
+
|
33
|
+
put "Story: #{story.name}"
|
34
|
+
put "URL: #{story.url}"
|
35
|
+
|
36
|
+
put "Updating #{type} status in Pivotal Tracker..."
|
37
|
+
story.update(:owned_by => options[:full_name], :current_state => :started)
|
38
|
+
|
39
|
+
if story.errors.empty?
|
40
|
+
suffix_or_prefix = ""
|
41
|
+
unless options[:quiet] || options[:defaults]
|
42
|
+
put "Enter branch name (will be #{options[:append_name] ? 'appended' : 'prepended'} by #{story.id}) [#{suffix_or_prefix}]: ", false
|
43
|
+
suffix_or_prefix = input.gets.chomp
|
44
|
+
end
|
45
|
+
suffix_or_prefix = branch_suffix if suffix_or_prefix == ""
|
46
|
+
|
47
|
+
if options[:append_name]
|
48
|
+
branch = "#{suffix_or_prefix}-#{story.id}"
|
49
|
+
else
|
50
|
+
branch = "#{story.id}-#{suffix_or_prefix}"
|
51
|
+
end
|
52
|
+
if get("git branch").match(branch).nil?
|
53
|
+
put "Switched to a new branch '#{branch}'"
|
54
|
+
sys "git checkout -b #{branch}"
|
55
|
+
end
|
56
|
+
|
57
|
+
return 0
|
58
|
+
else
|
59
|
+
put "Unable to mark #{type} as started"
|
60
|
+
put "\t" + story.errors.to_a.join("\n\t")
|
61
|
+
|
62
|
+
return 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def story
|
69
|
+
return @story if @story
|
70
|
+
|
71
|
+
conditions = { :story_type => type, :current_state => "unstarted", :limit => 1, :offset => 0 }
|
72
|
+
conditions[:owned_by] = options[:full_name] if options[:only_mine]
|
73
|
+
@story = project.stories.all(conditions).first
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'commands/map'
|
2
|
+
require 'commands/bug'
|
3
|
+
require 'commands/card'
|
4
|
+
require 'commands/chore'
|
5
|
+
require 'commands/feature'
|
6
|
+
|
7
|
+
module Commands
|
8
|
+
class Start
|
9
|
+
COMMAND_MAP = Map.new.merge({
|
10
|
+
"bug" => Commands::Bug,
|
11
|
+
"chore" => Commands::Chore,
|
12
|
+
"feature" => Commands::Feature,
|
13
|
+
/^\d+$/ => Commands::Card
|
14
|
+
})
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def for(*args)
|
18
|
+
identifier = args.shift
|
19
|
+
construct_instance_for(identifier, args) ||
|
20
|
+
raise(ArgumentError, "Unknown card identifier given: #{identifier}")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def construct_instance_for(identifier, args)
|
26
|
+
if klass=COMMAND_MAP[identifier]
|
27
|
+
instance = klass.new(*args)
|
28
|
+
instance.story_id = identifier if instance.respond_to?(:story_id=)
|
29
|
+
instance
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'commands/base'
|
2
|
+
require 'commands/block'
|
3
|
+
|
4
|
+
module Commands
|
5
|
+
class Unblock < Base
|
6
|
+
PlaceholderLabel = "."
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
@story_id = args.shift if args.first =~ /^(\d+)$/
|
10
|
+
super(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def run!
|
14
|
+
super
|
15
|
+
|
16
|
+
unless story_id
|
17
|
+
put "No story id was supplied and you aren't on a topic branch!"
|
18
|
+
return 1
|
19
|
+
end
|
20
|
+
|
21
|
+
unless story.labels.to_s.include?(Block::Label)
|
22
|
+
put "Story #{story_id} is already unblocked."
|
23
|
+
return 0
|
24
|
+
end
|
25
|
+
|
26
|
+
labels = story.labels.to_s.split(",") - [Block::Label]
|
27
|
+
|
28
|
+
# this line is to work aroudn Pivotal Tracker's broken API for removing the last
|
29
|
+
# label on a card. http://community.pivotaltracker.com/pivotal/topics/api_v3_cannot_remove_labels_from_a_story_anymore
|
30
|
+
if labels.empty?
|
31
|
+
labels << PlaceholderLabel
|
32
|
+
put "Note: a '.' label will be placed on this card due to a bug in the v3 API of Pivotal Tracker."
|
33
|
+
end
|
34
|
+
|
35
|
+
story.update :labels => labels.join(",")
|
36
|
+
put "Story #{story_id} has been unblocked."
|
37
|
+
|
38
|
+
return 0
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def on_parse(opts)
|
44
|
+
opts.on("-m [String]", "--message [String]", "The message to provide when blocking a story."){ |m| options[:message] = m }
|
45
|
+
end
|
46
|
+
|
47
|
+
def story_id
|
48
|
+
@story_id || current_branch[/\d+/]
|
49
|
+
end
|
50
|
+
|
51
|
+
def story
|
52
|
+
@story ||= project.stories.find(story_id)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|