modulesync 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +18 -3
  3. data/.github/workflows/release.yml +2 -4
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +14 -8
  6. data/.rubocop_todo.yml +25 -17
  7. data/.simplecov +46 -0
  8. data/CHANGELOG.md +32 -0
  9. data/Gemfile +1 -1
  10. data/bin/msync +17 -1
  11. data/features/cli.feature +12 -6
  12. data/features/execute.feature +51 -0
  13. data/features/hook.feature +5 -8
  14. data/features/push.feature +46 -0
  15. data/features/reset.feature +57 -0
  16. data/features/step_definitions/git_steps.rb +29 -1
  17. data/features/support/env.rb +9 -0
  18. data/features/update/bump_version.feature +8 -12
  19. data/features/update/dot_sync.feature +52 -0
  20. data/features/update/pull_request.feature +180 -0
  21. data/features/update.feature +74 -103
  22. data/lib/modulesync/cli/thor.rb +12 -0
  23. data/lib/modulesync/cli.rb +122 -28
  24. data/lib/modulesync/git_service/base.rb +63 -0
  25. data/lib/modulesync/git_service/factory.rb +28 -0
  26. data/lib/modulesync/{pr → git_service}/github.rb +23 -21
  27. data/lib/modulesync/git_service/gitlab.rb +62 -0
  28. data/lib/modulesync/git_service.rb +96 -0
  29. data/lib/modulesync/hook.rb +11 -13
  30. data/lib/modulesync/renderer.rb +3 -6
  31. data/lib/modulesync/repository.rb +71 -25
  32. data/lib/modulesync/settings.rb +0 -1
  33. data/lib/modulesync/source_code.rb +28 -2
  34. data/lib/modulesync/util.rb +4 -4
  35. data/lib/modulesync.rb +104 -66
  36. data/modulesync.gemspec +7 -4
  37. data/spec/helpers/faker/puppet_module_remote_repo.rb +16 -1
  38. data/spec/spec_helper.rb +1 -23
  39. data/spec/unit/modulesync/git_service/factory_spec.rb +16 -0
  40. data/spec/unit/modulesync/git_service/github_spec.rb +81 -0
  41. data/spec/unit/modulesync/git_service/gitlab_spec.rb +90 -0
  42. data/spec/unit/modulesync/git_service_spec.rb +201 -0
  43. data/spec/unit/modulesync/source_code_spec.rb +22 -0
  44. data/spec/unit/modulesync_spec.rb +0 -12
  45. metadata +74 -12
  46. data/lib/modulesync/pr/gitlab.rb +0 -54
  47. data/spec/unit/modulesync/pr/github_spec.rb +0 -49
  48. data/spec/unit/modulesync/pr/gitlab_spec.rb +0 -81
@@ -7,31 +7,34 @@ require 'modulesync/util'
7
7
 
8
8
  module ModuleSync
9
9
  module CLI
10
+ def self.prepare_options(cli_options, **more_options)
11
+ options = CLI.defaults
12
+ options.merge! Util.symbolize_keys(cli_options)
13
+ options.merge! more_options
14
+
15
+ Util.symbolize_keys options
16
+ end
17
+
10
18
  def self.defaults
11
19
  @defaults ||= Util.symbolize_keys(Util.parse_config(Constants::MODULESYNC_CONF_FILE))
12
20
  end
13
21
 
14
22
  class Hook < Thor
15
- class_option :project_root,
16
- :aliases => '-c',
17
- :desc => 'Path used by git to clone modules into. Defaults to "modules"',
18
- :default => CLI.defaults[:project_root] || 'modules'
19
- class_option :hook_args,
20
- :aliases => '-a',
21
- :desc => 'Arguments to pass to msync in the git hook'
22
-
23
+ option :hook_args,
24
+ :aliases => '-a',
25
+ :desc => 'Arguments to pass to msync in the git hook'
26
+ option :branch,
27
+ :aliases => '-b',
28
+ :desc => 'Branch name to pass to msync in the git hook',
29
+ :default => CLI.defaults[:branch]
23
30
  desc 'activate', 'Activate the git hook.'
