git-start 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 73f2b69903091b8fd9445ac25d5d048f0f9cef2b
4
+ data.tar.gz: caf0a01c5ec21b1513db3adffd437f1125957f65
5
+ SHA512:
6
+ metadata.gz: b980b1dbb24ee141dc535750015bc6173830c36884c854ff3d07e3f3626410f67bb20874ccf9e73fb3be05150d119783b27c7c528bb6c0a7cb29111580f2d124
7
+ data.tar.gz: c7807baddd097b753443fe89e3f5429b64704853bb81bb20bcfe23489872b1fbed0eac06a3f7749fc37528cd051ac97fd893fb7857d35be734255e87bcaca701
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ * Basic workflow
6
+ * Plugin infrastructure
7
+ * GitHub pull requests
8
+ * PivotalTracker story integration
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Nico Hagenburger
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,22 @@
1
+ # Git-Start
2
+
3
+ `git start`—a Git workflow helper that:
4
+
5
+ * Shows all your PivotalTracker stories
6
+ * Creates feature/bug/chore branches for a chosen story
7
+ **Bonus:** you can also create a new story in the command line
8
+ * Sets the story started
9
+ * _Optional:_ Outputs a summary of the story to the command line
10
+
11
+ `git finish`—to call after you finished the story:
12
+
13
+ * Sets the story finished
14
+ * _Optional:_ Rebases the current branch to your *master* branch
15
+ * Pushes the branch
16
+ * _Optional:_ Opens a GitHub pull request (incl. link to the corresponding PivotalTracker story)
17
+ * Checks out *master*
18
+
19
+
20
+ ## Setup
21
+
22
+ The setup happens on the fly. All information will be requested when needed.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'git-start'
7
+
8
+ GitStart.finish
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'git-start'
7
+
8
+ GitStart.start
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'git-start/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'git-start'
8
+ spec.version = Git::Start::VERSION
9
+ spec.authors = ['Nico Hagenburger']
10
+ spec.email = ['nico@hagenburger.net']
11
+ spec.summary = %q{Connects Git with project management software}
12
+ spec.description = %q{Connects Git with project management software}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'hooks'
22
+ spec.add_development_dependency 'bundler', '~> 1.7'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ end
@@ -0,0 +1,139 @@
1
+ require 'git-start/version'
2
+ require 'hooks'
3
+ require 'fileutils'
4
+
5
+ PLUGINS = {
6
+ 'show-summary' => 'Show story summary after starting',
7
+ 'pivotal' => 'PivotalTracker integration',
8
+ 'rebase' => 'Rebase to default branch before finishing',
9
+ 'pull-request' => 'Open pull request after finishing',
10
+ }
11
+
12
+ class GitStart
13
+ include Hooks
14
+ include Hooks::InstanceHooks
15
+
16
+ define_hook :setup
17
+ define_hook :before_start
18
+ define_hook :after_start
19
+ define_hook :before_finish
20
+ define_hook :after_finish
21
+
22
+ class << self
23
+ attr_accessor :default_branch
24
+ attr_accessor :title
25
+ attr_accessor :summary
26
+
27
+ def start
28
+ run_hook :setup
29
+ run_hook :before_start
30
+ git :checkout, "-b #{branch}"
31
+ run_hook :after_start
32
+ end
33
+
34
+ def finish
35
+ @branch = current_branch
36
+ run_hook :setup
37
+ run_hook :before_finish
38
+ git :checkout, default_branch
39
+ run_hook :after_finish
40
+ end
41
+
42
+ def branch
43
+ @branch ||= ask_for('Branch name')
44
+ @branch.strip.downcase.gsub(/[^\w\d\/]+/, '-')
45
+ end
46
+
47
+ def default_branch
48
+ @default_branch ||= config(
49
+ 'git-start.default-branch',
50
+ 'Default branch name',
51
+ :local,
52
+ 'master'
53
+ )
54
+ end
55
+
56
+ def ask_for(question, default = nil)
57
+ print "#{question}#{" [#{default}]" unless default.nil?}: "
58
+ value = STDIN.readline.strip
59
+ value == '' ? default : value
60
+ end
61
+
62
+ def select_multiple_of(question, *option_lists)
63
+ select_of("#{question} (comma separated)", *option_lists)
64
+ end
65
+
66
+ def select_one_of(question, *option_lists)
67
+ select_of(question, *option_lists).first
68
+ end
69
+
70
+ def git(command, *options)
71
+ `git #{command} #{options.map{ |o| o.is_a?(Symbol) ? "--#{o}" : o}.join(' ')}`
72
+ end
73
+
74
+ def config(key, question = nil, scope = nil, default = nil)
75
+ value = git(:config, key).strip
76
+ if value == '' and !question.nil?
77
+ value = ask_for(question, default)
78
+ git :config, scope, key, value
79
+ end
80
+ value
81
+ end
82
+
83
+ def requires_gem(name)
84
+ require name
85
+ rescue LoadError
86
+ abort "\nPlease install the `#{name}` Gem first:\n\ngem install #{name}\n\n"
87
+ end
88
+
89
+ def include_plugin(plugin)
90
+ @included_plugins ||= []
91
+ return if @included_plugins.include?(plugin)
92
+ @included_plugins << plugin
93
+ file = File.expand_path("../git-start/plugins/#{plugin}.rb", __FILE__)
94
+ instance_eval File.read(file)
95
+ end
96
+
97
+ def current_branch
98
+ `git rev-parse --abbrev-ref HEAD`.strip
99
+ end
100
+
101
+ private
102
+
103
+ def select_of(question, *option_lists)
104
+ list_options *option_lists
105
+ choice = ask_for(question)
106
+ abort 'Command aborted.' if choice.nil?
107
+ choice.split(',').map do |item|
108
+ item =~ /^\d+$/ ? item.to_i - 1 : item.to_sym
109
+ end
110
+ end
111
+
112
+ def list_options(*option_lists)
113
+ option_lists.each do |list|
114
+ next unless list.any?
115
+ puts "\n"
116
+ list.each_with_index do |a, b|
117
+ key, value = list.is_a?(Hash) ? [a[0], a[1]] : [b + 1, a]
118
+ puts "[#{key}]".to_s.rjust(@column_size) + " #{value}"
119
+ end
120
+ end
121
+ puts "\n"
122
+ end
123
+ end
124
+
125
+ setup do
126
+ @column_size = 5
127
+ plugins = config('git-start.plugins').strip.split(',')
128
+ if plugins.empty?
129
+ question = 'Choose plugins (comma separated)'
130
+ select_multiple_of(question, PLUGINS.values).each do |index|
131
+ plugins << PLUGINS.keys[index]
132
+ end
133
+ git :config, :local, 'git-start.plugins', plugins.join(',')
134
+ end
135
+ plugins.each do |plugin|
136
+ include_plugin plugin
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,8 @@
1
+ requires_gem 'octokit'
2
+
3
+ setup do
4
+ remote_url = config('remote.origin.url')
5
+ @github_repo = remote_url.match(/(?<=:).+(?=\.git)/).to_s
6
+ @github_token = config('github.token', 'Your GitHub API token')
7
+ @github = Octokit::Client.new(access_token: @github_token)
8
+ end
@@ -0,0 +1,116 @@
1
+ requires_gem 'tracker_api'
2
+
3
+ # TrackerApi does not support saving yet
4
+ class ::TrackerApi::Client
5
+ def put(path, options = {})
6
+ request(:put, parse_query_and_convenience_headers(path, options))
7
+ end
8
+ end
9
+
10
+ setup do
11
+ @story_types = { f: :feature, b: :bug, c: :chore }
12
+ @tracker_token = config('pivotal.token', 'Your PivotalTracker API token', :global)
13
+ @user_initials = config('pivotal.user', 'Your PivotalTracker user initials', :global).upcase
14
+ @project_id = config('pivotal.project', 'PivotalTracker project ID (last part of URL)', :local)
15
+ @tracker = TrackerApi::Client.new(token: @tracker_token)
16
+ @project = @tracker.project(@project_id)
17
+ @user_id = user_id_of(@user_initials)
18
+ end
19
+
20
+ before_start do
21
+ select_story
22
+ @branch = "#{@story.story_type.downcase}/#{@story.id}-#{@story.name}"
23
+ @title = @story.name
24
+ @summary = generate_summary
25
+ end
26
+
27
+ after_start do
28
+ update_story @story.id, current_state: :started
29
+ end
30
+
31
+ before_finish do
32
+ @story_id = branch.match(/(?<=feature\/|chore\/|bug\/)(\d+)(?=-)/).to_s.to_i
33
+ @story = @project.story(@story_id)
34
+ @title = @story.name
35
+ @summary = generate_summary
36
+ end
37
+
38
+
39
+ def generate_summary
40
+ <<-MARKDOWN.gsub(/^ /, '').gsub(/\n+/, "\n\n")
41
+ # #{@story.name}
42
+ #{@story.description}
43
+ #{@story.tasks.map{ |t| " * #{t.description}" }.join("\n") rescue ''}
44
+ <#{@story.url}>
45
+ MARKDOWN
46
+ end
47
+
48
+ def stories
49
+ @stories ||= @project.stories(filter: filter, limit: 999)
50
+ end
51
+
52
+ def filter
53
+ "current_state:unscheduled,unstarted,rejected mywork:#{@user_initials}"
54
+ end
55
+
56
+ def select_story
57
+ existing_stories = stories.map do |s|
58
+ s.story_type.upcase.ljust(9) + s.name
59
+ end
60
+ new_story_types = @story_types.inject({}) do |h, (key, type)|
61
+ h.update(key => type.to_s.upcase.ljust(9) + "Create new #{type} story")
62
+ end
63
+ choice = select_one_of('Select story', existing_stories, new_story_types)
64
+
65
+ if @story_types.has_key?(choice)
66
+ @story = create_story(@story_types[choice])
67
+ else
68
+ if choice >= 0 and choice < stories.length
69
+ @story = stories[choice]
70
+ else
71
+ abort "No valid story selected."
72
+ end
73
+ end
74
+ estimate if needs_estimation?
75
+ end
76
+
77
+ def needs_estimation?
78
+ @story.estimate.nil? &&
79
+ @story.story_type == 'feature' || @project.bugs_and_chores_are_estimatable
80
+ end
81
+
82
+ def estimate
83
+ scale = @project.point_scale.gsub(',', ', ')
84
+ estimate = ask_for("This story needs an estimate first [#{scale}]")
85
+ update_story @story.id, estimate: estimate
86
+ end
87
+
88
+ def update_story(id, attributes)
89
+ @tracker.put "/projects/#{@project_id}/stories/#{id}", params: attributes
90
+ end
91
+
92
+ def user_id_of(initials)
93
+ membership = @project.memberships.detect{ |m| m.person.initials == initials }
94
+ if membership.nil?
95
+ abort "User with initials #{initials} not found for this project."
96
+ end
97
+ membership.person.id
98
+ end
99
+
100
+ def create_story(type)
101
+ params = {
102
+ name: ask_for('Story title'),
103
+ description: ask_for('Story description'),
104
+ requested_by_id: requester_id,
105
+ owner_ids: [@user_id],
106
+ story_type: type
107
+ }
108
+ data = @tracker.post("/projects/#{@project_id}/stories", params: params).body
109
+ TrackerApi::Resources::Story.new({ client: @client }.merge(data))
110
+ end
111
+
112
+ def requester_id
113
+ question = 'Story requester initials'
114
+ initials = config('pivotal.requester', question, :local, @user_initials).upcase
115
+ user_id_of(initials)
116
+ end
@@ -0,0 +1,5 @@
1
+ include_plugin 'github'
2
+
3
+ after_finish do
4
+ @github.create_pull_request @github_repo, 'master', branch, title, summary
5
+ end
@@ -0,0 +1,7 @@
1
+ before_finish do
2
+ git :checkout, default_branch
3
+ git :pull
4
+ git :checkout, branch
5
+ git :rebase, default_branch
6
+ git :push, 'origin', branch, :force
7
+ end
@@ -0,0 +1,6 @@
1
+ after_start do
2
+ unless summary.nil?
3
+ text = summary.strip.gsub(/^#+ *(.+)/){ $1.upcase }.gsub(/<(http.+)>/, '\\1')
4
+ puts "\n#{'-' * @column_size}\n\n#{text}\n\n#{'-' * @column_size}\n\n"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Git
2
+ module Start
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-start
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nico Hagenburger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hooks
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Connects Git with project management software
56
+ email:
57
+ - nico@hagenburger.net
58
+ executables:
59
+ - git-finish
60
+ - git-start
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".gitignore"
65
+ - CHANGELOG.md
66
+ - Gemfile
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - bin/git-finish
71
+ - bin/git-start
72
+ - git-start.gemspec
73
+ - lib/git-start.rb
74
+ - lib/git-start/plugins/github.rb
75
+ - lib/git-start/plugins/pivotal.rb
76
+ - lib/git-start/plugins/pull-request.rb
77
+ - lib/git-start/plugins/rebase.rb
78
+ - lib/git-start/plugins/show-summary.rb
79
+ - lib/git-start/version.rb
80
+ homepage: ''
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.2.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Connects Git with project management software
104
+ test_files: []