modulesync 1.1.0 → 2.0.2
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/.gitignore +9 -3
- data/.rubocop.yml +8 -1
- data/.rubocop_todo.yml +1 -1
- data/.travis.yml +7 -6
- data/CHANGELOG.md +71 -0
- data/Gemfile +5 -1
- data/HISTORY.md +227 -0
- data/README.md +42 -17
- data/Rakefile +24 -0
- data/features/cli.feature +11 -6
- data/features/hook.feature +3 -5
- data/features/step_definitions/git_steps.rb +63 -35
- data/features/support/env.rb +4 -0
- data/features/update.feature +160 -339
- data/features/update/bad_context.feature +26 -0
- data/lib/modulesync.rb +66 -48
- data/lib/modulesync/cli.rb +13 -7
- data/lib/modulesync/cli/thor.rb +24 -0
- data/lib/modulesync/git.rb +1 -1
- data/lib/modulesync/pr/github.rb +57 -0
- data/lib/modulesync/pr/gitlab.rb +54 -0
- data/lib/modulesync/util.rb +4 -1
- data/lib/monkey_patches.rb +9 -48
- data/modulesync.gemspec +5 -4
- data/spec/helpers/faker.rb +14 -0
- data/spec/helpers/faker/puppet_module_remote_repo.rb +140 -0
- data/spec/unit/modulesync/pr/github_spec.rb +49 -0
- data/spec/unit/modulesync/pr/gitlab_spec.rb +81 -0
- data/spec/unit/modulesync_spec.rb +5 -52
- metadata +42 -7
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Run `msync update` without a good context
|
2
|
+
|
3
|
+
Scenario: Run `msync update` without any module
|
4
|
+
Given a directory named "moduleroot"
|
5
|
+
When I run `msync update --message "In a bad context"`
|
6
|
+
Then the exit status should be 1
|
7
|
+
And the stderr should contain:
|
8
|
+
"""
|
9
|
+
No modules found
|
10
|
+
"""
|
11
|
+
|
12
|
+
Scenario: Run `msync update` without the "moduleroot" directory
|
13
|
+
Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
|
14
|
+
When I run `msync update --message "In a bad context"`
|
15
|
+
Then the exit status should be 1
|
16
|
+
And the stderr should contain "moduleroot"
|
17
|
+
|
18
|
+
Scenario: Run `msync update` without commit message
|
19
|
+
Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
|
20
|
+
And a directory named "moduleroot"
|
21
|
+
When I run `msync update`
|
22
|
+
Then the exit status should be 1
|
23
|
+
And the stderr should contain:
|
24
|
+
"""
|
25
|
+
No value provided for required option "--message"
|
26
|
+
"""
|
data/lib/modulesync.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'fileutils'
|
2
|
-
require 'octokit'
|
3
2
|
require 'pathname'
|
4
3
|
require 'modulesync/cli'
|
5
4
|
require 'modulesync/constants'
|
@@ -10,12 +9,6 @@ require 'modulesync/settings'
|
|
10
9
|
require 'modulesync/util'
|
11
10
|
require 'monkey_patches'
|
12
11
|
|
13
|
-
GITHUB_TOKEN = ENV.fetch('GITHUB_TOKEN', '')
|
14
|
-
|
15
|
-
Octokit.configure do |c|
|
16
|
-
c.api_endpoint = ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com')
|
17
|
-
end
|
18
|
-
|
19
12
|
module ModuleSync # rubocop:disable Metrics/ModuleLength
|
20
13
|
include Constants
|
21
14
|
|
@@ -46,10 +39,10 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
46
39
|
.collect { |p| p.chomp('.erb') }
|
47
40
|
.to_a
|
48
41
|
else
|
49
|
-
$
|
42
|
+
$stderr.puts "#{local_template_dir} does not exist." \
|
50
43
|
' Check that you are working in your module configs directory or' \
|
51
44
|
' that you have passed in the correct directory with -c.'
|
52
|
-
exit
|
45
|
+
exit 1
|
53
46
|
end
|
54
47
|
end
|
55
48
|
|
@@ -60,9 +53,9 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
60
53
|
def self.managed_modules(config_file, filter, negative_filter)
|
61
54
|
managed_modules = Util.parse_config(config_file)
|
62
55
|
if managed_modules.empty?
|
63
|
-
$
|
56
|
+
$stderr.puts "No modules found in #{config_file}." \
|
64
57
|
' Check that you specified the right :configs directory and :managed_modules_conf file.'
|
65
|
-
exit
|
58
|
+
exit 1
|
66
59
|
end
|
67
60
|
managed_modules.select! { |m| m =~ Regexp.new(filter) } unless filter.nil?
|
68
61
|
managed_modules.reject! { |m| m =~ Regexp.new(negative_filter) } unless negative_filter.nil?
|
@@ -112,7 +105,11 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
112
105
|
end
|
113
106
|
|
114
107
|
def self.manage_module(puppet_module, module_files, module_options, defaults, options)
|
115
|
-
|
108
|
+
default_namespace = options[:namespace]
|
109
|
+
if module_options.is_a?(Hash) && module_options.key?(:namespace)
|
110
|
+
default_namespace = module_options[:namespace]
|
111
|
+
end
|
112
|
+
namespace, module_name = module_name(puppet_module, default_namespace)
|
116
113
|
git_repo = File.join(namespace, module_name)
|
117
114
|
unless options[:offline]
|
118
115
|
Git.pull(options[:git_base], git_repo, options[:branch], options[:project_root], module_options || {})
|
@@ -135,55 +132,39 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
135
132
|
|
136
133
|
if options[:noop]
|
137
134
|
Git.update_noop(git_repo, options)
|
135
|
+
options[:pr] && pr(module_options).manage(namespace, module_name, options)
|
138
136
|
elsif !options[:offline]
|
139
|
-
# Git.update() returns a boolean: true if files were pushed, false if not.
|
140
137
|
pushed = Git.update(git_repo, files_to_manage, options)
|
141
|
-
|
142
|
-
|
143
|
-
manage_pr(namespace, module_name, options)
|
138
|
+
pushed && options[:pr] && pr(module_options).manage(namespace, module_name, options)
|
144
139
|
end
|
145
140
|
end
|
146
141
|
|
147
|
-
def self.
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
end
|
152
|
-
|
153
|
-
# We only do GitHub PR work if the GITHUB_TOKEN variable is set in the environment.
|
154
|
-
repo_path = File.join(namespace, module_name)
|
155
|
-
github = Octokit::Client.new(:access_token => GITHUB_TOKEN)
|
156
|
-
|
157
|
-
# Skip creating the PR if it exists already.
|
158
|
-
head = "#{namespace}:#{options[:branch]}"
|
159
|
-
pull_requests = github.pull_requests(repo_path, :state => 'open', :base => 'master', :head => head)
|
160
|
-
if pull_requests.empty?
|
161
|
-
pr = github.create_pull_request(repo_path, 'master', options[:branch], options[:pr_title], options[:message])
|
162
|
-
$stdout.puts "Submitted PR '#{options[:pr_title]}' to #{repo_path} - merges #{options[:branch]} into master"
|
163
|
-
else
|
164
|
-
$stdout.puts "Skipped! #{pull_requests.length} PRs found for branch #{options[:branch]}"
|
165
|
-
end
|
166
|
-
|
167
|
-
# PR labels can either be a list in the YAML file or they can pass in a comma
|
168
|
-
# separated list via the command line argument.
|
169
|
-
pr_labels = Util.parse_list(options[:pr_labels])
|
142
|
+
def self.config_path(file, options)
|
143
|
+
return file if Pathname.new(file).absolute?
|
144
|
+
File.join(options[:configs], file)
|
145
|
+
end
|
170
146
|
|
171
|
-
|
172
|
-
|
173
|
-
return if pr_labels.empty?
|
174
|
-
$stdout.puts "Attaching the following labels to PR #{pr['number']}: #{pr_labels.join(', ')}"
|
175
|
-
github.add_labels_to_an_issue(repo_path, pr['number'], pr_labels)
|
147
|
+
def config_path(file, options)
|
148
|
+
self.class.config_path(file, options)
|
176
149
|
end
|
177
150
|
|
178
151
|
def self.update(options)
|
179
152
|
options = config_defaults.merge(options)
|
180
|
-
defaults = Util.parse_config(
|
153
|
+
defaults = Util.parse_config(config_path(CONF_FILE, options))
|
154
|
+
if options[:pr]
|
155
|
+
unless options[:branch]
|
156
|
+
$stderr.puts 'A branch must be specified with --branch to use --pr!'
|
157
|
+
raise
|
158
|
+
end
|
159
|
+
|
160
|
+
@pr = create_pr_manager if options[:pr]
|
161
|
+
end
|
181
162
|
|
182
|
-
local_template_dir =
|
163
|
+
local_template_dir = config_path(MODULE_FILES_DIR, options)
|
183
164
|
local_files = find_template_files(local_template_dir)
|
184
165
|
module_files = relative_names(local_files, local_template_dir)
|
185
166
|
|
186
|
-
managed_modules = self.managed_modules(
|
167
|
+
managed_modules = self.managed_modules(config_path(options[:managed_modules_conf], options),
|
187
168
|
options[:filter],
|
188
169
|
options[:negative_filter])
|
189
170
|
|
@@ -191,7 +172,8 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
191
172
|
# managed_modules is either an array or a hash
|
192
173
|
managed_modules.each do |puppet_module, module_options|
|
193
174
|
begin
|
194
|
-
|
175
|
+
mod_options = module_options.nil? ? nil : Util.symbolize_keys(module_options)
|
176
|
+
manage_module(puppet_module, module_files, mod_options, defaults, options)
|
195
177
|
rescue # rubocop:disable Lint/RescueWithoutErrorClass
|
196
178
|
$stderr.puts "Error while updating #{puppet_module}"
|
197
179
|
raise unless options[:skip_broken]
|
@@ -201,4 +183,40 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
201
183
|
end
|
202
184
|
exit 1 if errors && options[:fail_on_warnings]
|
203
185
|
end
|
186
|
+
|
187
|
+
def self.pr(module_options)
|
188
|
+
module_options ||= {}
|
189
|
+
github_conf = module_options[:github]
|
190
|
+
gitlab_conf = module_options[:gitlab]
|
191
|
+
|
192
|
+
if !github_conf.nil?
|
193
|
+
base_url = github_conf[:base_url] || ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com')
|
194
|
+
require 'modulesync/pr/github'
|
195
|
+
ModuleSync::PR::GitHub.new(github_conf[:token], base_url)
|
196
|
+
elsif !gitlab_conf.nil?
|
197
|
+
base_url = gitlab_conf[:base_url] || ENV.fetch('GITLAB_BASE_URL', 'https://gitlab.com/api/v4')
|
198
|
+
require 'modulesync/pr/gitlab'
|
199
|
+
ModuleSync::PR::GitLab.new(gitlab_conf[:token], base_url)
|
200
|
+
elsif @pr.nil?
|
201
|
+
$stderr.puts 'No GitHub or GitLab token specified for --pr!'
|
202
|
+
raise
|
203
|
+
else
|
204
|
+
@pr
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.create_pr_manager
|
209
|
+
github_token = ENV.fetch('GITHUB_TOKEN', '')
|
210
|
+
gitlab_token = ENV.fetch('GITLAB_TOKEN', '')
|
211
|
+
|
212
|
+
if !github_token.empty?
|
213
|
+
require 'modulesync/pr/github'
|
214
|
+
ModuleSync::PR::GitHub.new(github_token, ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com'))
|
215
|
+
elsif !gitlab_token.empty?
|
216
|
+
require 'modulesync/pr/gitlab'
|
217
|
+
ModuleSync::PR::GitLab.new(gitlab_token, ENV.fetch('GITLAB_BASE_URL', 'https://gitlab.com/api/v4'))
|
218
|
+
else
|
219
|
+
warn '--pr specified without environment variables GITHUB_TOKEN or GITLAB_TOKEN'
|
220
|
+
end
|
221
|
+
end
|
204
222
|
end
|
data/lib/modulesync/cli.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'thor'
|
2
|
+
|
2
3
|
require 'modulesync'
|
4
|
+
require 'modulesync/cli/thor'
|
3
5
|
require 'modulesync/constants'
|
4
6
|
require 'modulesync/util'
|
5
7
|
|
6
8
|
module ModuleSync
|
7
|
-
|
9
|
+
module CLI
|
8
10
|
def self.defaults
|
9
11
|
@defaults ||= Util.symbolize_keys(Util.parse_config(Constants::MODULESYNC_CONF_FILE))
|
10
12
|
end
|
@@ -36,15 +38,14 @@ module ModuleSync
|
|
36
38
|
class Base < Thor
|
37
39
|
class_option :project_root,
|
38
40
|
:aliases => '-c',
|
39
|
-
:desc => 'Path used by git to clone modules into.
|
41
|
+
:desc => 'Path used by git to clone modules into.',
|
40
42
|
:default => CLI.defaults[:project_root] || 'modules'
|
41
43
|
class_option :git_base,
|
42
44
|
:desc => 'Specify the base part of a git URL to pull from',
|
43
45
|
:default => CLI.defaults[:git_base] || 'git@github.com:'
|
44
46
|
class_option :namespace,
|
45
47
|
:aliases => '-n',
|
46
|
-
:desc => 'Remote github namespace (user or organization) to clone from and push to.'
|
47
|
-
' Defaults to puppetlabs',
|
48
|
+
:desc => 'Remote github namespace (user or organization) to clone from and push to.',
|
48
49
|
:default => CLI.defaults[:namespace] || 'puppetlabs'
|
49
50
|
class_option :filter,
|
50
51
|
:aliases => '-f',
|
@@ -67,6 +68,8 @@ module ModuleSync
|
|
67
68
|
:aliases => '-c',
|
68
69
|
:desc => 'The local directory or remote repository to define the list of managed modules,' \
|
69
70
|
' the file templates, and the default values for template variables.'
|
71
|
+
option :managed_modules_conf,
|
72
|
+
:desc => 'The file name to define the list of managed modules'
|
70
73
|
option :remote_branch,
|
71
74
|
:aliases => '-r',
|
72
75
|
:desc => 'Remote branch name to push the changes to. Defaults to the branch name.',
|
@@ -90,15 +93,18 @@ module ModuleSync
|
|
90
93
|
:default => false
|
91
94
|
option :pr,
|
92
95
|
:type => :boolean,
|
93
|
-
:desc => 'Submit
|
96
|
+
:desc => 'Submit pull/merge request',
|
94
97
|
:default => false
|
95
98
|
option :pr_title,
|
96
|
-
:desc => 'Title of
|
99
|
+
:desc => 'Title of pull/merge request',
|
97
100
|
:default => CLI.defaults[:pr_title] || 'Update to module template files'
|
98
101
|
option :pr_labels,
|
99
102
|
:type => :array,
|
100
|
-
:desc => 'Labels to add to the
|
103
|
+
:desc => 'Labels to add to the pull/merge request',
|
101
104
|
:default => CLI.defaults[:pr_labels] || []
|
105
|
+
option :pr_target_branch,
|
106
|
+
:desc => 'Target branch for the pull/merge request',
|
107
|
+
:default => CLI.defaults[:pr_target_branch] || 'master'
|
102
108
|
option :offline,
|
103
109
|
:type => :boolean,
|
104
110
|
:desc => 'Do not run any Git commands. Allows the user to manage Git outside of ModuleSync.',
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'modulesync/cli'
|
3
|
+
|
4
|
+
module ModuleSync
|
5
|
+
module CLI
|
6
|
+
# Workaround some, still unfixed, Thor behaviors
|
7
|
+
#
|
8
|
+
# This class extends ::Thor class to
|
9
|
+
# - exit with status code sets to `1` on Thor failure (e.g. missing required option)
|
10
|
+
# - exit with status code sets to `1` when user calls `msync` (or a subcommand) without required arguments
|
11
|
+
class Thor < ::Thor
|
12
|
+
desc '_invalid_command_call', 'Invalid command', hide: true
|
13
|
+
def _invalid_command_call
|
14
|
+
self.class.new.help
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
default_task :_invalid_command_call
|
18
|
+
|
19
|
+
def self.exit_on_failure?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/modulesync/git.rb
CHANGED
@@ -150,7 +150,7 @@ module ModuleSync
|
|
150
150
|
tag(repo, new, options[:tag_pattern]) if options[:tag]
|
151
151
|
end
|
152
152
|
rescue ::Git::GitExecuteError => git_error
|
153
|
-
if git_error.message
|
153
|
+
if git_error.message.match?(/working (directory|tree) clean/)
|
154
154
|
puts "There were no files to update in #{name}. Not committing."
|
155
155
|
return false
|
156
156
|
else
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'modulesync/util'
|
3
|
+
|
4
|
+
module ModuleSync
|
5
|
+
module PR
|
6
|
+
# GitHub creates and manages pull requests on github.com or GitHub
|
7
|
+
# Enterprise installations.
|
8
|
+
class GitHub
|
9
|
+
def initialize(token, endpoint)
|
10
|
+
Octokit.configure do |c|
|
11
|
+
c.api_endpoint = endpoint
|
12
|
+
end
|
13
|
+
@api = Octokit::Client.new(:access_token => token)
|
14
|
+
end
|
15
|
+
|
16
|
+
def manage(namespace, module_name, options)
|
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 PR '#{options[:pr_title]}' to #{repo_path} " \
|
25
|
+
"- merges #{branch} into #{target_branch}"
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
pull_requests = @api.pull_requests(repo_path,
|
30
|
+
:state => 'open',
|
31
|
+
:base => target_branch,
|
32
|
+
:head => head)
|
33
|
+
unless pull_requests.empty?
|
34
|
+
# Skip creating the PR if it exists already.
|
35
|
+
$stdout.puts "Skipped! #{pull_requests.length} PRs found for branch #{branch}"
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
pr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
|
40
|
+
pr = @api.create_pull_request(repo_path,
|
41
|
+
target_branch,
|
42
|
+
branch,
|
43
|
+
options[:pr_title],
|
44
|
+
options[:message])
|
45
|
+
$stdout.puts \
|
46
|
+
"Submitted PR '#{options[:pr_title]}' to #{repo_path} " \
|
47
|
+
"- merges #{branch} into #{target_branch}"
|
48
|
+
|
49
|
+
# We only assign labels to the PR if we've discovered a list > 1. The labels MUST
|
50
|
+
# already exist. We DO NOT create missing labels.
|
51
|
+
return if pr_labels.empty?
|
52
|
+
$stdout.puts "Attaching the following labels to PR #{pr['number']}: #{pr_labels.join(', ')}"
|
53
|
+
@api.add_labels_to_an_issue(repo_path, pr['number'], pr_labels)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'gitlab'
|
2
|
+
require 'modulesync/util'
|
3
|
+
|
4
|
+
module ModuleSync
|
5
|
+
module PR
|
6
|
+
# GitLab creates and manages merge requests on gitlab.com or private GitLab
|
7
|
+
# installations.
|
8
|
+
class GitLab
|
9
|
+
def initialize(token, endpoint)
|
10
|
+
@api = Gitlab::Client.new(
|
11
|
+
:endpoint => endpoint,
|
12
|
+
:private_token => token
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def manage(namespace, module_name, options)
|
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
|
28
|
+
|
29
|
+
merge_requests = @api.merge_requests(repo_path,
|
30
|
+
:state => 'opened',
|
31
|
+
:source_branch => head,
|
32
|
+
:target_branch => target_branch)
|
33
|
+
unless merge_requests.empty?
|
34
|
+
# Skip creating the MR if it exists already.
|
35
|
+
$stdout.puts "Skipped! #{merge_requests.length} MRs found for branch #{branch}"
|
36
|
+
return
|
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(', ')}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
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
|