24
31
  def activate
25
- config = { :command => 'hook' }.merge(options)
26
- config[:hook] = 'activate'
27
- ModuleSync.hook(config)
32
+ ModuleSync.hook CLI.prepare_options(options, hook: 'activate')
28
33
  end
29
34
 
30
35
  desc 'deactivate', 'Deactivate the git hook.'
31
36
  def deactivate
32
- config = { :command => 'hook' }.merge(options)
33
- config[:hook] = 'deactivate'
34
- ModuleSync.hook(config)
37
+ ModuleSync.hook CLI.prepare_options(options, hook: 'deactivate')
35
38
  end
36
39
  end
37
40
 
@@ -39,7 +42,7 @@ module ModuleSync
39
42
  class_option :project_root,
40
43
  :aliases => '-c',
41
44
  :desc => 'Path used by git to clone modules into.',
42
- :default => CLI.defaults[:project_root] || 'modules'
45
+ :default => CLI.defaults[:project_root]
43
46
  class_option :git_base,
44
47
  :desc => 'Specify the base part of a git URL to pull from',
45
48
  :default => CLI.defaults[:git_base] || 'git@github.com:'
@@ -53,11 +56,11 @@ module ModuleSync
53
56
  class_option :negative_filter,
54
57
  :aliases => '-x',
55
58
  :desc => 'A regular expression to skip repositories.'
56
- class_option :branch,
57
- :aliases => '-b',
58
- :desc => 'Branch name to make the changes in.' \
59
- ' Defaults to the default branch of the upstream repository, but falls back to "master".',
60
- :default => CLI.defaults[:branch]
59
+ class_option :verbose,
60
+ :aliases => '-v',
61
+ :desc => 'Verbose mode',
62
+ :type => :boolean,
63
+ :default => false
61
64
 
62
65
  desc 'update', 'Update the modules in managed_modules.yml'
63
66
  option :message,
@@ -67,7 +70,7 @@ module ModuleSync
67
70
  option :configs,
68
71
  :aliases => '-c',
69
72
  :desc => 'The local directory or remote repository to define the list of managed modules,' \
70
- ' the file templates, and the default values for template variables.'
73
+ ' the file templates, and the default values for template variables.'
71
74
  option :managed_modules_conf,
72
75
  :desc => 'The file name to define the list of managed modules'
73
76
  option :remote_branch,
@@ -104,7 +107,7 @@ module ModuleSync
104
107
  :default => CLI.defaults[:pr_labels] || []
105
108
  option :pr_target_branch,
106
109
  :desc => 'Target branch for the pull/merge request',
107
- :default => CLI.defaults[:pr_target_branch] || 'master'
110
+ :default => CLI.defaults[:pr_target_branch]
108
111
  option :offline,
109
112
  :type => :boolean,
110
113
  :desc => 'Do not run any Git commands. Allows the user to manage Git outside of ModuleSync.',
@@ -130,17 +133,108 @@ module ModuleSync
130
133
  :type => :boolean,
131
134
  :aliases => '-F',
132
135
  :desc => 'Produce a failure exit code when there are warnings' \
133
- ' (only has effect when --skip_broken is enabled)',
136
+ ' (only has effect when --skip_broken is enabled)',
134
137
  :default => false
135
-
138
+ option :branch,
139
+ :aliases => '-b',
140
+ :desc => 'Branch name to make the changes in.' \
141
+ ' Defaults to the default branch of the upstream repository, but falls back to "master".',
142
+ :default => CLI.defaults[:branch]
136
143
  def update
137
- config = { :command => 'update' }.merge(options)
138
- config = Util.symbolize_keys(config)
144
+ config = CLI.prepare_options(options)
139
145
  raise Thor::Error, 'No value provided for required option "--message"' unless config[:noop] \
140
146
  || config[:message] \
