spire-git-pivotal-tracker 1.0.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/LICENSE +202 -0
- data/NOTICE +2 -0
- data/README.md +176 -0
- data/bin/git-finish +19 -0
- data/bin/git-release +19 -0
- data/bin/git-start +19 -0
- data/lib/git-pivotal-tracker-integration/command/base.rb +47 -0
- data/lib/git-pivotal-tracker-integration/command/command.rb +20 -0
- data/lib/git-pivotal-tracker-integration/command/configuration.rb +109 -0
- data/lib/git-pivotal-tracker-integration/command/finish.rb +57 -0
- data/lib/git-pivotal-tracker-integration/command/prepare-commit-msg.sh +26 -0
- data/lib/git-pivotal-tracker-integration/command/release.rb +56 -0
- data/lib/git-pivotal-tracker-integration/command/start.rb +67 -0
- data/lib/git-pivotal-tracker-integration/util/git.rb +263 -0
- data/lib/git-pivotal-tracker-integration/util/shell.rb +36 -0
- data/lib/git-pivotal-tracker-integration/util/story.rb +124 -0
- data/lib/git-pivotal-tracker-integration/util/util.rb +20 -0
- data/lib/git-pivotal-tracker-integration/version-update/gradle.rb +64 -0
- data/lib/git-pivotal-tracker-integration/version-update/version_update.rb +20 -0
- data/lib/git_pivotal_tracker_integration.rb +18 -0
- data/spec/git-pivotal-tracker-integration/command/base_spec.rb +38 -0
- data/spec/git-pivotal-tracker-integration/command/configuration_spec.rb +91 -0
- data/spec/git-pivotal-tracker-integration/command/finish_spec.rb +45 -0
- data/spec/git-pivotal-tracker-integration/command/release_spec.rb +57 -0
- data/spec/git-pivotal-tracker-integration/command/start_spec.rb +54 -0
- data/spec/git-pivotal-tracker-integration/util/git_spec.rb +235 -0
- data/spec/git-pivotal-tracker-integration/util/shell_spec.rb +52 -0
- data/spec/git-pivotal-tracker-integration/util/story_spec.rb +137 -0
- data/spec/git-pivotal-tracker-integration/version-update/gradle_spec.rb +74 -0
- metadata +215 -0
@@ -0,0 +1,20 @@
|
|
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
|
+
require 'git_pivotal_tracker_integration'
|
17
|
+
|
18
|
+
# A module encapsulating the commands for the project
|
19
|
+
module GitPivotalTrackerIntegration::Command
|
20
|
+
end
|
@@ -0,0 +1,109 @@
|
|
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
|
+
require 'git-pivotal-tracker-integration/command/command'
|
17
|
+
require 'git-pivotal-tracker-integration/util/git'
|
18
|
+
require 'highline/import'
|
19
|
+
require 'pivotal-tracker'
|
20
|
+
require 'github_api'
|
21
|
+
|
22
|
+
# A class that exposes configuration that commands can use
|
23
|
+
class GitPivotalTrackerIntegration::Command::Configuration
|
24
|
+
|
25
|
+
# Returns the user's Pivotal Tracker API token. If this token has not been
|
26
|
+
# configured, prompts the user for the value. The value is checked for in
|
27
|
+
# the _inherited_ Git configuration, but is stored in the _global_ Git
|
28
|
+
# configuration so that it can be used across multiple repositories.
|
29
|
+
#
|
30
|
+
# @return [String] The user's Pivotal Tracker API token
|
31
|
+
def api_token
|
32
|
+
api_token = GitPivotalTrackerIntegration::Util::Git.get_config KEY_API_TOKEN, :inherited
|
33
|
+
|
34
|
+
if api_token.empty?
|
35
|
+
api_token = ask('Pivotal API Token (found at https://www.pivotaltracker.com/profile): ').strip
|
36
|
+
GitPivotalTrackerIntegration::Util::Git.set_config KEY_API_TOKEN, api_token, :global
|
37
|
+
puts
|
38
|
+
end
|
39
|
+
|
40
|
+
api_token
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the Pivotal Tracker project id for this repository. If this id
|
44
|
+
# has not been configuration, prompts the user for the value. The value is
|
45
|
+
# checked for in the _inherited_ Git configuration, but is stored in the
|
46
|
+
# _local_ Git configuration so that it is specific to this repository.
|
47
|
+
#
|
48
|
+
# @return [String] The repository's Pivotal Tracker project id
|
49
|
+
def project_id
|
50
|
+
project_id = GitPivotalTrackerIntegration::Util::Git.get_config KEY_PROJECT_ID, :inherited
|
51
|
+
|
52
|
+
if project_id.empty?
|
53
|
+
project_id = choose do |menu|
|
54
|
+
menu.prompt = 'Choose project associated with this repository: '
|
55
|
+
|
56
|
+
PivotalTracker::Project.all.sort_by { |project| project.name }.each do |project|
|
57
|
+
menu.choice(project.name) { project.id }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
GitPivotalTrackerIntegration::Util::Git.set_config KEY_PROJECT_ID, project_id, :local
|
62
|
+
puts
|
63
|
+
end
|
64
|
+
|
65
|
+
project_id
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the story associated with the current development branch
|
69
|
+
#
|
70
|
+
# @param [PivotalTracker::Project] project the project the story belongs to
|
71
|
+
# @return [PivotalTracker::Story] the story associated with the current development branch
|
72
|
+
def story(project)
|
73
|
+
story_id = GitPivotalTrackerIntegration::Util::Git.get_config KEY_STORY_ID, :branch
|
74
|
+
project.stories.find story_id.to_i
|
75
|
+
end
|
76
|
+
|
77
|
+
# Stores the story associated with the current development branch
|
78
|
+
#
|
79
|
+
# @param [PivotalTracker::Story] story the story associated with the current development branch
|
80
|
+
# @return [void]
|
81
|
+
def story=(story)
|
82
|
+
GitPivotalTrackerIntegration::Util::Git.set_config KEY_STORY_ID, story.id, :branch
|
83
|
+
end
|
84
|
+
|
85
|
+
def github
|
86
|
+
token = GitPivotalTrackerIntegration::Util::Git.get_config GITHUB_API_OAUTH_TOKEN
|
87
|
+
|
88
|
+
if (token.empty?)
|
89
|
+
token = ask("Github OAuth Token (help.github.com/articles/creating-an-access-token-for-command-line-use): ").strip
|
90
|
+
GitPivotalTrackerIntegration::Util::Git.set_config(GITHUB_API_OAUTH_TOKEN, token, :local)
|
91
|
+
end
|
92
|
+
|
93
|
+
repo = GitPivotalTrackerIntegration::Util::Git.repo_name
|
94
|
+
|
95
|
+
::Github.new(:oauth_token => token, :org => "spire-inc", :repo => repo, :user => "spire-inc")
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
KEY_API_TOKEN = 'pivotal.api-token'.freeze
|
101
|
+
|
102
|
+
KEY_PROJECT_ID = 'pivotal.project-id'.freeze
|
103
|
+
|
104
|
+
KEY_STORY_ID = 'pivotal-story-id'.freeze
|
105
|
+
|
106
|
+
GITHUB_API_OAUTH_TOKEN = 'workflow.github.oauth'.freeze
|
107
|
+
GITHUB_API_REPO_TOKEN = 'workflow.github.repo'.freeze
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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
|
+
require 'git-pivotal-tracker-integration/command/base'
|
17
|
+
require 'git-pivotal-tracker-integration/command/command'
|
18
|
+
require 'git-pivotal-tracker-integration/util/git'
|
19
|
+
|
20
|
+
# The class that encapsulates finishing a Pivotal Tracker Story
|
21
|
+
class GitPivotalTrackerIntegration::Command::Finish < GitPivotalTrackerIntegration::Command::Base
|
22
|
+
|
23
|
+
# Finishes a Pivotal Tracker story by doing the following steps:
|
24
|
+
# * Check that the pending merge will be trivial
|
25
|
+
# * Merge the development branch into the root branch
|
26
|
+
# * Delete the development branch
|
27
|
+
# * Push changes to remote
|
28
|
+
#
|
29
|
+
# @return [void]
|
30
|
+
def run(argument)
|
31
|
+
GitPivotalTrackerIntegration::Util::Git.verify_uncommitted_changes!
|
32
|
+
|
33
|
+
github = @configuration.github
|
34
|
+
|
35
|
+
story = @configuration.story(@project)
|
36
|
+
|
37
|
+
branch_name = GitPivotalTrackerIntegration::Util::Git.branch_name
|
38
|
+
|
39
|
+
print 'Creating PR on Github... '
|
40
|
+
pr = github.pull_requests.create(
|
41
|
+
user: github.user,
|
42
|
+
repo: github.repo,
|
43
|
+
base: GitPivotalTrackerIntegration::Util::Git.root_branch,
|
44
|
+
head: branch_name,
|
45
|
+
title: "Fixing #{branch_name}",
|
46
|
+
body: "#{story.name}\n#{story.description}\nPivotal Task: #{story.url}"
|
47
|
+
)
|
48
|
+
puts 'OK'
|
49
|
+
print 'Finishing story on Pivotal Tracker... '
|
50
|
+
story.update(
|
51
|
+
:current_state => 'finished',
|
52
|
+
:owned_by => GitPivotalTrackerIntegration::Util::Git.get_config('user.name')
|
53
|
+
)
|
54
|
+
puts 'OK'
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
# Git Pivotal Tracker Integration
|
3
|
+
# Copyright (c) 2013 the original author or authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
CURRENT_BRANCH=$(git branch | grep "*" | sed "s/* //")
|
18
|
+
STORY_ID=$(git config branch.$CURRENT_BRANCH.pivotal-story-id)
|
19
|
+
|
20
|
+
if [[ $2 != "commit" && -n $STORY_ID ]]; then
|
21
|
+
ORIG_MSG_FILE="$1"
|
22
|
+
TEMP=$(mktemp /tmp/git-XXXXX)
|
23
|
+
|
24
|
+
(printf "\n\n[#$STORY_ID]" ; cat "$1") > "$TEMP"
|
25
|
+
cat "$TEMP" > "$ORIG_MSG_FILE"
|
26
|
+
fi
|
@@ -0,0 +1,56 @@
|
|
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
|
+
require 'git-pivotal-tracker-integration/command/base'
|
17
|
+
require 'git-pivotal-tracker-integration/command/command'
|
18
|
+
require 'git-pivotal-tracker-integration/util/git'
|
19
|
+
require 'git-pivotal-tracker-integration/util/story'
|
20
|
+
require 'git-pivotal-tracker-integration/version-update/gradle'
|
21
|
+
|
22
|
+
# The class that encapsulates releasing a Pivotal Tracker Story
|
23
|
+
class GitPivotalTrackerIntegration::Command::Release < GitPivotalTrackerIntegration::Command::Base
|
24
|
+
|
25
|
+
# Releases a Pivotal Tracker story by doing the following steps:
|
26
|
+
# * Update the version to the release version
|
27
|
+
# * Create a tag for the release version
|
28
|
+
# * Update the version to the new development version
|
29
|
+
# * Push tag and changes to remote
|
30
|
+
#
|
31
|
+
# @param [String, nil] filter a filter for selecting the release to start. This
|
32
|
+
# filter can be either:
|
33
|
+
# * a story id
|
34
|
+
# * +nil+
|
35
|
+
# @return [void]
|
36
|
+
def run(filter)
|
37
|
+
story = GitPivotalTrackerIntegration::Util::Story.select_story(@project, filter.nil? ? 'release' : filter, 1)
|
38
|
+
GitPivotalTrackerIntegration::Util::Story.pretty_print story
|
39
|
+
|
40
|
+
updater = [
|
41
|
+
GitPivotalTrackerIntegration::VersionUpdate::Gradle.new(@repository_root)
|
42
|
+
].find { |candidate| candidate.supports? }
|
43
|
+
|
44
|
+
current_version = updater.current_version
|
45
|
+
release_version = ask("Enter release version (current: #{current_version}): ")
|
46
|
+
next_version = ask("Enter next development version (current: #{current_version}): ")
|
47
|
+
|
48
|
+
updater.update_version release_version
|
49
|
+
GitPivotalTrackerIntegration::Util::Git.create_release_tag release_version, story
|
50
|
+
updater.update_version next_version
|
51
|
+
GitPivotalTrackerIntegration::Util::Git.create_commit "#{next_version} Development", story
|
52
|
+
|
53
|
+
GitPivotalTrackerIntegration::Util::Git.push GitPivotalTrackerIntegration::Util::Git.branch_name, "v#{release_version}"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,67 @@
|
|
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
|
+
require 'git-pivotal-tracker-integration/command/base'
|
17
|
+
require 'git-pivotal-tracker-integration/command/command'
|
18
|
+
require 'git-pivotal-tracker-integration/util/git'
|
19
|
+
require 'git-pivotal-tracker-integration/util/story'
|
20
|
+
require 'pivotal-tracker'
|
21
|
+
|
22
|
+
# The class that encapsulates starting a Pivotal Tracker Story
|
23
|
+
class GitPivotalTrackerIntegration::Command::Start < GitPivotalTrackerIntegration::Command::Base
|
24
|
+
|
25
|
+
# Starts a Pivotal Tracker story by doing the following steps:
|
26
|
+
# * Create a branch
|
27
|
+
# * Add default commit hook
|
28
|
+
# * Start the story on Pivotal Tracker
|
29
|
+
#
|
30
|
+
# @param [String, nil] filter a filter for selecting the story to start. This
|
31
|
+
# filter can be either:
|
32
|
+
# * a story id
|
33
|
+
# * a story type (feature, bug, chore)
|
34
|
+
# * +nil+
|
35
|
+
# @return [void]
|
36
|
+
def run(filter)
|
37
|
+
story = GitPivotalTrackerIntegration::Util::Story.select_story @project, filter
|
38
|
+
|
39
|
+
GitPivotalTrackerIntegration::Util::Story.pretty_print story
|
40
|
+
|
41
|
+
development_branch_name = development_branch_name story
|
42
|
+
GitPivotalTrackerIntegration::Util::Git.create_branch development_branch_name
|
43
|
+
@configuration.story = story
|
44
|
+
|
45
|
+
GitPivotalTrackerIntegration::Util::Git.add_hook 'prepare-commit-msg', File.join(File.dirname(__FILE__), 'prepare-commit-msg.sh')
|
46
|
+
|
47
|
+
start_on_tracker story
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def development_branch_name(story)
|
53
|
+
branch_name = "#{story.id}-" + ask("Enter branch name (#{story.id}-<branch-name>): ")
|
54
|
+
puts
|
55
|
+
branch_name
|
56
|
+
end
|
57
|
+
|
58
|
+
def start_on_tracker(story)
|
59
|
+
print 'Starting story on Pivotal Tracker... '
|
60
|
+
story.update(
|
61
|
+
:current_state => 'started',
|
62
|
+
:owned_by => GitPivotalTrackerIntegration::Util::Git.get_config('user.name')
|
63
|
+
)
|
64
|
+
puts 'OK'
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,263 @@
|
|
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
|
+
require 'git-pivotal-tracker-integration/util/shell'
|
17
|
+
require 'git-pivotal-tracker-integration/util/util'
|
18
|
+
|
19
|
+
# Utilities for dealing with Git
|
20
|
+
class GitPivotalTrackerIntegration::Util::Git
|
21
|
+
|
22
|
+
# Adds a Git hook to the current repository
|
23
|
+
#
|
24
|
+
# @param [String] name the name of the hook to add
|
25
|
+
# @param [String] source the file to use as the source for the created hook
|
26
|
+
# @param [Boolean] overwrite whether to overwrite the hook if it already exists
|
27
|
+
# @return [void]
|
28
|
+
def self.add_hook(name, source, overwrite = false)
|
29
|
+
hooks_directory = File.join repository_root, '.git', 'hooks'
|
30
|
+
hook = File.join hooks_directory, name
|
31
|
+
|
32
|
+
if overwrite || !File.exist?(hook)
|
33
|
+
print "Creating Git hook #{name}... "
|
34
|
+
|
35
|
+
FileUtils.mkdir_p hooks_directory
|
36
|
+
File.open(source, 'r') do |input|
|
37
|
+
File.open(hook, 'w') do |output|
|
38
|
+
output.write(input.read)
|
39
|
+
output.chmod(0755)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
puts 'OK'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the name of the currently checked out branch
|
48
|
+
#
|
49
|
+
# @return [String] the name of the currently checked out branch
|
50
|
+
def self.branch_name
|
51
|
+
GitPivotalTrackerIntegration::Util::Shell.exec('git branch').scan(/\* (.*)/)[0][0]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.root_branch
|
55
|
+
get_config KEY_ROOT_BRANCH, :branch
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.repo_name
|
59
|
+
GitPivotalTrackerIntegration::Util::Shell.exec('git config -l').scan(/spire\-inc\/(.*)\.git/)[0][0]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Creates a branch with a given +name+. First pulls the current branch to
|
63
|
+
# ensure that it is up to date and then creates and checks out the new
|
64
|
+
# branch. If specified, sets branch-specific properties that are passed in.
|
65
|
+
#
|
66
|
+
# @param [String] name the name of the branch to create
|
67
|
+
# @param [Boolean] print_messages whether to print messages
|
68
|
+
# @return [void]
|
69
|
+
def self.create_branch(name, print_messages = true)
|
70
|
+
root_branch = branch_name
|
71
|
+
root_remote = get_config KEY_REMOTE, :branch
|
72
|
+
|
73
|
+
if print_messages; print "Pulling #{root_branch}... " end
|
74
|
+
GitPivotalTrackerIntegration::Util::Shell.exec 'git pull --quiet --ff-only'
|
75
|
+
if print_messages; puts 'OK'
|
76
|
+
end
|
77
|
+
|
78
|
+
if print_messages; print "Creating and checking out #{name}... " end
|
79
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git checkout --quiet -b #{name}"
|
80
|
+
set_config KEY_ROOT_BRANCH, root_branch, :branch
|
81
|
+
set_config KEY_ROOT_REMOTE, root_remote, :branch
|
82
|
+
if print_messages; puts 'OK'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates a commit with a given message. The commit includes all change
|
87
|
+
# files.
|
88
|
+
#
|
89
|
+
# @param [String] message The commit message, which will be appended with
|
90
|
+
# +[#<story-id]+
|
91
|
+
# @param [PivotalTracker::Story] story the story associated with the current
|
92
|
+
# commit
|
93
|
+
# @return [void]
|
94
|
+
def self.create_commit(message, story)
|
95
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git commit --quiet --all --allow-empty --message \"#{message}\n\n[##{story.id}]\""
|
96
|
+
end
|
97
|
+
|
98
|
+
# Creates a tag with the given name. Before creating the tag, commits all
|
99
|
+
# outstanding changes with a commit message that reflects that these changes
|
100
|
+
# are for a release.
|
101
|
+
#
|
102
|
+
# @param [String] name the name of the tag to create
|
103
|
+
# @param [PivotalTracker::Story] story the story associated with the current
|
104
|
+
# tag
|
105
|
+
# @return [void]
|
106
|
+
def self.create_release_tag(name, story)
|
107
|
+
root_branch = branch_name
|
108
|
+
|
109
|
+
print "Creating tag v#{name}... "
|
110
|
+
|
111
|
+
create_branch RELEASE_BRANCH_NAME, false
|
112
|
+
create_commit "#{name} Release", story
|
113
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git tag v#{name}"
|
114
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git checkout --quiet #{root_branch}"
|
115
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git branch --quiet -D #{RELEASE_BRANCH_NAME}"
|
116
|
+
|
117
|
+
puts 'OK'
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.verify_uncommitted_changes!
|
121
|
+
result = `git diff --exit-code`
|
122
|
+
if $?.exitstatus != 0
|
123
|
+
abort "You have uncommitted changes!"
|
124
|
+
end
|
125
|
+
result = `git diff --staged --exit-code`
|
126
|
+
|
127
|
+
if $?.exitstatus != 0
|
128
|
+
abort "You have uncommitted staged changes!"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns a Git configuration value. This value is read using the +git
|
133
|
+
# config+ command. The scope of the value to read can be controlled with the
|
134
|
+
# +scope+ parameter.
|
135
|
+
#
|
136
|
+
# @param [String] key the key of the configuration to retrieve
|
137
|
+
# @param [:branch, :inherited] scope the scope to read the configuration from
|
138
|
+
# * +:branch+: equivalent to calling +git config branch.branch-name.key+
|
139
|
+
# * +:inherited+: equivalent to calling +git config key+
|
140
|
+
# @return [String] the value of the configuration
|
141
|
+
# @raise if the specified scope is not +:branch+ or +:inherited+
|
142
|
+
def self.get_config(key, scope = :inherited)
|
143
|
+
if :branch == scope
|
144
|
+
GitPivotalTrackerIntegration::Util::Shell.exec("git config branch.#{branch_name}.#{key}", false).strip
|
145
|
+
elsif :inherited == scope
|
146
|
+
GitPivotalTrackerIntegration::Util::Shell.exec("git config #{key}", false).strip
|
147
|
+
else
|
148
|
+
raise "Unable to get Git configuration for scope '#{scope}'"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Merges the current branch to its root branch and deletes the current branch
|
153
|
+
#
|
154
|
+
# @param [PivotalTracker::Story] story the story associated with the current branch
|
155
|
+
# @param [Boolean] no_complete whether to suppress the +Completes+ statement in the commit message
|
156
|
+
# @return [void]
|
157
|
+
def self.merge(story, no_complete)
|
158
|
+
development_branch = branch_name
|
159
|
+
root_branch = get_config KEY_ROOT_BRANCH, :branch
|
160
|
+
|
161
|
+
print "Merging #{development_branch} to #{root_branch}... "
|
162
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git checkout --quiet #{root_branch}"
|
163
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git merge --quiet --no-ff -m \"Merge #{development_branch} to #{root_branch}\n\n[#{no_complete ? '' : 'Completes '}##{story.id}]\" #{development_branch}"
|
164
|
+
puts 'OK'
|
165
|
+
|
166
|
+
print "Deleting #{development_branch}... "
|
167
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git branch --quiet -D #{development_branch}"
|
168
|
+
puts 'OK'
|
169
|
+
end
|
170
|
+
|
171
|
+
# Push changes to the remote of the current branch
|
172
|
+
#
|
173
|
+
# @param [String] refs the explicit references to push
|
174
|
+
# @return [void]
|
175
|
+
def self.push(*refs)
|
176
|
+
remote = get_config KEY_REMOTE, :branch
|
177
|
+
|
178
|
+
print "Pushing to #{remote}... "
|
179
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git push --quiet #{remote} " + refs.join(' ')
|
180
|
+
puts 'OK'
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the root path of the current Git repository. The root is
|
184
|
+
# determined by ascending the path hierarchy, starting with the current
|
185
|
+
# working directory (+Dir#pwd+), until a directory is found that contains a
|
186
|
+
# +.git/+ sub directory.
|
187
|
+
#
|
188
|
+
# @return [String] the root path of the Git repository
|
189
|
+
# @raise if the current working directory is not in a Git repository
|
190
|
+
def self.repository_root
|
191
|
+
repository_root = Dir.pwd
|
192
|
+
|
193
|
+
until Dir.entries(repository_root).any? { |child| File.directory?(child) && (child =~ /^.git$/) }
|
194
|
+
next_repository_root = File.expand_path('..', repository_root)
|
195
|
+
abort('Current working directory is not in a Git repository') unless repository_root != next_repository_root
|
196
|
+
repository_root = next_repository_root
|
197
|
+
end
|
198
|
+
|
199
|
+
repository_root
|
200
|
+
end
|
201
|
+
|
202
|
+
# Sets a Git configuration value. This value is set using the +git config+
|
203
|
+
# command. The scope of the set value can be controlled with the +scope+
|
204
|
+
# parameter.
|
205
|
+
#
|
206
|
+
# @param [String] key the key of configuration to store
|
207
|
+
# @param [String] value the value of the configuration to store
|
208
|
+
# @param [:branch, :global, :local] scope the scope to store the configuration value in.
|
209
|
+
# * +:branch+: equivalent to calling +git config --local branch.branch-name.key value+
|
210
|
+
# * +:global+: equivalent to calling +git config --global key value+
|
211
|
+
# * +:local+: equivalent to calling +git config --local key value+
|
212
|
+
# @return [void]
|
213
|
+
# @raise if the specified scope is not +:branch+, +:global+, or +:local+
|
214
|
+
def self.set_config(key, value, scope = :local)
|
215
|
+
if :branch == scope
|
216
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git config --local branch.#{branch_name}.#{key} #{value}"
|
217
|
+
elsif :global == scope
|
218
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git config --global #{key} #{value}"
|
219
|
+
elsif :local == scope
|
220
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git config --local #{key} #{value}"
|
221
|
+
else
|
222
|
+
raise "Unable to set Git configuration for scope '#{scope}'"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Checks whether merging the current branch back to its root branch would be
|
227
|
+
# a trivial merge. A trivial merge is defined as one where the net change
|
228
|
+
# of the merge would be the same as the net change of the branch being
|
229
|
+
# merged. The easiest way to ensure that a merge is trivial is to rebase a
|
230
|
+
# development branch onto the tip of its root branch.
|
231
|
+
#
|
232
|
+
# @return [void]
|
233
|
+
def self.trivial_merge?
|
234
|
+
development_branch = branch_name
|
235
|
+
|
236
|
+
print "Checking for trivial merge from #{development_branch} to #{root_branch}... "
|
237
|
+
|
238
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git checkout --quiet #{root_branch}"
|
239
|
+
GitPivotalTrackerIntegration::Util::Shell.exec 'git pull --quiet --ff-only'
|
240
|
+
GitPivotalTrackerIntegration::Util::Shell.exec "git checkout --quiet #{development_branch}"
|
241
|
+
|
242
|
+
root_tip = GitPivotalTrackerIntegration::Util::Shell.exec "git rev-parse #{root_branch}"
|
243
|
+
common_ancestor = GitPivotalTrackerIntegration::Util::Shell.exec "git merge-base #{root_branch} #{development_branch}"
|
244
|
+
|
245
|
+
if root_tip != common_ancestor
|
246
|
+
abort 'FAIL'
|
247
|
+
end
|
248
|
+
|
249
|
+
puts 'OK'
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
KEY_REMOTE = 'remote'.freeze
|
255
|
+
|
256
|
+
KEY_ROOT_BRANCH = 'root-branch'.freeze
|
257
|
+
|
258
|
+
KEY_ROOT_REMOTE = 'root-remote'.freeze
|
259
|
+
|
260
|
+
RELEASE_BRANCH_NAME = 'pivotal-tracker-release'.freeze
|
261
|
+
|
262
|
+
end
|
263
|
+
|