thegarage-gitx 1.5.5.pre1 → 2.0.0.pre1
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 +4 -4
- data/Guardfile +8 -0
- data/README.md +2 -0
- data/bin/git-buildtag +3 -2
- data/bin/git-cleanup +4 -2
- data/bin/git-integrate +3 -2
- data/bin/git-nuke +3 -2
- data/bin/git-release +3 -2
- data/bin/git-reviewrequest +3 -2
- data/bin/git-share +3 -2
- data/bin/git-start +3 -2
- data/bin/git-track +3 -2
- data/bin/git-update +3 -2
- data/lib/thegarage/gitx/cli/base_command.rb +74 -0
- data/lib/thegarage/gitx/cli/buildtag_command.rb +39 -0
- data/lib/thegarage/gitx/cli/cleanup_command.rb +36 -0
- data/lib/thegarage/gitx/cli/integrate_command.rb +40 -0
- data/lib/thegarage/gitx/cli/nuke_command.rb +64 -0
- data/lib/thegarage/gitx/cli/release_command.rb +31 -0
- data/lib/thegarage/gitx/cli/review_request_command.rb +203 -0
- data/lib/thegarage/gitx/cli/share_command.rb +17 -0
- data/lib/thegarage/gitx/cli/start_command.rb +31 -0
- data/lib/thegarage/gitx/cli/track_command.rb +16 -0
- data/lib/thegarage/gitx/cli/update_command.rb +23 -0
- data/lib/thegarage/gitx/version.rb +1 -1
- data/lib/thegarage/gitx.rb +0 -2
- data/spec/spec_helper.rb +3 -1
- data/spec/thegarage/gitx/cli/buildtag_command_spec.rb +71 -0
- data/spec/thegarage/gitx/cli/cleanup_command_spec.rb +38 -0
- data/spec/thegarage/gitx/cli/integrate_command_spec.rb +65 -0
- data/spec/thegarage/gitx/cli/nuke_command_spec.rb +105 -0
- data/spec/thegarage/gitx/cli/release_command_spec.rb +56 -0
- data/spec/thegarage/gitx/cli/review_request_command_spec.rb +188 -0
- data/spec/thegarage/gitx/cli/share_command_spec.rb +32 -0
- data/spec/thegarage/gitx/cli/start_command_spec.rb +61 -0
- data/spec/thegarage/gitx/cli/track_command_spec.rb +31 -0
- data/spec/thegarage/gitx/cli/update_command_spec.rb +33 -0
- data/thegarage-gitx.gemspec +3 -0
- metadata +76 -11
- data/lib/thegarage/gitx/cli.rb +0 -117
- data/lib/thegarage/gitx/git.rb +0 -210
- data/lib/thegarage/gitx/github.rb +0 -185
- data/spec/thegarage/gitx/cli_spec.rb +0 -257
- data/spec/thegarage/gitx/git_spec.rb +0 -231
- data/spec/thegarage/gitx/github_spec.rb +0 -91
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5afd02a7ef9c6fc40e717fae217988d5b0eb0b0e
|
4
|
+
data.tar.gz: 980ae3bf2e94996ef6d1f14599ae7956a66b7c01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48e0396c3a9d39952560d3a96caea10dedd64e9b0989da918863528a25969736073c7955fa1385d5aa0abe34104b579ab182e352ff2b34fa008c0ca8c0fe771d
|
7
|
+
data.tar.gz: 8fbb99c25f264ea137e5b6501ea4e8a565b7fdd39730b04d274764b41b8e051eb98bb17191783a4ddc4a4a81df56afbc62eace8eab65d8e63787f5e02b9a6c09
|
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# thegarage-gitx
|
2
2
|
|
3
3
|
[](https://travis-ci.org/thegarage/thegarage-gitx)
|
4
|
+
[](https://coveralls.io/r/thegarage/thegarage-gitx)
|
5
|
+
[](https://codeclimate.com/github/thegarage/thegarage-gitx)
|
4
6
|
|
5
7
|
Useful Git eXtensions for Development workflow at The Garage.
|
6
8
|
|
data/bin/git-buildtag
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'buildtag_command.rb')
|
4
|
+
args = ARGV.dup.unshift('buildtag')
|
5
|
+
Thegarage::Gitx::Cli::BuildtagCommand.start(args)
|
data/bin/git-cleanup
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'cleanup_command.rb')
|
4
|
+
args = ARGV.dup.unshift('cleanup')
|
5
|
+
Thegarage::Gitx::Cli::CleanupCommand.start(args)
|
6
|
+
|
data/bin/git-integrate
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'integrate_command.rb')
|
4
|
+
args = ARGV.dup.unshift('integrate')
|
5
|
+
Thegarage::Gitx::Cli::IntegrateCommand.start(args)
|
data/bin/git-nuke
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'nuke_command.rb')
|
4
|
+
args = ARGV.dup.unshift('nuke')
|
5
|
+
Thegarage::Gitx::Cli::NukeCommand.start(args)
|
data/bin/git-release
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'release_command.rb')
|
4
|
+
args = ARGV.dup.unshift('release')
|
5
|
+
Thegarage::Gitx::Cli::ReleaseCommand.start(args)
|
data/bin/git-reviewrequest
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'review_request_command.rb')
|
4
|
+
args = ARGV.dup.unshift('reviewrequest')
|
5
|
+
Thegarage::Gitx::Cli::ReviewReqeustCommand.start(args)
|
data/bin/git-share
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'share_command.rb')
|
4
|
+
args = ARGV.dup.unshift('share')
|
5
|
+
Thegarage::Gitx::Cli::ShareCommand.start(args)
|
data/bin/git-start
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'start_command.rb')
|
4
|
+
args = ARGV.dup.unshift('start')
|
5
|
+
Thegarage::Gitx::Cli::StartCommand.start(args)
|
data/bin/git-track
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'track_command.rb')
|
4
|
+
args = ARGV.dup.unshift('track')
|
5
|
+
Thegarage::Gitx::Cli::TrackCommand.start(args)
|
data/bin/git-update
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx', '
|
4
|
-
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'thegarage/gitx/cli', 'update_command.rb')
|
4
|
+
args = ARGV.dup.unshift('update')
|
5
|
+
Thegarage::Gitx::Cli::UpdateCommand.start(args)
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'pathname'
|
3
|
+
require 'rugged'
|
4
|
+
require 'English'
|
5
|
+
require 'thegarage/gitx'
|
6
|
+
|
7
|
+
module Thegarage
|
8
|
+
module Gitx
|
9
|
+
module Cli
|
10
|
+
class BaseCommand < Thor
|
11
|
+
include Thor::Actions
|
12
|
+
|
13
|
+
AGGREGATE_BRANCHES = %w( staging prototype )
|
14
|
+
RESERVED_BRANCHES = %w( HEAD master next_release ) + AGGREGATE_BRANCHES
|
15
|
+
add_runtime_options!
|
16
|
+
|
17
|
+
method_option :trace, :type => :boolean, :aliases => '-v'
|
18
|
+
def initialize(*args)
|
19
|
+
super(*args)
|
20
|
+
RestClient.proxy = ENV['HTTPS_PROXY'] if ENV.has_key?('HTTPS_PROXY')
|
21
|
+
RestClient.log = Logger.new(STDOUT) if options[:trace]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def repo
|
27
|
+
@repo ||= begin
|
28
|
+
path = Dir.pwd
|
29
|
+
root_path = Rugged::Repository.discover(path)
|
30
|
+
Rugged::Repository.new(root_path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# lookup the current branch of the repo
|
35
|
+
def current_branch
|
36
|
+
repo.branches.find(&:head?)
|
37
|
+
end
|
38
|
+
|
39
|
+
# execute a shell command and raise an error if non-zero exit code is returned
|
40
|
+
# return the string output from the command
|
41
|
+
def run_cmd(cmd, options = {})
|
42
|
+
say "$ #{cmd}"
|
43
|
+
output = `#{cmd}`
|
44
|
+
success = $CHILD_STATUS.to_i == 0
|
45
|
+
fail "#{cmd} failed" unless success || options[:allow_failure]
|
46
|
+
output
|
47
|
+
end
|
48
|
+
|
49
|
+
def aggregate_branch?(branch)
|
50
|
+
AGGREGATE_BRANCHES.include?(branch)
|
51
|
+
end
|
52
|
+
|
53
|
+
def assert_not_protected_branch!(branch, action)
|
54
|
+
raise "Cannot #{action} reserved branch" if RESERVED_BRANCHES.include?(branch) || aggregate_branch?(branch)
|
55
|
+
end
|
56
|
+
|
57
|
+
# retrieve a list of branches
|
58
|
+
def branches(options = {})
|
59
|
+
branches = []
|
60
|
+
args = []
|
61
|
+
args << '-r' if options[:remote]
|
62
|
+
args << "--merged #{options[:merged].is_a?(String) ? options[:merged] : ''}" if options[:merged]
|
63
|
+
output = `git branch #{args.join(' ')}`.split("\n")
|
64
|
+
output.each do |branch|
|
65
|
+
branch = branch.gsub(/\*/, '').strip.split(' ').first
|
66
|
+
branch = branch.split('/').last if options[:remote]
|
67
|
+
branches << branch unless RESERVED_BRANCHES.include?(branch)
|
68
|
+
end
|
69
|
+
branches.uniq
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thegarage/gitx'
|
3
|
+
require 'thegarage/gitx/cli/base_command'
|
4
|
+
|
5
|
+
module Thegarage
|
6
|
+
module Gitx
|
7
|
+
module Cli
|
8
|
+
class BuildtagCommand < BaseCommand
|
9
|
+
TAGGABLE_BRANCHES = %w( master staging )
|
10
|
+
|
11
|
+
desc 'buildtag', 'create a tag for the current Travis-CI build and push it back to origin'
|
12
|
+
def buildtag
|
13
|
+
branch = ENV['TRAVIS_BRANCH']
|
14
|
+
pull_request = ENV['TRAVIS_PULL_REQUEST']
|
15
|
+
|
16
|
+
raise "Unknown branch. ENV['TRAVIS_BRANCH'] is required." unless branch
|
17
|
+
|
18
|
+
if pull_request != 'false'
|
19
|
+
say "Skipping creation of tag for pull request: #{pull_request}"
|
20
|
+
elsif !TAGGABLE_BRANCHES.include?(branch)
|
21
|
+
say "Cannot create build tag for branch: #{branch}. Only #{TAGGABLE_BRANCHES} are supported."
|
22
|
+
else
|
23
|
+
label = "Generated tag from TravisCI build #{ENV['TRAVIS_BUILD_NUMBER']}"
|
24
|
+
create_build_tag(branch, label)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def create_build_tag(branch, label)
|
31
|
+
timestamp = Time.now.utc.strftime '%Y-%m-%d-%H-%M-%S'
|
32
|
+
git_tag = "build-#{branch}-#{timestamp}"
|
33
|
+
run_cmd "git tag #{git_tag} -a -m '#{label}'"
|
34
|
+
run_cmd "git push origin #{git_tag}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thegarage/gitx'
|
3
|
+
require 'thegarage/gitx/cli/base_command'
|
4
|
+
|
5
|
+
module Thegarage
|
6
|
+
module Gitx
|
7
|
+
module Cli
|
8
|
+
class CleanupCommand < BaseCommand
|
9
|
+
desc 'cleanup', 'Cleanup branches that have been merged into master from the repo'
|
10
|
+
def cleanup
|
11
|
+
run_cmd "git checkout #{Thegarage::Gitx::BASE_BRANCH}"
|
12
|
+
run_cmd "git pull"
|
13
|
+
run_cmd 'git remote prune origin'
|
14
|
+
|
15
|
+
say "Deleting local and remote branches that have been merged into "
|
16
|
+
say Thegarage::Gitx::BASE_BRANCH, :green
|
17
|
+
merged_remote_branches.each do |branch|
|
18
|
+
run_cmd "git push origin --delete #{branch}"
|
19
|
+
end
|
20
|
+
merged_local_branches.each do |branch|
|
21
|
+
run_cmd "git branch -d #{branch}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def merged_local_branches
|
28
|
+
branches(:merged => true).reject { |b| aggregate_branch?(b) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def merged_remote_branches
|
32
|
+
branches(:merged => true, :remote => true).reject { |b| aggregate_branch?(b) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thegarage/gitx'
|
3
|
+
require 'thegarage/gitx/cli/base_command'
|
4
|
+
require 'thegarage/gitx/cli/update_command'
|
5
|
+
|
6
|
+
module Thegarage
|
7
|
+
module Gitx
|
8
|
+
module Cli
|
9
|
+
class IntegrateCommand < BaseCommand
|
10
|
+
desc 'integrate', 'integrate the current branch into one of the aggregate development branches (default = staging)'
|
11
|
+
def integrate(target_branch = 'staging')
|
12
|
+
branch = current_branch.name
|
13
|
+
assert_not_protected_branch!(branch, 'integrate') unless aggregate_branch?(target_branch)
|
14
|
+
raise "Only aggregate branches are allowed for integration: #{AGGREGATE_BRANCHES}" unless aggregate_branch?(target_branch) || target_branch == Thegarage::Gitx::BASE_BRANCH
|
15
|
+
|
16
|
+
UpdateCommand.new.update
|
17
|
+
|
18
|
+
say "Integrating "
|
19
|
+
say "#{branch} ", :green
|
20
|
+
say "into "
|
21
|
+
say target_branch, :green
|
22
|
+
|
23
|
+
refresh_branch_from_remote target_branch
|
24
|
+
run_cmd "git pull . #{branch}"
|
25
|
+
run_cmd "git push origin HEAD"
|
26
|
+
run_cmd "git checkout #{branch}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# nuke local branch and pull fresh version from remote repo
|
32
|
+
def refresh_branch_from_remote(target_branch)
|
33
|
+
run_cmd "git branch -D #{target_branch}", :allow_failure => true
|
34
|
+
run_cmd "git fetch origin"
|
35
|
+
run_cmd "git checkout #{target_branch}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thegarage/gitx'
|
3
|
+
require 'thegarage/gitx/cli/base_command'
|
4
|
+
|
5
|
+
module Thegarage
|
6
|
+
module Gitx
|
7
|
+
module Cli
|
8
|
+
class NukeCommand < BaseCommand
|
9
|
+
desc 'nuke', 'nuke the specified aggregate branch and reset it to a known good state'
|
10
|
+
method_option :destination, :type => :string, :aliases => '-d', :desc => 'destination branch to reset to'
|
11
|
+
def nuke(bad_branch)
|
12
|
+
good_branch = options[:destination] || ask("What branch do you want to reset #{bad_branch} to? (default: #{bad_branch})")
|
13
|
+
good_branch = bad_branch if good_branch.length == 0
|
14
|
+
|
15
|
+
last_known_good_tag = current_build_tag(good_branch)
|
16
|
+
return unless yes?("Reset #{bad_branch} to #{last_known_good_tag}? (y/n)", :green)
|
17
|
+
fail "Only aggregate branches are allowed to be reset: #{AGGREGATE_BRANCHES}" unless aggregate_branch?(bad_branch)
|
18
|
+
return if migrations_need_to_be_reverted?(bad_branch, last_known_good_tag)
|
19
|
+
|
20
|
+
say "Resetting "
|
21
|
+
say "#{bad_branch} ", :green
|
22
|
+
say "branch to "
|
23
|
+
say last_known_good_tag, :green
|
24
|
+
|
25
|
+
run_cmd "git checkout #{Thegarage::Gitx::BASE_BRANCH}"
|
26
|
+
run_cmd "git branch -D #{bad_branch}", :allow_failure => true
|
27
|
+
run_cmd "git push origin --delete #{bad_branch}", :allow_failure => true
|
28
|
+
run_cmd "git checkout -b #{bad_branch} #{last_known_good_tag}"
|
29
|
+
run_cmd "git push origin #{bad_branch}"
|
30
|
+
run_cmd "git branch --set-upstream-to origin/#{bad_branch}"
|
31
|
+
run_cmd "git checkout #{Thegarage::Gitx::BASE_BRANCH}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def migrations_need_to_be_reverted?(bad_branch, last_known_good_tag)
|
37
|
+
return false unless File.exists?('db/migrate')
|
38
|
+
outdated_migrations = run_cmd("git diff #{last_known_good_tag}...#{bad_branch} --name-only db/migrate").split
|
39
|
+
return false if outdated_migrations.empty?
|
40
|
+
|
41
|
+
say "#{bad_branch} contains migrations that may need to be reverted. Ensure any reversable migrations are reverted on affected databases before nuking.", :red
|
42
|
+
say 'Example commands to revert outdated migrations:'
|
43
|
+
outdated_migrations.reverse.each do |migration|
|
44
|
+
version = File.basename(migration).split('_').first
|
45
|
+
say "rake db:migrate:down VERSION=#{version}"
|
46
|
+
end
|
47
|
+
!yes?("Are you sure you want to nuke #{bad_branch}? (y/n) ", :green)
|
48
|
+
end
|
49
|
+
|
50
|
+
def current_build_tag(branch)
|
51
|
+
last_build_tag = build_tags_for_branch(branch).last
|
52
|
+
raise "No known good tag found for branch: #{branch}. Verify tag exists via `git tag -l 'build-#{branch}-*'`" unless last_build_tag
|
53
|
+
last_build_tag
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_tags_for_branch(branch)
|
57
|
+
run_cmd "git fetch --tags"
|
58
|
+
build_tags = run_cmd("git tag -l 'build-#{branch}-*'").split
|
59
|
+
build_tags.sort
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thegarage/gitx'
|
3
|
+
require 'thegarage/gitx/cli/base_command'
|
4
|
+
require 'thegarage/gitx/cli/update_command'
|
5
|
+
require 'thegarage/gitx/cli/integrate_command'
|
6
|
+
require 'thegarage/gitx/cli/cleanup_command'
|
7
|
+
|
8
|
+
module Thegarage
|
9
|
+
module Gitx
|
10
|
+
module Cli
|
11
|
+
class ReleaseCommand < BaseCommand
|
12
|
+
desc 'release', 'release the current branch to production'
|
13
|
+
def release
|
14
|
+
return unless yes?("Release #{current_branch.name} to production? (y/n)", :green)
|
15
|
+
|
16
|
+
branch = current_branch.name
|
17
|
+
assert_not_protected_branch!(branch, 'release')
|
18
|
+
UpdateCommand.new.update
|
19
|
+
|
20
|
+
run_cmd "git checkout #{Thegarage::Gitx::BASE_BRANCH}"
|
21
|
+
run_cmd "git pull origin #{Thegarage::Gitx::BASE_BRANCH}"
|
22
|
+
run_cmd "git pull . #{branch}"
|
23
|
+
run_cmd "git push origin HEAD"
|
24
|
+
|
25
|
+
IntegrateCommand.new.integrate('staging')
|
26
|
+
CleanupCommand.new.cleanup
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thegarage/gitx'
|
3
|
+
require 'thegarage/gitx/cli/base_command'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Thegarage
|
7
|
+
module Gitx
|
8
|
+
module Cli
|
9
|
+
class ReviewRequestCommand < BaseCommand
|
10
|
+
CLIENT_URL = 'https://github.com/thegarage/thegarage-gitx'
|
11
|
+
PULL_REQUEST_FOOTER = <<-EOS.dedent
|
12
|
+
# Pull Request Protips(tm):
|
13
|
+
# * Include description of how this change accomplishes the task at hand.
|
14
|
+
# * Use GitHub flavored Markdown http://github.github.com/github-flavored-markdown/
|
15
|
+
# * Review CONTRIBUTING.md for recommendations of artifacts, links, images, screencasts, etc.
|
16
|
+
#
|
17
|
+
# This footer will automatically be stripped from the pull request description
|
18
|
+
EOS
|
19
|
+
|
20
|
+
desc "reviewrequest", "Create or update a pull request on github"
|
21
|
+
method_option :description, :type => :string, :aliases => '-d', :desc => 'pull request description'
|
22
|
+
method_option :assignee, :type => :string, :aliases => '-a', :desc => 'pull request assignee'
|
23
|
+
method_option :open, :type => :boolean, :aliases => '-o', :desc => 'open the pull request in a web browser'
|
24
|
+
# @see http://developer.github.com/v3/pulls/
|
25
|
+
def reviewrequest
|
26
|
+
fail 'Github authorization token not found' unless authorization_token
|
27
|
+
|
28
|
+
branch = current_branch.name
|
29
|
+
pull_request = find_pull_request(branch)
|
30
|
+
if pull_request.nil?
|
31
|
+
update
|
32
|
+
changelog = run_cmd "git log #{Thegarage::Gitx::BASE_BRANCH}...#{branch} --no-merges --pretty=format:'* %s%n%b'"
|
33
|
+
pull_request = create_pull_request(branch, changelog, options)
|
34
|
+
say 'Pull request created: '
|
35
|
+
say pull_request['html_url'], :green
|
36
|
+
end
|
37
|
+
assign_pull_request(pull_request, options[:assignee]) if options[:assignee]
|
38
|
+
|
39
|
+
run_cmd "open #{pull_request['html_url']}" if options[:open]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# returns [Hash] data structure of created pull request
|
45
|
+
# request github authorization token
|
46
|
+
# User-Agent is required
|
47
|
+
# store the token in local git config
|
48
|
+
# @returns [String] auth token stored in git (current repo, user config or installed global settings)
|
49
|
+
# @see http://developer.github.com/v3/oauth/#scopes
|
50
|
+
# @see http://developer.github.com/v3/#user-agent-required
|
51
|
+
def authorization_token
|
52
|
+
auth_token = repo.config['thegarage.gitx.githubauthtoken']
|
53
|
+
return auth_token unless auth_token.to_s.blank?
|
54
|
+
|
55
|
+
fail "Github user not configured. Run: `git config --global github.user 'me@email.com'`" unless username
|
56
|
+
password = ask("Github password for #{username}: ", :echo => false)
|
57
|
+
|
58
|
+
client_name = "The Garage Git eXtensions - #{remote_origin_name}"
|
59
|
+
payload = {
|
60
|
+
:scopes => ['repo'],
|
61
|
+
:note => client_name,
|
62
|
+
:note_url => CLIENT_URL
|
63
|
+
}.to_json
|
64
|
+
response = RestClient::Request.new({
|
65
|
+
:url => "https://api.github.com/authorizations",
|
66
|
+
:method => "POST",
|
67
|
+
:user => username,
|
68
|
+
:password => password,
|
69
|
+
:payload => payload,
|
70
|
+
:headers => {
|
71
|
+
:accept => :json,
|
72
|
+
:content_type => :json,
|
73
|
+
:user_agent => 'thegarage/gitx'
|
74
|
+
}
|
75
|
+
}).execute
|
76
|
+
data = JSON.parse response.body
|
77
|
+
token = data['token']
|
78
|
+
repo.config['thegarage.gitx.githubauthtoken'] = token
|
79
|
+
token
|
80
|
+
rescue RestClient::Exception => e
|
81
|
+
process_error e
|
82
|
+
end
|
83
|
+
|
84
|
+
# @see http://developer.github.com/v3/pulls/
|
85
|
+
def create_pull_request(branch, changelog, options = {})
|
86
|
+
body = pull_request_body(changelog, options[:description])
|
87
|
+
|
88
|
+
say "Creating pull request for "
|
89
|
+
say "#{branch} ", :green
|
90
|
+
say "against "
|
91
|
+
say "#{Thegarage::Gitx::BASE_BRANCH} ", :green
|
92
|
+
say "in "
|
93
|
+
say remote_origin_name, :green
|
94
|
+
|
95
|
+
payload = {
|
96
|
+
:title => branch,
|
97
|
+
:base => Thegarage::Gitx::BASE_BRANCH,
|
98
|
+
:head => branch,
|
99
|
+
:body => body
|
100
|
+
}.to_json
|
101
|
+
response = RestClient::Request.new(:url => pull_request_url, :method => "POST", :payload => payload, :headers => request_headers).execute
|
102
|
+
pull_request = JSON.parse response.body
|
103
|
+
|
104
|
+
pull_request
|
105
|
+
rescue RestClient::Exception => e
|
106
|
+
process_error e
|
107
|
+
end
|
108
|
+
|
109
|
+
def assign_pull_request(pull_request, assignee)
|
110
|
+
say "Assigning pull request to "
|
111
|
+
say assignee, :green
|
112
|
+
|
113
|
+
branch = pull_request['head']['ref']
|
114
|
+
payload = {
|
115
|
+
:title => branch,
|
116
|
+
:assignee => assignee
|
117
|
+
}.to_json
|
118
|
+
RestClient::Request.new(:url => pull_request['issue_url'], :method => "PATCH", :payload => payload, :headers => request_headers).execute
|
119
|
+
rescue RestClient::Exception => e
|
120
|
+
process_error e
|
121
|
+
end
|
122
|
+
|
123
|
+
# @returns [Hash] data structure of pull request info if found
|
124
|
+
# @returns nil if no pull request found
|
125
|
+
def find_pull_request(branch)
|
126
|
+
head_reference = [remote_origin_name.split('/').first, branch].join(':')
|
127
|
+
params = {
|
128
|
+
head: head_reference,
|
129
|
+
state: 'open'
|
130
|
+
}
|
131
|
+
response = RestClient::Request.new(:url => pull_request_url, :method => "GET", :headers => request_headers.merge(params: params)).execute
|
132
|
+
data = JSON.parse(response.body)
|
133
|
+
data.first
|
134
|
+
rescue RestClient::Exception => e
|
135
|
+
process_error e
|
136
|
+
end
|
137
|
+
|
138
|
+
def process_error(e)
|
139
|
+
data = JSON.parse e.http_body
|
140
|
+
say "Github request failed: #{data['message']}", :red
|
141
|
+
throw e
|
142
|
+
end
|
143
|
+
|
144
|
+
def pull_request_url
|
145
|
+
"https://api.github.com/repos/#{remote_origin_name}/pulls"
|
146
|
+
end
|
147
|
+
|
148
|
+
def request_headers
|
149
|
+
{
|
150
|
+
:accept => :json,
|
151
|
+
:content_type => :json,
|
152
|
+
'Authorization' => "token #{authorization_token}"
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
# @returns [String] github username (ex: 'wireframe') of the current github.user
|
157
|
+
# @returns empty [String] when no github.user is set on the system
|
158
|
+
def username
|
159
|
+
repo.config['github.user']
|
160
|
+
end
|
161
|
+
|
162
|
+
# lookup the current repository of the PWD
|
163
|
+
# ex: git@github.com:socialcast/thegarage/gitx.git OR https://github.com/socialcast/thegarage/gitx.git
|
164
|
+
def remote_origin_name
|
165
|
+
remote = repo.config['remote.origin.url']
|
166
|
+
remote.to_s.gsub(/\.git$/,'').split(/[:\/]/).last(2).join('/')
|
167
|
+
end
|
168
|
+
|
169
|
+
def pull_request_body(changelog, description = nil)
|
170
|
+
description_template = []
|
171
|
+
description_template << "#{description}\n" if description
|
172
|
+
description_template << '### Changelog'
|
173
|
+
description_template << changelog
|
174
|
+
description_template << PULL_REQUEST_FOOTER
|
175
|
+
|
176
|
+
body = input_from_editor(description_template.join("\n"))
|
177
|
+
body.gsub(PULL_REQUEST_FOOTER, '').chomp.strip
|
178
|
+
end
|
179
|
+
|
180
|
+
# launch configured editor to retreive message/string
|
181
|
+
def input_from_editor(initial_text = '')
|
182
|
+
Tempfile.open('reviewrequest.md') do |f|
|
183
|
+
f << initial_text
|
184
|
+
f.flush
|
185
|
+
|
186
|
+
editor = repo.config['core.editor'] || ENV['EDITOR'] || 'vi'
|
187
|
+
flags = case editor
|
188
|
+
when 'mate', 'emacs', 'subl'
|
189
|
+
'-w'
|
190
|
+
when 'mvim'
|
191
|
+
'-f'
|
192
|
+
else
|
193
|
+
''
|
194
|
+
end
|
195
|
+
pid = fork { exec([editor, flags, f.path].join(' ')) }
|
196
|
+
Process.waitpid(pid)
|
197
|
+
File.read(f.path)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thegarage/gitx'
|
3
|
+
require 'thegarage/gitx/cli/base_command'
|
4
|
+
|
5
|
+
module Thegarage
|
6
|
+
module Gitx
|
7
|
+
module Cli
|
8
|
+
class ShareCommand < BaseCommand
|
9
|
+
desc 'share', 'Share the current branch in the remote repository'
|
10
|
+
def share
|
11
|
+
run_cmd "git push origin #{current_branch.name}"
|
12
|
+
run_cmd "git branch --set-upstream-to origin/#{current_branch.name}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|