141
147
  || config[:offline]
142
- config[:git_opts] = { 'amend' => config[:amend], 'force' => config[:force] }
143
- ModuleSync.update(config)
148
+
149
+ ModuleSync.update config
150
+ end
151
+
152
+ desc 'execute [OPTIONS] -- COMMAND..', 'Execute the command in each managed modules'
153
+ long_desc <<~DESC
154
+ Execute the command in each managed modules.
155
+
156
+ COMMAND can be an absolute or a relative path.
157
+
158
+ To ease running local commands, a relative path is expanded with the current user directory but only if the target file exists.
159
+
160
+ Example: `msync exec custom-scripts/true` will run "$PWD/custom-scripts/true" in each repository.
161
+
162
+ As side effect, you can shadow system binary if a local file is present:
163
+ \x5 `msync exec true` will run "$PWD/true", not `/bin/true` if "$PWD/true" exists.
164
+ DESC
165
+
166
+ option :configs,
167
+ :aliases => '-c',
168
+ :desc => 'The local directory or remote repository to define the list of managed modules,' \
169
+ ' the file templates, and the default values for template variables.'
170
+ option :managed_modules_conf,
171
+ :desc => 'The file name to define the list of managed modules'
172
+ option :branch,
173
+ :aliases => '-b',
174
+ :desc => 'Branch name to make the changes in.',
175
+ :default => CLI.defaults[:branch]
176
+ option :fail_fast,
177
+ :type => :boolean,
178
+ :desc => 'Abort the run after a command execution failure',
179
+ :default => CLI.defaults[:fail_fast].nil? ? true : CLI.defaults[:fail_fast]
180
+ def execute(*command_args)
181
+ raise Thor::Error, 'COMMAND is a required argument' if command_args.empty?
182
+
183
+ ModuleSync.execute CLI.prepare_options(options, command_args: command_args)
184
+ end
185
+
186
+ desc 'reset', 'Reset local repositories to a well-known state'
187
+ long_desc <<~DESC
188
+ Reset local repository to a well-known state:
189
+ \x5 * Switch local repositories to specified branch
190
+ \x5 * Fetch and prune repositories unless running with `--offline` option
191
+ \x5 * Hard-reset any changes to specified source branch, technically any git refs, e.g. `main`, `origin/wip`
192
+ \x5 * Clean all extra local files
193
+
194
+ Note: If a repository is not already cloned, it will operate the following to reach to well-known state:
195
+ \x5 * Clone the repository
196
+ \x5 * Switch to specified branch
197
+ DESC
198
+ option :configs,
199
+ :aliases => '-c',
200
+ :desc => 'The local directory or remote repository to define the list of managed modules,' \
201
+ ' the file templates, and the default values for template variables.'
202
+ option :managed_modules_conf,
203
+ :desc => 'The file name to define the list of managed modules'
204
+ option :branch,
205
+ :aliases => '-b',
206
+ :desc => 'Branch name to make the changes in.',
207
+ :default => CLI.defaults[:branch]
208
+ option :offline,
209
+ :type => :boolean,
210
+ :desc => 'Only proceed local operations',
211
+ :default => false
212
+ option :source_branch,
213
+ :desc => 'Branch to reset from (e.g. origin/wip)'
214
+ def reset
215
+ ModuleSync.reset CLI.prepare_options(options)
216
+ end
217
+
218
+ desc 'push', 'Push all available commits from branch to remote'
219
+ option :configs,
220
+ :aliases => '-c',
221
+ :desc => 'The local directory or remote repository to define the list of managed modules,' \
222
+ ' the file templates, and the default values for template variables.'
223
+ option :managed_modules_conf,
224
+ :desc => 'The file name to define the list of managed modules'
225
+ option :branch,
226
+ :aliases => '-b',
227
+ :desc => 'Branch name to push',
228
+ :default => CLI.defaults[:branch]
229
+ option :remote_branch,
230
+ :desc => 'Remote branch to push to (e.g. maintenance)'
231
+ def push
232
+ ModuleSync.push CLI.prepare_options(options)
233
+ end
234
+
235
+ desc 'clone', 'Clone repositories that need to'
236
+ def clone
237
+ ModuleSync.clone CLI.prepare_options(options)
144
238
  end
