git-pivotal-tracker 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|