modulesync 1.1.0 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|