145
239
 
146
240
  desc 'hook', 'Activate or deactivate a git hook.'
@@ -0,0 +1,63 @@
1
+ module ModuleSync
2
+ module GitService
3
+ # Generic class for git services
4
+ class Base
5
+ def open_pull_request(repo_path:, namespace:, title:, message:, source_branch:, target_branch:, labels:, noop:) # rubocop:disable Metrics/ParameterLists
6
+ unless source_branch != target_branch
7
+ raise ModuleSync::Error,
8
+ "Unable to open a pull request with the same source and target branch: '#{source_branch}'"
9
+ end
10
+
11
+ _open_pull_request(
12
+ repo_path: repo_path,
13
+ namespace: namespace,
14
+ title: title,
15
+ message: message,
16
+ source_branch: source_branch,
17
+ target_branch: target_branch,
18
+ labels: labels,
19
+ noop: noop,
20
+ )
21
+ end
22
+
23
+ # This method attempts to guess the git service endpoint based on remote
24
+ def self.guess_endpoint_from(remote:)
25
+ hostname = extract_hostname(remote)
26
+ return nil if hostname.nil?
27
+
28
+ "https://#{hostname}"
29
+ end
30
+
31
+ # This method extracts hostname from URL like:
32
+ #
33
+ # - ssh://[user@]host.xz[:port]/path/to/repo.git/
34
+ # - git://host.xz[:port]/path/to/repo.git/
35
+ # - [user@]host.xz:path/to/repo.git/
36
+ # - http[s]://host.xz[:port]/path/to/repo.git/
37
+ # - ftp[s]://host.xz[:port]/path/to/repo.git/
38
+ #
39
+ # Returns nil if
40
+ # - /path/to/repo.git/
41
+ # - file:///path/to/repo.git/
42
+ # - any invalid URL
43
+ def self.extract_hostname(url)
44
+ return nil if url.start_with?('/') || url.start_with?('file://') # local path (e.g. file:///path/to/repo)
45
+
46
+ unless url.start_with?(%r{[a-z]+://}) # SSH notation does not contain protocol (e.g. user@server:path/to/repo/)
47
+ pattern = /^(?<user>.*@)?(?<hostname>[\w|.]*):(?<repo>.*)$/ # SSH path (e.g. user@server:repo)
48
+ return url.match(pattern)[:hostname] if url.match?(pattern)
49
+ end
50
+
51
+ URI.parse(url).host
52
+ rescue URI::InvalidURIError
53
+ nil
54
+ end
55
+
56
+ protected
57
+
58
+ def _open_pull_request(repo_path:, namespace:, title:, message:, source_branch:, target_branch:, labels:, noop:) # rubocop:disable Metrics/ParameterLists
59
+ raise NotImplementedError
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,28 @@
1
+ module ModuleSync
2
+ module GitService
3
+ # Git service's factory
4
+ module Factory
5
+ def self.instantiate(type:, endpoint:, token:)
6
+ raise MissingCredentialsError, <<~MESSAGE if token.nil?
7
+ A token is required to use services from #{type}:
8
+ Please set environment variable: "#{type.upcase}_TOKEN" or set the token entry in module options.
9
+ MESSAGE
10
+
11
+ klass(type: type).new token, endpoint
12
+ end
13
+
14
+ def self.klass(type:)
15
+ case type
16
+ when :github
17
+ require 'modulesync/git_service/github'
18
+ ModuleSync::GitService::GitHub
19
+ when :gitlab
20
+ require 'modulesync/git_service/gitlab'
21
+ ModuleSync::GitService::GitLab
22
+ else
23
+ raise NotImplementedError, "Unknown git service: '#{type}'"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,28 +1,30 @@
1
+ require 'modulesync/git_service'
2
+ require 'modulesync/git_service/base'
1
3
  require 'octokit'
2
- require 'modulesync/util'
3
4
 
4
5
  module ModuleSync
5
- module PR
6
+ module GitService
6
7
  # GitHub creates and manages pull requests on github.com or GitHub
7
8
  # Enterprise installations.
8
- class GitHub
9
+ class GitHub < Base
9
10
  def initialize(token, endpoint)
11
+ super()
12
+
10
13
  Octokit.configure do |c|
11
14
  c.api_endpoint = endpoint
12
15
  end
13
16
  @api = Octokit::Client.new(:access_token => token)
14
17
  end
15
18
 
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'
19
+ private
20
+
21
+ def _open_pull_request(repo_path:, namespace:, title:, message:, source_branch:, target_branch:, labels:, noop:) # rubocop:disable Metrics/ParameterLists
22
+ head = "#{namespace}:#{source_branch}"
21
23
 
22
- if options[:noop]
24
+ if noop
23
25
  $stdout.puts \
24
- "Using no-op. Would submit PR '#{options[:pr_title]}' to #{repo_path} " \
25
- "- merges #{branch} into #{target_branch}"
26
+ "Using no-op. Would submit PR '#{title}' to '#{repo_path}' " \
27
+ "- merges '#{source_branch}' into '#{target_branch}'"
26
28
  return
27
29
  end
28
30
 
@@ -32,25 +34,25 @@ module ModuleSync
32
34
  :head => head)
33
35
  unless pull_requests.empty?
34
36
  # Skip creating the PR if it exists already.
35
- $stdout.puts "Skipped! #{pull_requests.length} PRs found for branch #{branch}"
37
+ $stdout.puts "Skipped! #{pull_requests.length} PRs found for branch '#{source_branch}'"
36
38
  return
37
39
  end
38
40
 
39
- pr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
40
41
  pr = @api.create_pull_request(repo_path,
41
42
  target_branch,
42
- branch,
43
- options[:pr_title],
44
- options[:message])
43
+ source_branch,
44
+ title,
45
+ message)
45
46
  $stdout.puts \
