modulesync 1.2.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.config/cucumber.yml +1 -0
- data/.github/workflows/ci.yml +29 -0
- data/.github/workflows/release.yml +30 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +8 -1
- data/.rubocop_todo.yml +16 -35
- data/CHANGELOG.md +82 -1
- data/Gemfile +5 -1
- data/HISTORY.md +227 -0
- data/README.md +29 -6
- data/Rakefile +24 -0
- data/features/cli.feature +14 -6
- data/features/hook.feature +3 -5
- data/features/step_definitions/git_steps.rb +73 -35
- data/features/support/env.rb +4 -0
- data/features/update.feature +163 -341
- data/features/update/bad_context.feature +26 -0
- data/features/update/bump_version.feature +87 -0
- data/lib/modulesync.rb +92 -51
- data/lib/modulesync/cli.rb +10 -4
- data/lib/modulesync/cli/thor.rb +24 -0
- data/lib/modulesync/pr/github.rb +25 -9
- data/lib/modulesync/pr/gitlab.rb +27 -13
- data/lib/modulesync/puppet_module.rb +37 -0
- data/lib/modulesync/repository.rb +158 -0
- data/lib/modulesync/source_code.rb +57 -0
- data/lib/modulesync/util.rb +4 -1
- data/lib/monkey_patches.rb +9 -48
- data/modulesync.gemspec +4 -4
- data/spec/helpers/faker.rb +14 -0
- data/spec/helpers/faker/puppet_module_remote_repo.rb +146 -0
- data/spec/unit/modulesync_spec.rb +7 -3
- metadata +30 -10
- data/.travis.yml +0 -27
- data/lib/modulesync/git.rb +0 -194
data/lib/modulesync/pr/gitlab.rb
CHANGED
@@ -15,25 +15,39 @@ module ModuleSync
|
|
15
15
|
|
16
16
|
def manage(namespace, module_name, options)
|
17
17
|
repo_path = File.join(namespace, module_name)
|
18
|
+
branch = options[:remote_branch] || options[:branch]
|
19
|
+
head = "#{namespace}:#{branch}"
|
20
|
+
target_branch = options[:pr_target_branch] || 'master'
|
21
|
+
|
22
|
+
if options[:noop]
|
23
|
+
$stdout.puts \
|
24
|
+
"Using no-op. Would submit MR '#{options[:pr_title]}' to #{repo_path} " \
|
25
|
+
"- merges #{branch} into #{target_branch}"
|
26
|
+
return
|
27
|
+
end
|
18
28
|
|
19
|
-
head = "#{namespace}:#{options[:branch]}"
|
20
29
|
merge_requests = @api.merge_requests(repo_path,
|
21
30
|
:state => 'opened',
|
22
31
|
:source_branch => head,
|
23
|
-
:target_branch =>
|
24
|
-
|
25
|
-
mr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
|
26
|
-
mr = @api.create_merge_request(repo_path, options[:pr_title],
|
27
|
-
:source_branch => options[:branch],
|
28
|
-
:target_branch => 'master',
|
29
|
-
:labels => mr_labels)
|
30
|
-
$stdout.puts "Submitted MR '#{options[:pr_title]}' to #{repo_path} - merges #{options[:branch]} into master"
|
31
|
-
return if mr_labels.empty?
|
32
|
-
$stdout.puts "Attached the following labels to MR #{mr.iid}: #{mr_labels.join(', ')}"
|
33
|
-
else
|
32
|
+
:target_branch => target_branch)
|
33
|
+
unless merge_requests.empty?
|
34
34
|
# Skip creating the MR if it exists already.
|
35
|
-
$stdout.puts "Skipped! #{merge_requests.length} MRs found for branch #{
|
35
|
+
$stdout.puts "Skipped! #{merge_requests.length} MRs found for branch #{branch}"
|
36
|
+
return
|
36
37
|
end
|
38
|
+
|
39
|
+
mr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
|
40
|
+
mr = @api.create_merge_request(repo_path,
|
41
|
+
options[:pr_title],
|
42
|
+
:source_branch => branch,
|
43
|
+
:target_branch => target_branch,
|
44
|
+
:labels => mr_labels)
|
45
|
+
$stdout.puts \
|
46
|
+
"Submitted MR '#{options[:pr_title]}' to #{repo_path} " \
|
47
|
+
"- merges #{branch} into #{target_branch}"
|
48
|
+
|
49
|
+
return if mr_labels.empty?
|
50
|
+
$stdout.puts "Attached the following labels to MR #{mr.iid}: #{mr_labels.join(', ')}"
|
37
51
|
end
|
38
52
|
end
|
39
53
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'puppet_blacksmith'
|
2
|
+
|
3
|
+
require 'modulesync/source_code'
|
4
|
+
|
5
|
+
module ModuleSync
|
6
|
+
# Provide methods to manipulate puppet module code
|
7
|
+
class PuppetModule < SourceCode
|
8
|
+
def update_changelog(version, message)
|
9
|
+
changelog = path('CHANGELOG.md')
|
10
|
+
if File.exist?(changelog)
|
11
|
+
puts "Updating #{changelog} for version #{version}"
|
12
|
+
changes = File.readlines(changelog)
|
13
|
+
File.open(changelog, 'w') do |f|
|
14
|
+
date = Time.now.strftime('%Y-%m-%d')
|
15
|
+
f.puts "## #{date} - Release #{version}\n\n"
|
16
|
+
f.puts "#{message}\n\n"
|
17
|
+
# Add old lines again
|
18
|
+
f.puts changes
|
19
|
+
end
|
20
|
+
repository.git.add('CHANGELOG.md')
|
21
|
+
else
|
22
|
+
puts 'No CHANGELOG.md file found, not updating.'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def bump(message, changelog = false)
|
27
|
+
m = Blacksmith::Modulefile.new path('metadata.json')
|
28
|
+
new = m.bump!
|
29
|
+
puts "Bumped to version #{new}"
|
30
|
+
repository.git.add('metadata.json')
|
31
|
+
update_changelog(new, message) if changelog
|
32
|
+
repository.git.commit("Release version #{new}")
|
33
|
+
repository.git.push
|
34
|
+
new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module ModuleSync
|
4
|
+
# Wrapper for Git in ModuleSync context
|
5
|
+
class Repository
|
6
|
+
def initialize(directory:, remote:)
|
7
|
+
@directory = directory
|
8
|
+
@remote = remote
|
9
|
+
end
|
10
|
+
|
11
|
+
def git
|
12
|
+
@git ||= Git.open @directory
|
13
|
+
end
|
14
|
+
|
15
|
+
# This is an alias to minimize code alteration
|
16
|
+
def repo
|
17
|
+
git
|
18
|
+
end
|
19
|
+
|
20
|
+
def remote_branch_exists?(branch)
|
21
|
+
repo.branches.remote.collect(&:name).include?(branch)
|
22
|
+
end
|
23
|
+
|
24
|
+
def local_branch_exists?(branch)
|
25
|
+
repo.branches.local.collect(&:name).include?(branch)
|
26
|
+
end
|
27
|
+
|
28
|
+
def remote_branch_differ?(local_branch, remote_branch)
|
29
|
+
!remote_branch_exists?(remote_branch) ||
|
30
|
+
repo.diff("#{local_branch}..origin/#{remote_branch}").any?
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_branch
|
34
|
+
symbolic_ref = repo.branches.find { |b| b.full =~ %r{remotes/origin/HEAD} }
|
35
|
+
return unless symbolic_ref
|
36
|
+
%r{remotes/origin/HEAD\s+->\s+origin/(?<branch>.+?)$}.match(symbolic_ref.full)[:branch]
|
37
|
+
end
|
38
|
+
|
39
|
+
def switch_branch(branch)
|
40
|
+
unless branch
|
41
|
+
branch = default_branch
|
42
|
+
puts "Using repository's default branch: #{branch}"
|
43
|
+
end
|
44
|
+
return if repo.current_branch == branch
|
45
|
+
|
46
|
+
if local_branch_exists?(branch)
|
47
|
+
puts "Switching to branch #{branch}"
|
48
|
+
repo.checkout(branch)
|
49
|
+
elsif remote_branch_exists?(branch)
|
50
|
+
puts "Creating local branch #{branch} from origin/#{branch}"
|
51
|
+
repo.checkout("origin/#{branch}")
|
52
|
+
repo.branch(branch).checkout
|
53
|
+
else
|
54
|
+
repo.checkout('origin/master')
|
55
|
+
puts "Creating new branch #{branch}"
|
56
|
+
repo.branch(branch).checkout
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def prepare_workspace(branch)
|
61
|
+
# Repo needs to be cloned in the cwd
|
62
|
+
if !Dir.exist?("#{@directory}/.git")
|
63
|
+
puts 'Cloning repository fresh'
|
64
|
+
puts "Cloning from '#{@remote}'"
|
65
|
+
@git = Git.clone(@remote, @directory)
|
66
|
+
switch_branch(branch)
|
67
|
+
# Repo already cloned, check out master and override local changes
|
68
|
+
else
|
69
|
+
# Some versions of git can't properly handle managing a repo from outside the repo directory
|
70
|
+
Dir.chdir(@directory) do
|
71
|
+
puts "Overriding any local changes to repository in '#{@directory}'"
|
72
|
+
@git = Git.open('.')
|
73
|
+
repo.fetch
|
74
|
+
repo.reset_hard
|
75
|
+
switch_branch(branch)
|
76
|
+
git.pull('origin', branch) if remote_branch_exists?(branch)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def tag(version, tag_pattern)
|
82
|
+
tag = tag_pattern % version
|
83
|
+
puts "Tagging with #{tag}"
|
84
|
+
repo.add_tag(tag)
|
85
|
+
repo.push('origin', tag)
|
86
|
+
end
|
87
|
+
|
88
|
+
def checkout_branch(branch)
|
89
|
+
selected_branch = branch || repo.current_branch || 'master'
|
90
|
+
repo.branch(selected_branch).checkout
|
91
|
+
selected_branch
|
92
|
+
end
|
93
|
+
|
94
|
+
# Git add/rm, git commit, git push
|
95
|
+
def submit_changes(files, options)
|
96
|
+
message = options[:message]
|
97
|
+
branch = checkout_branch(options[:branch])
|
98
|
+
files.each do |file|
|
99
|
+
if repo.status.deleted.include?(file)
|
100
|
+
repo.remove(file)
|
101
|
+
elsif File.exist?("#{@directory}/#{file}")
|
102
|
+
repo.add(file)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
begin
|
106
|
+
opts_commit = {}
|
107
|
+
opts_push = {}
|
108
|
+
opts_commit = { :amend => true } if options[:amend]
|
109
|
+
opts_push = { :force => true } if options[:force]
|
110
|
+
if options[:pre_commit_script]
|
111
|
+
script = "#{File.dirname(File.dirname(__FILE__))}/../contrib/#{options[:pre_commit_script]}"
|
112
|
+
`#{script} #{@directory}`
|
113
|
+
end
|
114
|
+
repo.commit(message, opts_commit)
|
115
|
+
if options[:remote_branch]
|
116
|
+
if remote_branch_differ?(branch, options[:remote_branch])
|
117
|
+
repo.push('origin', "#{branch}:#{options[:remote_branch]}", opts_push)
|
118
|
+
end
|
119
|
+
else
|
120
|
+
repo.push('origin', branch, opts_push)
|
121
|
+
end
|
122
|
+
rescue Git::GitExecuteError => e
|
123
|
+
raise unless e.message.match?(/working (directory|tree) clean/)
|
124
|
+
|
125
|
+
puts "There were no changes in '#{@directory}'. Not committing."
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
# Needed because of a bug in the git gem that lists ignored files as
|
133
|
+
# untracked under some circumstances
|
134
|
+
# https://github.com/schacon/ruby-git/issues/130
|
135
|
+
def untracked_unignored_files
|
136
|
+
ignore_path = "#{@directory}/.gitignore"
|
137
|
+
ignored = File.exist?(ignore_path) ? File.read(ignore_path).split : []
|
138
|
+
repo.status.untracked.keep_if { |f, _| ignored.none? { |i| File.fnmatch(i, f) } }
|
139
|
+
end
|
140
|
+
|
141
|
+
def show_changes(options)
|
142
|
+
checkout_branch(options[:branch])
|
143
|
+
|
144
|
+
puts 'Files changed:'
|
145
|
+
repo.diff('HEAD', '--').each do |diff|
|
146
|
+
puts diff.patch
|
147
|
+
end
|
148
|
+
|
149
|
+
puts 'Files added:'
|
150
|
+
untracked_unignored_files.each_key do |file|
|
151
|
+
puts file
|
152
|
+
end
|
153
|
+
|
154
|
+
puts "\n\n"
|
155
|
+
puts '--------------------------------'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'modulesync'
|
2
|
+
require 'modulesync/repository'
|
3
|
+
require 'modulesync/util'
|
4
|
+
|
5
|
+
module ModuleSync
|
6
|
+
# Provide methods to retrieve source code attributes
|
7
|
+
class SourceCode
|
8
|
+
attr_reader :given_name
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
def initialize(given_name, options)
|
12
|
+
@options = Util.symbolize_keys(options || {})
|
13
|
+
|
14
|
+
@given_name = given_name
|
15
|
+
|
16
|
+
return unless given_name.include?('/')
|
17
|
+
|
18
|
+
@repository_name = given_name.split('/').last
|
19
|
+
@repository_namespace = given_name.split('/')[0...-1].join('/')
|
20
|
+
end
|
21
|
+
|
22
|
+
def repository
|
23
|
+
@repository ||= Repository.new directory: working_directory, remote: repository_remote
|
24
|
+
end
|
25
|
+
|
26
|
+
def repository_name
|
27
|
+
@repository_name ||= given_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def repository_namespace
|
31
|
+
@repository_namespace ||= @options[:namespace] || ModuleSync.options[:namespace]
|
32
|
+
end
|
33
|
+
|
34
|
+
def repository_path
|
35
|
+
@repository_path ||= "#{repository_namespace}/#{repository_name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def repository_remote
|
39
|
+
@repository_remote ||= @options[:remote] || _repository_remote
|
40
|
+
end
|
41
|
+
|
42
|
+
def working_directory
|
43
|
+
@working_directory ||= File.join(ModuleSync.options[:project_root], repository_path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def path(*parts)
|
47
|
+
File.join(working_directory, *parts)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def _repository_remote
|
53
|
+
git_base = ModuleSync.options[:git_base]
|
54
|
+
git_base.start_with?('file://') ? "#{git_base}#{repository_path}" : "#{git_base}#{repository_path}.git"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/modulesync/util.rb
CHANGED
@@ -3,7 +3,10 @@ require 'yaml'
|
|
3
3
|
module ModuleSync
|
4
4
|
module Util
|
5
5
|
def self.symbolize_keys(hash)
|
6
|
-
hash.inject({})
|
6
|
+
hash.inject({}) do |memo, (k, v)|
|
7
|
+
memo[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
|
8
|
+
memo
|
9
|
+
end
|
7
10
|
end
|
8
11
|
|
9
12
|
def self.parse_config(config_file)
|
data/lib/monkey_patches.rb
CHANGED
@@ -1,55 +1,16 @@
|
|
1
1
|
module Git
|
2
|
-
|
3
|
-
# Monkey patch
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
}
|
11
|
-
final = {}
|
12
|
-
current_file = nil
|
13
|
-
full_diff_utf8_encoded = @full_diff.encode("UTF-8", "binary", {
|
14
|
-
:invalid => :replace,
|
15
|
-
:undef => :replace
|
16
|
-
})
|
17
|
-
full_diff_utf8_encoded.split("\n").each do |line|
|
18
|
-
if m = /^diff --git a\/(.*?) b\/(.*?)/.match(line)
|
19
|
-
current_file = m[1]
|
20
|
-
final[current_file] = defaults.merge({:patch => line, :path => current_file})
|
21
|
-
elsif !current_file.nil?
|
22
|
-
if m = /^index (.......)\.\.(.......)( ......)*/.match(line)
|
23
|
-
final[current_file][:src] = m[1]
|
24
|
-
final[current_file][:dst] = m[2]
|
25
|
-
final[current_file][:mode] = m[3].strip if m[3]
|
26
|
-
end
|
27
|
-
if m = /^([[:alpha:]]*?) file mode (......)/.match(line)
|
28
|
-
final[current_file][:type] = m[1]
|
29
|
-
final[current_file][:mode] = m[2]
|
30
|
-
end
|
31
|
-
if m = /^Binary files /.match(line)
|
32
|
-
final[current_file][:binary] = true
|
33
|
-
end
|
34
|
-
final[current_file][:patch] << "\n" + line
|
35
|
-
end
|
36
|
-
end
|
37
|
-
final.map { |e| [e[0], DiffFile.new(@base, e[1])] }
|
2
|
+
module LibMonkeyPatch
|
3
|
+
# Monkey patch set_custom_git_env_variables due to our ::Git::GitExecuteError handling.
|
4
|
+
#
|
5
|
+
# We rescue on the GitExecuteError and proceed differently based on the output of git.
|
6
|
+
# This way makes code language-dependent, so here we ensure that Git gem throw git commands with the "C" language
|
7
|
+
def set_custom_git_env_variables
|
8
|
+
super
|
9
|
+
ENV['LANG'] = 'C.UTF-8'
|
38
10
|
end
|
39
11
|
end
|
40
12
|
|
41
13
|
class Lib
|
42
|
-
|
43
|
-
def ls_files(location=nil)
|
44
|
-
location ||= '.'
|
45
|
-
hsh = {}
|
46
|
-
command_lines('ls-files', ['--stage', location]).each do |line|
|
47
|
-
(info, file) = line.split("\t")
|
48
|
-
(mode, sha, stage) = info.split
|
49
|
-
file = eval(file) if file =~ /^\".*\"$/ # This takes care of quoted strings returned from git
|
50
|
-
hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
|
51
|
-
end
|
52
|
-
hsh
|
53
|
-
end
|
14
|
+
prepend LibMonkeyPatch
|
54
15
|
end
|
55
16
|
end
|
data/modulesync.gemspec
CHANGED
@@ -3,14 +3,14 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |spec|
|
5
5
|
spec.name = 'modulesync'
|
6
|
-
spec.version = '1.
|
6
|
+
spec.version = '2.1.0'
|
7
7
|
spec.authors = ['Vox Pupuli']
|
8
8
|
spec.email = ['voxpupuli@groups.io']
|
9
9
|
spec.summary = 'Puppet Module Synchronizer'
|
10
10
|
spec.description = 'Utility to synchronize common files across puppet modules in Github.'
|
11
11
|
spec.homepage = 'http://github.com/voxpupuli/modulesync'
|
12
12
|
spec.license = 'Apache-2.0'
|
13
|
-
spec.required_ruby_version = '>= 2.
|
13
|
+
spec.required_ruby_version = '>= 2.5.0'
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -23,9 +23,9 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency 'rspec'
|
24
24
|
spec.add_development_dependency 'rubocop', '~> 0.50.0'
|
25
25
|
|
26
|
-
spec.add_runtime_dependency 'git', '~>1.
|
26
|
+
spec.add_runtime_dependency 'git', '~>1.7'
|
27
27
|
spec.add_runtime_dependency 'gitlab', '~>4.0'
|
28
28
|
spec.add_runtime_dependency 'octokit', '~>4.0'
|
29
|
-
spec.add_runtime_dependency 'puppet-blacksmith', '
|
29
|
+
spec.add_runtime_dependency 'puppet-blacksmith', '>= 3.0', '< 7'
|
30
30
|
spec.add_runtime_dependency 'thor'
|
31
31
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ModuleSync
|
2
|
+
# Faker is a top-level module to keep global faker config
|
3
|
+
module Faker
|
4
|
+
def self.working_directory=(path)
|
5
|
+
@working_directory = path
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.working_directory
|
9
|
+
raise 'Working directory must be set' if @working_directory.nil?
|
10
|
+
FileUtils.mkdir_p @working_directory
|
11
|
+
@working_directory
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
require_relative '../faker'
|
4
|
+
|
5
|
+
module ModuleSync
|
6
|
+
# Fake a remote git repository that holds a puppet module
|
7
|
+
#
|
8
|
+
# This module allows to fake a remote repositiory using:
|
9
|
+
# - a bare repo
|
10
|
+
# - a temporary cloned repo to operate on the remote before exposing to modulesync
|
11
|
+
#
|
12
|
+
# Note: This module needs to have working_directory sets before using it
|
13
|
+
module Faker
|
14
|
+
class PuppetModuleRemoteRepo
|
15
|
+
class CommandExecutionError < StandardError; end
|
16
|
+
|
17
|
+
attr_reader :name, :namespace
|
18
|
+
|
19
|
+
def initialize(name, namespace)
|
20
|
+
@name = name
|
21
|
+
@namespace = namespace
|
22
|
+
end
|
23
|
+
|
24
|
+
def populate
|
25
|
+
FileUtils.chdir(Faker.working_directory) do
|
26
|
+
run %W[git init --bare #{bare_repo_dir}]
|
27
|
+
run %W[git clone #{bare_repo_dir} #{tmp_repo_dir}]
|
28
|
+
|
29
|
+
module_short_name = name.split('-').last
|
30
|
+
|
31
|
+
FileUtils.chdir(tmp_repo_dir) do
|
32
|
+
metadata = {
|
33
|
+
name: "modulesync-#{module_short_name}",
|
34
|
+
version: '0.4.2',
|
35
|
+
author: 'ModuleSync team',
|
36
|
+
}
|
37
|
+
|
38
|
+
File.write 'metadata.json', metadata.to_json
|
39
|
+
run %w[git add metadata.json]
|
40
|
+
run %w[git commit --message] << 'Initial commit'
|
41
|
+
run %w[git push]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def read_only=(value)
|
47
|
+
mode = value ? '0444' : '0644'
|
48
|
+
FileUtils.chdir(bare_repo_dir) do
|
49
|
+
run %W[git config core.sharedRepository #{mode}]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_branch
|
54
|
+
FileUtils.chdir(bare_repo_dir) do
|
55
|
+
stdout = run %w[git symbolic-ref --short HEAD]
|
56
|
+
return stdout.chomp
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_branch=(value)
|
61
|
+
FileUtils.chdir(bare_repo_dir) do
|
62
|
+
run %W[git branch -M #{default_branch} #{value}]
|
63
|
+
run %W[git symbolic-ref HEAD refs/heads/#{value}]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def read_file(filename, branch = nil)
|
68
|
+
branch ||= default_branch
|
69
|
+
FileUtils.chdir(bare_repo_dir) do
|
70
|
+
return run %W[git show #{branch}:#{filename}]
|
71
|
+
rescue CommandExecutionError
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_file(filename, content, branch = nil)
|
77
|
+
branch ||= default_branch
|
78
|
+
FileUtils.chdir(tmp_repo_dir) do
|
79
|
+
run %W[git checkout #{branch}]
|
80
|
+
File.write filename, content
|
81
|
+
run %W[git add #{filename}]
|
82
|
+
run %w[git commit --message] << "Add file: '#{filename}'"
|
83
|
+
run %w[git push]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def commit_count_between(commit1, commit2)
|
88
|
+
FileUtils.chdir(bare_repo_dir) do
|
89
|
+
stdout = run %W[git rev-list --count #{commit1}..#{commit2}]
|
90
|
+
return Integer(stdout)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def commit_count_by(author, commit = nil)
|
95
|
+
FileUtils.chdir(bare_repo_dir) do
|
96
|
+
commit ||= '--all'
|
97
|
+
stdout = run %W[git rev-list #{commit} --author #{author} --count]
|
98
|
+
return Integer(stdout)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def tags
|
103
|
+
FileUtils.chdir(bare_repo_dir) do
|
104
|
+
return run %w{git tag --list}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def remote_url
|
109
|
+
"file://#{bare_repo_dir}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.git_base
|
113
|
+
"file://#{Faker.working_directory}/bare/"
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def tmp_repo_dir
|
119
|
+
File.join Faker.working_directory, 'tmp', namespace, name
|
120
|
+
end
|
121
|
+
|
122
|
+
def bare_repo_dir
|
123
|
+
File.join Faker.working_directory, 'bare', namespace, "#{name}.git"
|
124
|
+
end
|
125
|
+
|
126
|
+
GIT_ENV = {
|
127
|
+
'GIT_AUTHOR_NAME' => 'Faker',
|
128
|
+
'GIT_AUTHOR_EMAIL' => 'faker@example.com',
|
129
|
+
'GIT_COMMITTER_NAME' => 'Faker',
|
130
|
+
'GIT_COMMITTER_EMAIL' => 'faker@example.com',
|
131
|
+
}.freeze
|
132
|
+
|
133
|
+
def run(command)
|
134
|
+
stdout, stderr, status = Open3.capture3(GIT_ENV, *command)
|
135
|
+
return stdout if status.success?
|
136
|
+
|
137
|
+
warn "Command '#{command}' failed: #{status}"
|
138
|
+
warn ' STDOUT:'
|
139
|
+
warn stdout
|
140
|
+
warn ' STDERR:'
|
141
|
+
warn stderr
|
142
|
+
raise CommandExecutionError, "Command '#{command}' failed: #{status}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|