github-pivotal-flow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a4e63d5c7d9eda8c218b536bf973127a9ed3fd48
4
+ data.tar.gz: fcc22c50ee4958a67ffdad288bd13bdb86adaa90
5
+ SHA512:
6
+ metadata.gz: 85fb6cea0fbc88c4abd12b1622557378db19758037bb495b3132471fd01403a66fbfd7a724918a6316655d9a19bd455adbf2037ab34970be48a9828b45011df1
7
+ data.tar.gz: bec91a2d14f3cc3e597a0de10e03917ff8eef9efb8d65a3378d1c249a4a084a4c387775952eafa6bf0d0b3b3950e132f5d731ff051a36d7298ed11a8ab15b65f
data/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ # Git Pivotal Tracker Integration
2
+ # Copyright (c) 2013 the original author or authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
data/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # Github Pivotal Flow
2
+
3
+ `github-pivotal-flow` provides a set of additional Git commands to help developers when working with [Pivotal Tracker][pivotal-tracker], git-flow and Github pull requests.
4
+ It follows the branch structure recommended by [Git flow][git-flow].
5
+
6
+ This is the tool we use internally to speed up our development process.
7
+
8
+ [pivotal-tracker]: http://www.pivotaltracker.com
9
+ [git-flow]: https://github.com/nvie/gitflow
10
+
11
+ ## Installation
12
+ `github-pivotal-flow` requires at least **Ruby 1.8.7**, **Git 1.8.2.1** in order to run. It is tested against Rubies _1.8.7_, _1.9.3_, and _2.0.0_. In order to install it, do the following:
13
+
14
+ ```plain
15
+ $ gem install github-pivotal-flow
16
+ ```
17
+
18
+
19
+ ## Usage
20
+ `github-pivotal-flow` is intended to vastly speed up your development workflow.
21
+ The typical workflow looks something like the following:
22
+
23
+ ```plain
24
+ $ git start # Creates branch, opens a pull request on Github and starts story
25
+ $ git commit ...
26
+ $ git commit ... # Your existing development process
27
+ $ git commit ...
28
+ $ git finish # Merges back into the main branch. Pushes to origin, destroys branch and finishes story.
29
+ ```
30
+
31
+
32
+ ## Configuration
33
+
34
+ ### Git Client
35
+ In order to use `github-pivotal-flow`, a few Git client configuration properties must be set. If these properties have not been set, you will be prompted for them and your Git configuration will be updated.
36
+
37
+ | Name | Description
38
+ | ---- | -----------
39
+ | `pivotal.api-token` | Your Pivotal Tracker API Token. This can be found in [your profile][profile] and should be set globally.
40
+ | `pivotal.project-id` | The Pivotal Tracker project id for the repository your are working in. This can be found in the project's URL and should be set.
41
+ | `gitflow.branch.master` | The Git-flow master branch name. If you've used Git-flow before this will already be set up. Otherwise this is the branch considered 'production'.
42
+ | `gitflow.branch.development` | The Git-flow development branch name. The branch that is commonly used for your development.
43
+ | `gitflow.prefix.feature` | Git-flow feature branch name prefix.
44
+ | `gitflow.prefix.hotfix` | Git-flow hotfix branch name prefix.
45
+ | `gitflow.prefix.feature` | Git-flow feature branch name prefix.
46
+ | `gitflow.prefix.release` | Git-flow release branch name prefix.
47
+
48
+ [profile]: https://www.pivotaltracker.com/profile
49
+
50
+
51
+ ### Git Server
52
+ In order to take advantage of automatic issue completion, the [Pivotal Tracker Source Code Integration][integration] must be enabled. If you are using GitHub, this integration is easy to enable by navgating to your project's 'Service Hooks' settings and configuring it with the proper credentials.
53
+
54
+ [integration]: https://www.pivotaltracker.com/help/integrations?version=v3#scm
55
+
56
+
57
+ ## Commands
58
+
59
+ ### `git start [ type | story-id ]`
60
+ This command starts a story by creating a Git branch and changing the story's state to `started`. This command can be run in three ways. First it can be run specifying the id of the story that you want to start.
61
+
62
+ ```plain
63
+ $ git start 12345678
64
+ ```
65
+
66
+ The second way to run the command is by specyifying the type of story that you would like to start. In this case it will then offer you the first five stories (based on the backlog's order) of that type to choose from.
67
+
68
+ ```plain
69
+ $ git start feature
70
+
71
+ 1. Lorem ipsum dolor sit amet, consectetur adipiscing elit
72
+ 2. Pellentesque sit amet ante eu tortor rutrum pharetra
73
+ 3. Ut at purus dolor, vel ultricies metus
74
+ 4. Duis egestas elit et leo ultrices non fringilla ante facilisis
75
+ 5. Ut ut nunc neque, quis auctor mauris
76
+ Choose story to start:
77
+ ```
78
+
79
+ Finally the command can be run without specifying anything. In this case, it will then offer the first five stories (based on the backlog's order) of any type to choose from.
80
+
81
+ ```plain
82
+ $ git start
83
+
84
+ 1. FEATURE Donec convallis leo mi, dictum ornare sem
85
+ 2. CHORE Sed et magna lectus, sed auctor purus
86
+ 3. FEATURE In a nunc et enim tincidunt interdum vitae et risus
87
+ 4. FEATURE Fusce facilisis varius lorem, at tristique sem faucibus in
88
+ 5. BUG Donec iaculis ante neque, ut tempus augue
89
+ Choose story to start:
90
+ ```
91
+
92
+ Once a story has been selected by one of the three methods, the command then prompts for the name of the branch to create.
93
+
94
+ ```plain
95
+ $ git start 12345678
96
+ Title: Lorem ipsum dolor sit amet, consectetur adipiscing elitattributes
97
+ Description: Ut consequat sapien ut erat volutpat egestas. Integer venenatis lacinia facilisis.
98
+
99
+ Enter branch name (12345678-<branch-name>):
100
+ ```
101
+
102
+ The value entered here will be prepended with the story id such that the branch name is `<story-id>-<branch-name>`. This branch is then created and checked out.
103
+
104
+ If it doesn't exist already, a `prepare-commit-msg` commit hook is added to your repository. This commit hook augments the existing commit messsage pattern by appending the story id to the message automatically.
105
+
106
+ ```plain
107
+
108
+ [#12345678]
109
+ # Please enter the commit message for your changes. Lines starting
110
+ # with '#' will be ignored, and an empty message aborts the commit.
111
+ # On branch 12345678-lorem-ipsum
112
+ # Changes to be committed:
113
+ # (use "git reset HEAD <file>..." to unstage)
114
+ #
115
+ # new file: dolor.txt
116
+ #
117
+ ```
118
+
119
+ ### `git finish [--no-complete]`
120
+ This command finishes a story by merging and cleaning up its branch and then pushing the changes to a remote server. This command can be run in two ways. First it can be run without the `--no-complete` option.
121
+
122
+ ```plain
123
+ $ git finish
124
+ Checking for trivial merge from 12345678-lorem-ipsum to master... OK
125
+ Merging 12345678-lorem-ipsum to master... OK
126
+ Deleting 12345678-lorem-ipsum... OK
127
+ Pushing to origin... OK
128
+ ```
129
+
130
+ The command checks that it will be able to do a trivial merge from the development branch to the target branch before it does anything. The check has the following constraints
131
+
132
+ 1. The local repository must be up to date with the remote repository (e.g. `origin`)
133
+ 2. The local merge target branch (e.g. `master`) must be up to date with the remote merge target branch (e.g. `origin/master`)
134
+ 3. The common ancestor (i.e. the branch point) of the development branch (e.g. `12345678-lorem-ipsum`) must be tip of the local merge target branch (e.g. `master`)
135
+
136
+ If all of these conditions are met, the development branch will be merged into the target branch with a message of:
137
+
138
+ ```plain
139
+ Merge 12345678-lorem-ipsum to master
140
+
141
+ [Completes #12345678]
142
+ ```
143
+
144
+ The second way is with the `--no-complete` option specified. In this case `finish` performs the same actions except the `Completes`... statement in the commit message will be supressed.
145
+
146
+ ```plain
147
+ Merge 12345678-lorem-ipsum to master
148
+
149
+ [#12345678]
150
+ ```
151
+
152
+ After merging, the development branch is deleted and the changes are pushed to the remote repository.
153
+
154
+ ### `git release [story-id]`
155
+ This command creates a release for a story. It does this by updating the version string in the project and creating a tag. This command can be run in two ways. First it can be run specifying the release that you want to create.
156
+
157
+ ```plain
158
+ $ git release 12345678
159
+ ```
160
+ The other way the command can be run without specifying anything. In this case, it will select the first release story (based on the backlog's order).
161
+
162
+ ```plain
163
+ $ git release
164
+ Title: Lorem ipsum dolor sit amet, consectetur adipiscing elitattributes
165
+ ```
166
+
167
+ Once a story has been selected by one of the two methods, the command then prompts for the release version and next development version.
168
+
169
+ ```plain
170
+ $ git release
171
+ Title: Lorem ipsum dolor sit amet, consectetur adipiscing elitattributes
172
+
173
+ Enter release version (current: 1.0.0.BUILD-SNAPSHOT): 1.0.0.M1
174
+ Enter next development version (current: 1.0.0.BUILD-SNAPSHOT): 1.1.0.BUILD-SNAPSHOT
175
+ Creating tag v1.0.0.M1... OK
176
+ Pushing to origin... OK
177
+ ```
178
+
179
+ Once these have been entered, the version string for the current project is updated to the release version and a tag is created. Then the version string for the current project is updated to the next development version and a new commit along the original branch is created. Finally the tag and changes are pushed to the remote sever.
180
+
181
+ Version update is currently supported for the following kinds of projects. If you do not see a project type that you would like supported, please open an issue or submit a pull request.
182
+
183
+ * Gradle
data/bin/git-finish ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby -U
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'github_pivotal_flow'
5
+
6
+ exit GithubPivotalFlow::Finish.new(STDIN, STDOUT, *ARGV).run!
data/bin/git-start ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby -U
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'github_pivotal_flow'
5
+
6
+ exit GithubPivotalFlow::Start.new(STDIN, STDOUT, *ARGV).run!
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+
3
+ class Object
4
+ # An object is blank if it's false, empty, or a whitespace string.
5
+ # For example, '', ' ', +nil+, [], and {} are all blank.
6
+ #
7
+ # This simplifies:
8
+ #
9
+ # if address.nil? || address.empty?
10
+ #
11
+ # ...to:
12
+ #
13
+ # if address.blank?
14
+ def blank?
15
+ respond_to?(:empty?) ? empty? : !self
16
+ end
17
+
18
+ # An object is present if it's not <tt>blank?</tt>.
19
+ def present?
20
+ !blank?
21
+ end
22
+
23
+ # Returns object if it's <tt>present?</tt> otherwise returns +nil+.
24
+ # <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
25
+ #
26
+ # This is handy for any representation of objects where blank is the same
27
+ # as not present at all. For example, this simplifies a common check for
28
+ # HTTP POST/query parameters:
29
+ #
30
+ # state = params[:state] if params[:state].present?
31
+ # country = params[:country] if params[:country].present?
32
+ # region = state || country || 'US'
33
+ #
34
+ # ...becomes:
35
+ #
36
+ # region = params[:state].presence || params[:country].presence || 'US'
37
+ def presence
38
+ self if present?
39
+ end
40
+ end
41
+
42
+ class NilClass
43
+ # +nil+ is blank:
44
+ #
45
+ # nil.blank? # => true
46
+ def blank?
47
+ true
48
+ end
49
+ end
50
+
51
+ class FalseClass
52
+ # +false+ is blank:
53
+ #
54
+ # false.blank? # => true
55
+ def blank?
56
+ true
57
+ end
58
+ end
59
+
60
+ class TrueClass
61
+ # +true+ is not blank:
62
+ #
63
+ # true.blank? # => false
64
+ def blank?
65
+ false
66
+ end
67
+ end
68
+
69
+ class Array
70
+ # An array is blank if it's empty:
71
+ #
72
+ # [].blank? # => true
73
+ # [1,2,3].blank? # => false
74
+ alias_method :blank?, :empty?
75
+ end
76
+
77
+ class Hash
78
+ # A hash is blank if it's empty:
79
+ #
80
+ # {}.blank? # => true
81
+ # { key: 'value' }.blank? # => false
82
+ alias_method :blank?, :empty?
83
+ end
84
+
85
+ class String
86
+ # A string is blank if it's empty or contains whitespaces only:
87
+ #
88
+ # ''.blank? # => true
89
+ # ' '.blank? # => true
90
+ # ' '.blank? # => true
91
+ # ' something here '.blank? # => false
92
+ def blank?
93
+ self !~ /[^[:space:]]/
94
+ end
95
+ end
96
+
97
+ class Numeric #:nodoc:
98
+ # No number is blank:
99
+ #
100
+ # 1.blank? # => false
101
+ # 0.blank? # => false
102
+ def blank?
103
+ false
104
+ end
105
+ end
@@ -0,0 +1,31 @@
1
+ # Git Pivotal Tracker Integration
2
+ # Copyright (c) 2013 the original author or authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ $:.unshift(File.dirname(__FILE__))
17
+
18
+ require 'highline/import'
19
+ require 'pivotal-tracker'
20
+
21
+ require File.join('core_ext', 'object', 'blank')
22
+
23
+ require File.join('github_pivotal_flow', 'shell')
24
+ require File.join('github_pivotal_flow', 'git')
25
+ require File.join('github_pivotal_flow', 'project')
26
+ require File.join('github_pivotal_flow', 'configuration')
27
+ require File.join('github_pivotal_flow', 'github_api')
28
+ require File.join('github_pivotal_flow', 'story')
29
+ require File.join('github_pivotal_flow', 'command')
30
+ require File.join('github_pivotal_flow', 'start')
31
+ require File.join('github_pivotal_flow', 'finish')
@@ -0,0 +1,57 @@
1
+ # An abstract base class for all commands
2
+ # @abstract Subclass and override {#run} to implement command functionality
3
+ module GithubPivotalFlow
4
+ class Command
5
+
6
+ attr_reader :options, :configuration
7
+
8
+ # Common initialization functionality for all command classes. This
9
+ # enforces that:
10
+ # * the command is being run within a valid Git repository
11
+ # * the user has specified their Pivotal Tracker API token
12
+ # * all communication with Pivotal Tracker will be protected with SSL
13
+ # * the user has configured the project id for this repository
14
+ def initialize(*args)
15
+ @options = {}
16
+ args = parse_argv(*args)
17
+ @options[:args] = args
18
+
19
+ @repository_root = Git.repository_root
20
+ @configuration = Configuration.new
21
+
22
+ PivotalTracker::Client.token = @configuration.api_token
23
+ PivotalTracker::Client.use_ssl = true
24
+
25
+ @project = PivotalTracker::Project.find @configuration.project_id
26
+
27
+ # Make sure that all the git flow config options are set up
28
+ @configuration.development_branch
29
+ @configuration.master_branch
30
+ @configuration.feature_prefix
31
+ @configuration.hotfix_prefix
32
+ end
33
+
34
+ # The main entry point to the command's execution
35
+ # @abstract Override this method to implement command functionality
36
+ def run!
37
+ raise NotImplementedError
38
+ end
39
+
40
+ protected
41
+
42
+ def parse_argv(*args)
43
+ OptionParser.new do |opts|
44
+ opts.banner = "Usage: git start <feature|chore|bug|story_id> | git finish"
45
+ opts.on("-t", "--api-token=", "Pivotal Tracker API key") { |k| options[:api_token] = k }
46
+ opts.on("-p", "--project-id=", "Pivotal Tracker project id") { |p| options[:project_id] = p }
47
+ opts.on("-n", "--full-name=", "Your Pivotal Tracker full name") { |n| options[:full_name] = n }
48
+ opts.on_tail("-h", "--help", "This usage guide") { put opts.to_s; exit 0 }
49
+ end.parse!(args)
50
+ end
51
+
52
+ def current_branch_name
53
+ Git.branch_name
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,251 @@
1
+ require 'highline/import'
2
+ require 'uri'
3
+
4
+ module GithubPivotalFlow
5
+ # A class that exposes configuration that commands can use
6
+ class Configuration
7
+ def initialize
8
+ @github_password_cache = {}
9
+ end
10
+
11
+ # Returns the user's Pivotal Tracker API token. If this token has not been
12
+ # configured, prompts the user for the value. The value is checked for in
13
+ # the _inherited_ Git configuration, but is stored in the _global_ Git
14
+ # configuration so that it can be used across multiple repositories.
15
+ #
16
+ # @return [String] The user's Pivotal Tracker API token
17
+ def api_token
18
+ api_token = Git.get_config KEY_API_TOKEN, :inherited
19
+
20
+ if api_token.empty?
21
+ api_token = ask('Pivotal API Token (found at https://www.pivotaltracker.com/profile): ').strip
22
+ Git.set_config KEY_API_TOKEN, api_token, :global
23
+ puts
24
+ end
25
+
26
+ api_token
27
+ end
28
+
29
+ # Returns the Pivotal Tracker project id for this repository. If this id
30
+ # has not been configuration, prompts the user for the value. The value is
31
+ # checked for in the _inherited_ Git configuration, but is stored in the
32
+ # _local_ Git configuration so that it is specific to this repository.
33
+ #
34
+ # @return [String] The repository's Pivotal Tracker project id
35
+ def project_id
36
+ project_id = Git.get_config KEY_PROJECT_ID, :inherited
37
+
38
+ if project_id.empty?
39
+ project_id = choose do |menu|
40
+ menu.prompt = 'Choose project associated with this repository: '
41
+
42
+ PivotalTracker::Project.all.sort_by { |project| project.name }.each do |project|
43
+ menu.choice(project.name) { project.id }
44
+ end
45
+ end
46
+
47
+ Git.set_config KEY_PROJECT_ID, project_id, :local
48
+ puts
49
+ end
50
+
51
+ project_id
52
+ end
53
+
54
+ # Returns the story associated with the current development branch
55
+ #
56
+ # @param [PivotalTracker::Project] project the project the story belongs to
57
+ # @return [PivotalTracker::Story] the story associated with the current development branch
58
+ def story(project)
59
+ story_id = Git.get_config(KEY_STORY_ID, :branch)
60
+ Story.new(project.stories.find(story_id.to_i), :branch_name => Git.current_branch)
61
+ end
62
+
63
+ # Stores the story associated with the current development branch
64
+ #
65
+ # @param [PivotalTracker::Story] story the story associated with the current development branch
66
+ # @return [void]
67
+ def story=(story)
68
+ Git.set_config KEY_STORY_ID, story.id, :branch
69
+ end
70
+
71
+ def feature_prefix
72
+ feature_prefix = Git.get_config KEY_FEATURE_PREFIX, :inherited
73
+
74
+ if feature_prefix.empty?
75
+ feature_prefix = ask('Please enter your git-flow feature branch prefix: [feature]').strip
76
+ feature_prefix = 'feature' if feature_prefix.blank?
77
+ Git.set_config KEY_FEATURE_PREFIX, feature_prefix, :local
78
+ puts
79
+ end
80
+
81
+ feature_prefix
82
+ end
83
+
84
+ def hotfix_prefix
85
+ hotfix_prefix = Git.get_config KEY_HOTFIX_PREFIX, :inherited
86
+
87
+ if hotfix_prefix.empty?
88
+ hotfix_prefix = ask('Please enter your git-flow hotfix branch prefix: [hotfix]').strip
89
+ hotfix_prefix = 'hotfix' if hotfix_prefix.blank?
90
+ Git.set_config KEY_HOTFIX_PREFIX, hotfix_prefix, :local
91
+ puts
92
+ end
93
+
94
+ hotfix_prefix
95
+ end
96
+
97
+ def release_prefix
98
+ release_prefix = Git.get_config KEY_RELEASE_PREFIX, :inherited
99
+
100
+ if release_prefix.empty?
101
+ release_prefix = ask('Please enter your git-flow release branch prefix: [release]').strip
102
+ release_prefix = 'release' if release_prefix.blank?
103
+ Git.set_config KEY_RELEASE_PREFIX, release_prefix, :local
104
+ puts
105
+ end
106
+
107
+ release_prefix
108
+ end
109
+
110
+ def development_branch
111
+ development_branch = Git.get_config KEY_DEVELOPMENT_BRANCH, :inherited
112
+
113
+ if development_branch.empty?
114
+ development_branch = ask('Please enter your git-flow development branch name: [development]').strip
115
+ development_branch = 'development' if development_branch.nil? || development_branch.empty?
116
+ Git.set_config KEY_DEVELOPMENT_BRANCH, development_branch, :local
117
+ puts
118
+ end
119
+ Git.ensure_branch_exists(development_branch)
120
+
121
+ development_branch
122
+ end
123
+
124
+ def master_branch
125
+ master_branch = Git.get_config KEY_MASTER_BRANCH, :inherited
126
+
127
+ if master_branch.blank?
128
+ master_branch = ask('Please enter your git-flow production branch name: [master]').strip
129
+ master_branch = 'master' if master_branch.nil? || master_branch.empty?
130
+ Git.set_config KEY_MASTER_BRANCH, master_branch, :local
131
+ puts
132
+ end
133
+ Git.ensure_branch_exists(master_branch)
134
+
135
+ master_branch
136
+ end
137
+
138
+ def github_project
139
+ @github_project ||= Project.from_url(URI(Git.get_config("remote.#{Git.get_remote}.url")))
140
+ end
141
+
142
+ def github_username(host)
143
+ return ENV['GITHUB_USER'] unless ENV['GITHUB_USER'].to_s.blank?
144
+ github_username = Git.get_config KEY_GITHUB_USERNAME, :inherited
145
+ if github_username.blank?
146
+ github_username = ask('Github username: ').strip
147
+ Git.set_config KEY_GITHUB_USERNAME, github_username, :local
148
+ end
149
+ github_username
150
+ end
151
+
152
+ def github_username=(github_username)
153
+ Git.set_config KEY_GITHUB_USERNAME, github_username, :local unless github_username.blank?
154
+ end
155
+
156
+ def github_password(host, user)
157
+ return ENV['GITHUB_PASSWORD'] unless ENV['GITHUB_PASSWORD'].to_s.empty?
158
+ @github_password_cache["#{user}"] ||= ask_password(user)
159
+ end
160
+
161
+ def github_api_token(host, user)
162
+ github_token = Git.get_config KEY_GITHUB_API_TOKEN, :inherited
163
+ if github_token.blank?
164
+ github_token = yield
165
+ Git.set_config KEY_GITHUB_API_TOKEN, github_token, :local
166
+ end
167
+ github_token
168
+ end
169
+
170
+ # special prompt that has hidden input
171
+ def ask_password(user)
172
+ print "Github password for #{user} (never stored): "
173
+ if $stdin.tty?
174
+ password = askpass
175
+ puts ''
176
+ password
177
+ else
178
+ # in testing
179
+ $stdin.gets.chomp
180
+ end
181
+ rescue Interrupt
182
+ abort
183
+ end
184
+
185
+ def ask_auth_code
186
+ print "two-factor authentication code: "
187
+ $stdin.gets.chomp
188
+ rescue Interrupt
189
+ abort
190
+ end
191
+
192
+ def askpass
193
+ noecho $stdin do |input|
194
+ input.gets.chomp
195
+ end
196
+ end
197
+
198
+ def noecho io
199
+ require 'io/console'
200
+ io.noecho { yield io }
201
+ rescue LoadError
202
+ fallback_noecho io
203
+ end
204
+
205
+ def fallback_noecho io
206
+ tty_state = `stty -g 2>#{NULL}`
207
+ system 'stty raw -echo -icanon isig' if $?.success?
208
+ pass = ''
209
+ while char = getbyte(io) and !(char == 13 or char == 10)
210
+ if char == 127 or char == 8
211
+ pass[-1,1] = '' unless pass.empty?
212
+ else
213
+ pass << char.chr
214
+ end
215
+ end
216
+ pass
217
+ ensure
218
+ system "stty #{tty_state}" unless tty_state.empty?
219
+ end
220
+
221
+ def getbyte(io)
222
+ if io.respond_to?(:getbyte)
223
+ io.getbyte
224
+ else
225
+ # In Ruby <= 1.8.6, getc behaved the same
226
+ io.getc
227
+ end
228
+ end
229
+
230
+ def proxy_uri(with_ssl)
231
+ env_name = "HTTP#{with_ssl ? 'S' : ''}_PROXY"
232
+ if proxy = ENV[env_name] || ENV[env_name.downcase] and !proxy.empty?
233
+ proxy = "http://#{proxy}" unless proxy.include? '://'
234
+ URI.parse proxy
235
+ end
236
+ end
237
+
238
+ private
239
+
240
+ KEY_API_TOKEN = 'pivotal.api-token'.freeze
241
+ KEY_PROJECT_ID = 'pivotal.project-id'.freeze
242
+ KEY_STORY_ID = 'pivotal-story-id'.freeze
243
+ KEY_FEATURE_PREFIX = 'gitflow.prefix.feature'.freeze
244
+ KEY_HOTFIX_PREFIX = 'gitflow.prefix.hotfix'.freeze
245
+ KEY_RELEASE_PREFIX = 'gitflow.prefix.release'.freeze
246
+ KEY_DEVELOPMENT_BRANCH = 'gitflow.branch.develop'.freeze
247
+ KEY_MASTER_BRANCH = 'gitflow.branch.master'.freeze
248
+ KEY_GITHUB_USERNAME = 'github.username'.freeze
249
+ KEY_GITHUB_API_TOKEN = 'github.api-token'.freeze
250
+ end
251
+ end