46
- "Submitted PR '#{options[:pr_title]}' to #{repo_path} " \
47
- "- merges #{branch} into #{target_branch}"
47
+ "Submitted PR '#{title}' to '#{repo_path}' " \
48
+ "- merges #{source_branch} into #{target_branch}"
48
49
 
49
50
  # We only assign labels to the PR if we've discovered a list > 1. The labels MUST
50
51
  # 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)
52
+ return if labels.empty?
53
+
54
+ $stdout.puts "Attaching the following labels to PR #{pr['number']}: #{labels.join(', ')}"
55
+ @api.add_labels_to_an_issue(repo_path, pr['number'], labels)
54
56
  end
55
57
  end
56
58
  end
@@ -0,0 +1,62 @@
1
+ require 'gitlab'
2
+ require 'modulesync/git_service'
3
+ require 'modulesync/git_service/base'
4
+
5
+ module ModuleSync
6
+ module GitService
7
+ # GitLab creates and manages merge requests on gitlab.com or private GitLab
8
+ # installations.
9
+ class GitLab < Base
10
+ def initialize(token, endpoint)
11
+ super()
12
+
13
+ @api = Gitlab::Client.new(
14
+ :endpoint => endpoint,
15
+ :private_token => token,
16
+ )
17
+ end
18
+
19
+ def self.guess_endpoint_from(remote:)
20
+ endpoint = super
21
+ return nil if endpoint.nil?
22
+
23
+ endpoint += '/api/v4'
24
+ endpoint
25
+ end
26
+
27
+ private
28
+
29
+ def _open_pull_request(repo_path:, namespace:, title:, message:, source_branch:, target_branch:, labels:, noop:) # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument
30
+ if noop
31
+ $stdout.puts \
32
+ "Using no-op. Would submit MR '#{title}' to '#{repo_path}' " \
33
+ "- merges #{source_branch} into #{target_branch}"
34
+ return
35
+ end
36
+
37
+ merge_requests = @api.merge_requests(repo_path,
38
+ :state => 'opened',
39
+ :source_branch => source_branch,
40
+ :target_branch => target_branch)
41
+ unless merge_requests.empty?
42
+ # Skip creating the MR if it exists already.
43
+ $stdout.puts "Skipped! #{merge_requests.length} MRs found for branch '#{source_branch}'"
44
+ return
45
+ end
46
+
47
+ mr = @api.create_merge_request(repo_path,
48
+ title,
49
+ :source_branch => source_branch,
50
+ :target_branch => target_branch,
51
+ :labels => labels)
52
+ $stdout.puts \
53
+ "Submitted MR '#{title}' to '#{repo_path}' " \
54
+ "- merges '#{source_branch}' into '#{target_branch}'"
55
+
56
+ return if labels.empty?
57
+
58
+ $stdout.puts "Attached the following labels to MR #{mr.iid}: #{labels.join(', ')}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,96 @@
1
+ module ModuleSync
2
+ class Error < StandardError; end
3
+
4
+ # Namespace for Git service classes (ie. GitHub, GitLab)
5
+ module GitService
6
+ class MissingCredentialsError < Error; end
7
+
8
+ class UnguessableTypeError < Error; end
9
+
10
+ def self.configuration_for(sourcecode:)
11
+ type = type_for(sourcecode: sourcecode)
12
+
13
+ {
14
+ type: type,
15
+ endpoint: endpoint_for(sourcecode: sourcecode, type: type),
16
+ token: token_for(sourcecode: sourcecode, type: type),
17
+ }
18
+ end
19
+
20
+ # This method attempts to guess git service's type (ie. gitlab or github)
21
+ # It process in this order
22
+ # 1. use module specific configuration entry (ie. a specific entry named `gitlab` or `github`)
23
+ # 2. guess using remote url (ie. looking for `github` or `gitlab` string)
24
+ # 3. use environment variables (ie. check if GITHUB_TOKEN or GITLAB_TOKEN is set)
25
+ # 4. fail
26
+ def self.type_for(sourcecode:)
27
+ return :github unless sourcecode.options[:github].nil?
28
+ return :gitlab unless sourcecode.options[:gitlab].nil?
29
+ return :github if sourcecode.repository_remote.include? 'github'
30
+ return :gitlab if sourcecode.repository_remote.include? 'gitlab'
31
+
32
+ if ENV['GITLAB_TOKEN'].nil? && ENV['GITHUB_TOKEN'].nil?
33
+ raise UnguessableTypeError, <<~MESSAGE
34
+ Unable to guess Git service type without GITLAB_TOKEN or GITHUB_TOKEN sets.
35
+ MESSAGE
36
+ end
37
+
38
+ unless ENV['GITLAB_TOKEN'].nil? || ENV['GITHUB_TOKEN'].nil?
39
+ raise UnguessableTypeError, <<~MESSAGE
40
+ Unable to guess Git service type with both GITLAB_TOKEN and GITHUB_TOKEN sets.
41
+
42
+ Please set the wanted one in configuration (ie. add `gitlab:` or `github:` key)
43
+ MESSAGE
44
+ end
45
+
46
+ return :github unless ENV['GITHUB_TOKEN'].nil?
47
+ return :gitlab unless ENV['GITLAB_TOKEN'].nil?
48
+
49
+ raise NotImplementedError
50
+ end
51
+
52
+ # This method attempts to find git service's endpoint based on sourcecode and type
53
+ # It process in this order
54
+ # 1. use module specific configuration (ie. `base_url`)
55
+ # 2. use environment variable dependending on type (e.g. GITLAB_BASE_URL)
56
+ # 3. guess using the git remote url
57
+ # 4. fail
58
+ def self.endpoint_for(sourcecode:, type:)
59
+ endpoint = sourcecode.options.dig(type, :base_url)
60
+
61
+ endpoint ||= case type
62
+ when :github
63
+ ENV['GITHUB_BASE_URL']
64
+ when :gitlab
65
+ ENV['GITLAB_BASE_URL']
66
+ end
67
+
68
+ endpoint ||= GitService::Factory.klass(type: type).guess_endpoint_from(remote: sourcecode.repository_remote)
69
+
70
+ raise NotImplementedError, <<~MESSAGE if endpoint.nil?
71
+ Unable to guess endpoint for remote: '#{sourcecode.repository_remote}'
72
+ Please provide `base_url` option in configuration file
73
+ MESSAGE
74
+
75
+ endpoint
76
+ end
77
+
78
+ # This method attempts to find the token associated to provided sourcecode and type
79
+ # It process in this order:
80
+ # 1. use module specific configuration (ie. `token`)
81
+ # 2. use environment variable depending on type (e.g. GITLAB_TOKEN)
82
+ # 3. fail
83
+ def self.token_for(sourcecode:, type:)
84
+ token = sourcecode.options.dig(type, :token)
85
+
86
+ token ||= case type
87
+ when :github
88
+ ENV['GITHUB_TOKEN']
89
+ when :gitlab
90
+ ENV['GITLAB_TOKEN']
91
+ end
92
+
93
+ token
94
+ end
95
+ end
96
+ end
@@ -6,20 +6,20 @@ module ModuleSync
6
6
 
