git-routines 0.3.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 58d64892849a8a45ed2ad5e96c9f8afff84ac8f5
4
+ data.tar.gz: 1928d6fff597db051bf12b60d7a67a5cf0e3fc92
5
+ SHA512:
6
+ metadata.gz: 990f4c4ff2f8b481feef73c4c7e8aaba24be4be873f8997fc8043fa8c00ad255b60fb2594e583a768f592184c8dc378ec33a8cad56bb5b69067da330e082aa50
7
+ data.tar.gz: 81017e49ae000ce43e31a22274f4f35aa0471aaab97ec9edb560180a54bf2ab14a9557764e0133cf0d00abd8d6ccfa5082b30518daf95aa6f4b0376d361afd01
data/.gitignore ADDED
@@ -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
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+
4
+ ## 0.3.0
5
+
6
+ * Gave it a (new) name
7
+
8
+
9
+ ## 0.2.1
10
+
11
+ * #2: A Chore story in PivotalTracker needs to be set to “accepted” rather than “finished”
12
+
13
+
14
+ ## 0.2.0
15
+
16
+ * Set PivotalTracker story to finished when done
17
+
18
+
19
+ ## 0.1.0
20
+
21
+ * Basic workflow
22
+ * Plugin infrastructure
23
+ * GitHub pull requests
24
+ * PivotalTracker story integration
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -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.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Git-Routines
2
+
3
+ `git start`—a Git workflow helper that:
4
+
5
+ * Shows all your PivotalTracker stories
6
+ * Creates feature/bug/chore branches (e.g. feature/4711-create-profile) for a chosen story
7
+ **Bonus:** you can also create a new story in the command line
8
+ * Asks to estimate the story if needed
9
+ * Sets the story started
10
+ * _Optional:_ Outputs a summary of the story to the command line (incl. description, tasks, and link to story)
11
+
12
+ `git finish`—to call after you finished the story:
13
+
14
+ * Sets the story finished
15
+ * _Optional:_ Rebases the current branch to your *master* branch
16
+ * Pushes the branch
17
+ * _Optional:_ Opens a GitHub pull request (incl. link to the corresponding PivotalTracker story)
18
+ * Checks out *master*
19
+
20
+ The first questions will only be asked once:
21
+ ![git-routines-demo](https://cloud.githubusercontent.com/assets/103399/5758653/6e2229c4-9cc7-11e4-9b49-c05e87817499.png)
22
+ ![git-routines-pull-request](https://cloud.githubusercontent.com/assets/103399/5758752/5f7af6de-9cc8-11e4-8e04-f968ee6acf94.png)
23
+
24
+ ## Setup
25
+
26
+ ```
27
+ gem install git-routines
28
+ ```
29
+
30
+ Other configuration happens on the fly. All information will be requested when needed.
31
+
32
+
33
+ ## Work in Progress
34
+
35
+ This is brand new but at [Homify](https://www.homify.co.uk) we’re using it for our workflows. It is mostly done, feedback is welcome, and [GitHub issues integration](https://github.com/hagenburger/git-routines/issues/1) as alternative to PivotalTracker is in the planning. Other tools should be easy to integrate. Have a look into the source or ping me [on Twitter](https://twitter.com/hagenburger). If you have a better name for this, let me know ;)
36
+
37
+
38
+ ## Contributing
39
+
40
+ 1. Fork it
41
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
42
+ 3. Commit your changes (`git commit -am 'add some awesome feature'`)
43
+ 4. Push to the branch (`git push origin my-new-feature`)
44
+ 5. Create new Pull Request
45
+
46
+
47
+ ## Copyright
48
+
49
+ Copyright 2015 [Nico Hagenburger](http://www.hagenburger.net).
50
+ See [MIT-LICENSE.txt](MIT-LICENSE.txt) for details.
51
+ Get in touch with [@hagenburger](http://twitter.com/hagenburger) on Twitter or [open an issue](https://github.com/hagenburger/git-routines/issues/new).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/git-finish ADDED
@@ -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-routines'
7
+
8
+ GitRoutines.finish
data/bin/git-start ADDED
@@ -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-routines'
7
+
8
+ GitRoutines.start
data/git-start.gemspec ADDED
@@ -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-routines/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'git-routines'
8
+ spec.version = Git::Routines::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-routines/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 GitRoutines
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-routines/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-routines.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-routines.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,121 @@
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
+ after_finish do
39
+ new_state = @story.story_type == 'chore' ? 'accepted' : 'finished'
40
+ update_story @story.id, current_state: new_state
41
+ end
42
+
43
+
44
+ def generate_summary
45
+ <<-MARKDOWN.gsub(/^ /, '').gsub(/\n+/, "\n\n")
46
+ # #{@story.name}
47
+ #{@story.description}
48
+ #{@story.tasks.map{ |t| " * #{t.description}" }.join("\n") rescue ''}
49
+ <#{@story.url}>
50
+ MARKDOWN
51
+ end
52
+
53
+ def stories
54
+ @stories ||= @project.stories(filter: filter, limit: 999)
55
+ end
56
+
57
+ def filter
58
+ "current_state:unscheduled,unstarted,rejected mywork:#{@user_initials}"
59
+ end
60
+
61
+ def select_story
62
+ existing_stories = stories.map do |s|
63
+ s.story_type.upcase.ljust(9) + s.name
64
+ end
65
+ new_story_types = @story_types.inject({}) do |h, (key, type)|
66
+ h.update(key => type.to_s.upcase.ljust(9) + "Create new #{type} story")
67
+ end
68
+ choice = select_one_of('Select story', existing_stories, new_story_types)
69
+
70
+ if @story_types.has_key?(choice)
71
+ @story = create_story(@story_types[choice])
72
+ else
73
+ if choice >= 0 and choice < stories.length
74
+ @story = stories[choice]
75
+ else
76
+ abort "No valid story selected."
77
+ end
78
+ end
79
+ estimate if needs_estimation?
80
+ end
81
+
82
+ def needs_estimation?
83
+ @story.estimate.nil? &&
84
+ @story.story_type == 'feature' || @project.bugs_and_chores_are_estimatable
85
+ end
86
+
87
+ def estimate
88
+ scale = @project.point_scale.gsub(',', ', ')
89
+ estimate = ask_for("This story needs an estimate first [#{scale}]")
90
+ update_story @story.id, estimate: estimate
91
+ end
92
+
93
+ def update_story(id, attributes)
94
+ @tracker.put "/projects/#{@project_id}/stories/#{id}", params: attributes
95
+ end
96
+
97
+ def user_id_of(initials)
98
+ membership = @project.memberships.detect{ |m| m.person.initials == initials }
99
+ if membership.nil?
100
+ abort "User with initials #{initials} not found for this project."
101
+ end
102
+ membership.person.id
103
+ end
104
+
105
+ def create_story(type)
106
+ params = {
107
+ name: ask_for('Story title'),
108
+ description: ask_for('Story description'),
109
+ requested_by_id: requester_id,
110
+ owner_ids: [@user_id],
111
+ story_type: type
112
+ }
113
+ data = @tracker.post("/projects/#{@project_id}/stories", params: params).body
114
+ TrackerApi::Resources::Story.new({ client: @client }.merge(data))
115
+ end
116
+
117
+ def requester_id
118
+ question = 'Story requester initials'
119
+ initials = config('pivotal.requester', question, :local, @user_initials).upcase
120
+ user_id_of(initials)
121
+ 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 Routines
3
+ VERSION = "0.3.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-routines
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Nico Hagenburger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-05 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
+ - .ruby-version
66
+ - CHANGELOG.md
67
+ - Gemfile
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - bin/git-finish
72
+ - bin/git-start
73
+ - git-start.gemspec
74
+ - lib/git-routines.rb
75
+ - lib/git-routines/plugins/github.rb
76
+ - lib/git-routines/plugins/pivotal.rb
77
+ - lib/git-routines/plugins/pull-request.rb
78
+ - lib/git-routines/plugins/rebase.rb
79
+ - lib/git-routines/plugins/show-summary.rb
80
+ - lib/git-routines/version.rb
81
+ homepage: ''
82
+ licenses:
83
+ - MIT
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.0.3
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Connects Git with project management software
105
+ test_files: []