7
7
  def initialize(hook_file, options = [])
8
8
  @hook_file = hook_file
9
- @namespace = options['namespace']
10
- @branch = options['branch']
11
- @args = options['hook_args']
9
+ @namespace = options[:namespace]
10
+ @branch = options[:branch]
11
+ @args = options[:hook_args]
12
12
  end
13
13
 
14
14
  def content(arguments)
15
- <<-CONTENT
16
- #!/usr/bin/env bash
15
+ <<~CONTENT
16
+ #!/usr/bin/env bash
17
17
 
18
- current_branch=\`git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,'\`
19
- git_dir=\`git rev-parse --show-toplevel\`
20
- message=\`git log -1 --format=%B\`
21
- msync -m "\$message" #{arguments}
22
- CONTENT
18
+ current_branch=\`git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,'\`
19
+ git_dir=\`git rev-parse --show-toplevel\`
20
+ message=\`git log -1 --format=%B\`
21
+ msync -m "\$message" #{arguments}
22
+ CONTENT
23
23
  end
24
24
 
25
25
  def activate
@@ -28,9 +28,7 @@ CONTENT
28
28
  hook_args << "-b #{branch}" if branch
29
29
  hook_args << args if args
30
30
 
31
- File.open(hook_file, 'w') do |file|
32
- file.write(content(hook_args.join(' ')))
33
- end
31
+ File.write(hook_file, content(hook_args.join(' ')))
34
32
  end
35
33
 
36
34
  def deactivate
@@ -12,7 +12,7 @@ module ModuleSync
12
12
 
13
13
  def self.build(target_name)
14
14
  template_file = if !File.exist?("#{target_name}.erb") && File.exist?(target_name)
15
- STDERR.puts "Warning: using '#{target_name}' as template without '.erb' suffix"
15
+ $stderr.puts "Warning: using '#{target_name}' as template without '.erb' suffix"
16
16
  target_name
17
17
  else
18
18
  "#{target_name}.erb"
@@ -32,11 +32,8 @@ module ModuleSync
32
32
  end
33
33
 
34
34
  def self.sync(template, target_name)
35
- path = target_name.rpartition('/').first
36
- FileUtils.mkdir_p(path) unless path.empty?
37
- File.open(target_name, 'w') do |file|
38
- file.write(template)
39
- end
35
+ FileUtils.mkdir_p(File.dirname(target_name))
36
+ File.write(target_name, template)
40
37
  end
41
38
  end
42
39
  end