modulesync 2.0.1 → 2.2.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 +34 -0
- data/.github/workflows/release.yml +32 -0
- data/.rubocop.yml +6 -1
- data/.rubocop_todo.yml +16 -35
- data/CHANGELOG.md +62 -0
- data/Gemfile +6 -4
- data/LICENSE +173 -12
- data/README.md +34 -4
- 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 +145 -345
- data/features/update/bad_context.feature +26 -0
- data/features/update/bump_version.feature +87 -0
- data/lib/modulesync.rb +60 -54
- data/lib/modulesync/cli.rb +5 -4
- data/lib/modulesync/cli/thor.rb +24 -0
- data/lib/modulesync/puppet_module.rb +37 -0
- data/lib/modulesync/repository.rb +162 -0
- data/lib/modulesync/source_code.rb +57 -0
- 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 +146 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/unit/modulesync_spec.rb +6 -2
- metadata +43 -10
- data/.travis.yml +0 -28
- data/lib/modulesync/git.rb +0 -194
@@ -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
|
+
"""
|
@@ -0,0 +1,87 @@
|
|
1
|
+
Feature: Bump a new version after an update
|
2
|
+
Scenario: Bump the module version, update changelog and tag it after an update that produces changes
|
3
|
+
Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
|
4
|
+
And the puppet module "puppet-test" from "fakenamespace" has a file named "CHANGELOG.md" with:
|
5
|
+
"""
|
6
|
+
## 1965-04-14 - Release 0.4.2
|
7
|
+
"""
|
8
|
+
And a file named "config_defaults.yml" with:
|
9
|
+
"""
|
10
|
+
---
|
11
|
+
new-file:
|
12
|
+
content: aruba
|
13
|
+
"""
|
14
|
+
And a directory named "moduleroot"
|
15
|
+
And a file named "moduleroot/new-file.erb" with:
|
16
|
+
"""
|
17
|
+
<%= @configs['content'] %>
|
18
|
+
"""
|
19
|
+
When I run `msync update --message "Add new-file" --bump --changelog --tag`
|
20
|
+
Then the exit status should be 0
|
21
|
+
And the file named "modules/fakenamespace/puppet-test/new-file" should contain "aruba"
|
22
|
+
And the stdout should contain:
|
23
|
+
"""
|
24
|
+
Bumped to version 0.4.3
|
25
|
+
"""
|
26
|
+
And the stdout should contain:
|
27
|
+
"""
|
28
|
+
Tagging with 0.4.3
|
29
|
+
"""
|
30
|
+
And the file named "modules/fakenamespace/puppet-test/CHANGELOG.md" should contain "0.4.3"
|
31
|
+
And the puppet module "puppet-test" from "fakenamespace" should have 2 commits made by "Aruba"
|
32
|
+
And the puppet module "puppet-test" from "fakenamespace" should have a tag named "0.4.3"
|
33
|
+
|
34
|
+
Scenario: Bump the module version after an update that produces changes
|
35
|
+
Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
|
36
|
+
And a file named "config_defaults.yml" with:
|
37
|
+
"""
|
38
|
+
---
|
39
|
+
new-file:
|
40
|
+
content: aruba
|
41
|
+
"""
|
42
|
+
And a directory named "moduleroot"
|
43
|
+
And a file named "moduleroot/new-file.erb" with:
|
44
|
+
"""
|
45
|
+
<%= @configs['content'] %>
|
46
|
+
"""
|
47
|
+
When I run `msync update --message "Add new-file" --bump`
|
48
|
+
Then the exit status should be 0
|
49
|
+
And the file named "modules/fakenamespace/puppet-test/new-file" should contain "aruba"
|
50
|
+
And the stdout should contain:
|
51
|
+
"""
|
52
|
+
Bumped to version 0.4.3
|
53
|
+
"""
|
54
|
+
And the puppet module "puppet-test" from "fakenamespace" should have 2 commits made by "Aruba"
|
55
|
+
And the puppet module "puppet-test" from "fakenamespace" should not have a tag named "0.4.3"
|
56
|
+
|
57
|
+
Scenario: Bump the module version with changelog update when no CHANGELOG.md is available
|
58
|
+
Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
|
59
|
+
And a file named "config_defaults.yml" with:
|
60
|
+
"""
|
61
|
+
---
|
62
|
+
new-file:
|
63
|
+
content: aruba
|
64
|
+
"""
|
65
|
+
And a directory named "moduleroot"
|
66
|
+
And a file named "moduleroot/new-file.erb" with:
|
67
|
+
"""
|
68
|
+
<%= @configs['content'] %>
|
69
|
+
"""
|
70
|
+
When I run `msync update --message "Add new-file" --bump --changelog`
|
71
|
+
Then the exit status should be 0
|
72
|
+
And the file named "modules/fakenamespace/puppet-test/new-file" should contain "aruba"
|
73
|
+
And the stdout should contain:
|
74
|
+
"""
|
75
|
+
Bumped to version 0.4.3
|
76
|
+
No CHANGELOG.md file found, not updating.
|
77
|
+
"""
|
78
|
+
And the file named "modules/fakenamespace/puppet-test/CHANGELOG.md" should not exist
|
79
|
+
And the puppet module "puppet-test" from "fakenamespace" should have 2 commits made by "Aruba"
|
80
|
+
|
81
|
+
Scenario: Dont bump the module version after an update that produces no changes
|
82
|
+
Given a basic setup with a puppet module "puppet-test" from "fakenamespace"
|
83
|
+
And a directory named "moduleroot"
|
84
|
+
When I run `msync update --message "Add new-file" --bump --tag`
|
85
|
+
Then the exit status should be 0
|
86
|
+
And the puppet module "puppet-test" from "fakenamespace" should have no commits made by "Aruba"
|
87
|
+
And the puppet module "puppet-test" from "fakenamespace" should not have a tag named "0.4.3"
|
data/lib/modulesync.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'pathname'
|
3
|
+
|
3
4
|
require 'modulesync/cli'
|
4
5
|
require 'modulesync/constants'
|
5
|
-
require 'modulesync/git'
|
6
6
|
require 'modulesync/hook'
|
7
|
+
require 'modulesync/puppet_module'
|
7
8
|
require 'modulesync/renderer'
|
8
9
|
require 'modulesync/settings'
|
9
10
|
require 'modulesync/util'
|
11
|
+
|
10
12
|
require 'monkey_patches'
|
11
13
|
|
12
14
|
module ModuleSync # rubocop:disable Metrics/ModuleLength
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
13
17
|
include Constants
|
14
18
|
|
15
19
|
def self.config_defaults
|
@@ -21,12 +25,12 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
21
25
|
}
|
22
26
|
end
|
23
27
|
|
24
|
-
def self.
|
25
|
-
|
28
|
+
def self.options
|
29
|
+
@options
|
26
30
|
end
|
27
31
|
|
28
|
-
def self.
|
29
|
-
File.join(
|
32
|
+
def self.local_file(config_path, file)
|
33
|
+
File.join(config_path, MODULE_FILES_DIR, file)
|
30
34
|
end
|
31
35
|
|
32
36
|
# List all template files.
|
@@ -39,10 +43,10 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
39
43
|
.collect { |p| p.chomp('.erb') }
|
40
44
|
.to_a
|
41
45
|
else
|
42
|
-
$
|
46
|
+
$stderr.puts "#{local_template_dir} does not exist." \
|
43
47
|
' Check that you are working in your module configs directory or' \
|
44
48
|
' that you have passed in the correct directory with -c.'
|
45
|
-
exit
|
49
|
+
exit 1
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
@@ -50,21 +54,20 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
50
54
|
file_list.map { |file| file.sub(/#{path}/, '') }
|
51
55
|
end
|
52
56
|
|
53
|
-
def self.managed_modules
|
57
|
+
def self.managed_modules
|
58
|
+
config_file = config_path(options[:managed_modules_conf], options)
|
59
|
+
filter = options[:filter]
|
60
|
+
negative_filter = options[:negative_filter]
|
61
|
+
|
54
62
|
managed_modules = Util.parse_config(config_file)
|
55
63
|
if managed_modules.empty?
|
56
|
-
$
|
64
|
+
$stderr.puts "No modules found in #{config_file}." \
|
57
65
|
' Check that you specified the right :configs directory and :managed_modules_conf file.'
|
58
|
-
exit
|
66
|
+
exit 1
|
59
67
|
end
|
60
68
|
managed_modules.select! { |m| m =~ Regexp.new(filter) } unless filter.nil?
|
61
69
|
managed_modules.reject! { |m| m =~ Regexp.new(negative_filter) } unless negative_filter.nil?
|
62
|
-
managed_modules
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.module_name(module_name, default_namespace)
|
66
|
-
return [default_namespace, module_name] unless module_name.include?('/')
|
67
|
-
ns, mod = module_name.split('/')
|
70
|
+
managed_modules.map { |given_name, options| PuppetModule.new(given_name, options) }
|
68
71
|
end
|
69
72
|
|
70
73
|
def self.hook(options)
|
@@ -78,11 +81,11 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
78
81
|
end
|
79
82
|
end
|
80
83
|
|
81
|
-
def self.manage_file(filename, settings, options)
|
84
|
+
def self.manage_file(puppet_module, filename, settings, options)
|
82
85
|
namespace = settings.additional_settings[:namespace]
|
83
86
|
module_name = settings.additional_settings[:puppet_module]
|
84
87
|
configs = settings.build_file_configs(filename)
|
85
|
-
target_file =
|
88
|
+
target_file = puppet_module.path(filename)
|
86
89
|
if configs['delete']
|
87
90
|
Renderer.remove(target_file)
|
88
91
|
else
|
@@ -92,50 +95,52 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
92
95
|
# Meta data passed to the template as @metadata[:name]
|
93
96
|
metadata = {
|
94
97
|
:module_name => module_name,
|
95
|
-
:workdir =>
|
98
|
+
:workdir => puppet_module.working_directory,
|
96
99
|
:target_file => target_file,
|
97
100
|
}
|
98
101
|
template = Renderer.render(erb, configs, metadata)
|
99
102
|
Renderer.sync(template, target_file)
|
100
|
-
rescue
|
101
|
-
$stderr.puts "Error while rendering #{filename}"
|
103
|
+
rescue StandardError => e
|
104
|
+
$stderr.puts "#{puppet_module.given_name}: Error while rendering file: '#{filename}'"
|
102
105
|
raise
|
103
106
|
end
|
104
107
|
end
|
105
108
|
end
|
106
109
|
|
107
|
-
def self.manage_module(puppet_module, module_files,
|
108
|
-
|
109
|
-
|
110
|
-
default_namespace = module_options[:namespace]
|
111
|
-
end
|
112
|
-
namespace, module_name = module_name(puppet_module, default_namespace)
|
113
|
-
git_repo = File.join(namespace, module_name)
|
114
|
-
unless options[:offline]
|
115
|
-
Git.pull(options[:git_base], git_repo, options[:branch], options[:project_root], module_options || {})
|
116
|
-
end
|
110
|
+
def self.manage_module(puppet_module, module_files, defaults)
|
111
|
+
puts "Syncing '#{puppet_module.given_name}'"
|
112
|
+
puppet_module.repository.prepare_workspace(options[:branch]) unless options[:offline]
|
117
113
|
|
118
|
-
module_configs = Util.parse_config(
|
114
|
+
module_configs = Util.parse_config puppet_module.path(MODULE_CONF_FILE)
|
119
115
|
settings = Settings.new(defaults[GLOBAL_DEFAULTS_KEY] || {},
|
120
116
|
defaults,
|
121
117
|
module_configs[GLOBAL_DEFAULTS_KEY] || {},
|
122
118
|
module_configs,
|
123
|
-
:puppet_module =>
|
119
|
+
:puppet_module => puppet_module.repository_name,
|
124
120
|
:git_base => options[:git_base],
|
125
|
-
:namespace =>
|
121
|
+
:namespace => puppet_module.repository_namespace)
|
122
|
+
|
126
123
|
settings.unmanaged_files(module_files).each do |filename|
|
127
|
-
$stdout.puts "Not managing #{filename} in #{
|
124
|
+
$stdout.puts "Not managing '#{filename}' in '#{puppet_module.given_name}'"
|
128
125
|
end
|
129
126
|
|
130
127
|
files_to_manage = settings.managed_files(module_files)
|
131
|
-
files_to_manage.each { |filename| manage_file(filename, settings, options) }
|
128
|
+
files_to_manage.each { |filename| manage_file(puppet_module, filename, settings, options) }
|
132
129
|
|
133
130
|
if options[:noop]
|
134
|
-
|
135
|
-
|
131
|
+
puts "Using no-op. Files in '#{puppet_module.given_name}' may be changed but will not be committed."
|
132
|
+
puppet_module.repository.show_changes(options)
|
133
|
+
options[:pr] && \
|
134
|
+
pr(puppet_module).manage(puppet_module.repository_namespace, puppet_module.repository_name, options)
|
136
135
|
elsif !options[:offline]
|
137
|
-
pushed =
|
138
|
-
|
136
|
+
pushed = puppet_module.repository.submit_changes(files_to_manage, options)
|
137
|
+
# Only bump/tag if pushing didn't fail (i.e. there were changes)
|
138
|
+
if pushed && options[:bump]
|
139
|
+
new = puppet_module.bump(options[:message], options[:changelog])
|
140
|
+
puppet_module.repository.tag(new, options[:tag_pattern]) if options[:tag]
|
141
|
+
end
|
142
|
+
pushed && options[:pr] && \
|
143
|
+
pr(puppet_module).manage(puppet_module.repository_namespace, puppet_module.repository_name, options)
|
139
144
|
end
|
140
145
|
end
|
141
146
|
|
@@ -148,9 +153,10 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
148
153
|
self.class.config_path(file, options)
|
149
154
|
end
|
150
155
|
|
151
|
-
def self.update(
|
152
|
-
options = config_defaults.merge(
|
156
|
+
def self.update(cli_options)
|
157
|
+
@options = config_defaults.merge(cli_options)
|
153
158
|
defaults = Util.parse_config(config_path(CONF_FILE, options))
|
159
|
+
|
154
160
|
if options[:pr]
|
155
161
|
unless options[:branch]
|
156
162
|
$stderr.puts 'A branch must be specified with --branch to use --pr!'
|
@@ -164,28 +170,28 @@ module ModuleSync # rubocop:disable Metrics/ModuleLength
|
|
164
170
|
local_files = find_template_files(local_template_dir)
|
165
171
|
module_files = relative_names(local_files, local_template_dir)
|
166
172
|
|
167
|
-
managed_modules = self.managed_modules(config_path(options[:managed_modules_conf], options),
|
168
|
-
options[:filter],
|
169
|
-
options[:negative_filter])
|
170
|
-
|
171
173
|
errors = false
|
172
174
|
# managed_modules is either an array or a hash
|
173
|
-
managed_modules.each do |puppet_module
|
175
|
+
managed_modules.each do |puppet_module|
|
174
176
|
begin
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
$stderr.puts "
|
177
|
+
manage_module(puppet_module, module_files, defaults)
|
178
|
+
rescue ModuleSync::Error, Git::GitExecuteError => e
|
179
|
+
message = e.message || "Error during '#{options[:command]}'"
|
180
|
+
$stderr.puts "#{puppet_module.given_name}: #{message}"
|
181
|
+
exit 1 unless options[:skip_broken]
|
182
|
+
errors = true
|
183
|
+
$stdout.puts "Skipping '#{puppet_module.given_name}' as update process failed"
|
184
|
+
rescue StandardError => e
|
179
185
|
raise unless options[:skip_broken]
|
180
186
|
errors = true
|
181
|
-
$stdout.puts "Skipping #{puppet_module} as update process failed"
|
187
|
+
$stdout.puts "Skipping '#{puppet_module.given_name}' as update process failed"
|
182
188
|
end
|
183
189
|
end
|
184
190
|
exit 1 if errors && options[:fail_on_warnings]
|
185
191
|
end
|
186
192
|
|
187
|
-
def self.pr(
|
188
|
-
module_options
|
193
|
+
def self.pr(puppet_module)
|
194
|
+
module_options = puppet_module.options
|
189
195
|
github_conf = module_options[:github]
|
190
196
|
gitlab_conf = module_options[:gitlab]
|
191
197
|
|
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',
|
@@ -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
|
@@ -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,162 @@
|
|
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
|
+
base_branch = default_branch
|
55
|
+
unless base_branch
|
56
|
+
puts "Couldn't detect default branch. Falling back to assuming 'master'"
|
57
|
+
base_branch = 'master'
|
58
|
+
end
|
59
|
+
puts "Creating new branch #{branch} from #{base_branch}"
|
60
|
+
repo.checkout("origin/#{base_branch}")
|
61
|
+
repo.branch(branch).checkout
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def prepare_workspace(branch)
|
66
|
+
# Repo needs to be cloned in the cwd
|
67
|
+
if !Dir.exist?("#{@directory}/.git")
|
68
|
+
puts "Cloning repository fresh from '#{@remote}'"
|
69
|
+
@git = Git.clone(@remote, @directory)
|
70
|
+
switch_branch(branch)
|
71
|
+
# Repo already cloned, check out master and override local changes
|
72
|
+
else
|
73
|
+
# Some versions of git can't properly handle managing a repo from outside the repo directory
|
74
|
+
Dir.chdir(@directory) do
|
75
|
+
puts "Overriding any local changes to repository in '#{@directory}'"
|
76
|
+
@git = Git.open('.')
|
77
|
+
repo.fetch
|
78
|
+
repo.reset_hard
|
79
|
+
switch_branch(branch)
|
80
|
+
git.pull('origin', branch) if remote_branch_exists?(branch)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def tag(version, tag_pattern)
|
86
|
+
tag = tag_pattern % version
|
87
|
+
puts "Tagging with #{tag}"
|
88
|
+
repo.add_tag(tag)
|
89
|
+
repo.push('origin', tag)
|
90
|
+
end
|
91
|
+
|
92
|
+
def checkout_branch(branch)
|
93
|
+
selected_branch = branch || repo.current_branch || 'master'
|
94
|
+
repo.branch(selected_branch).checkout
|
95
|
+
selected_branch
|
96
|
+
end
|
97
|
+
|
98
|
+
# Git add/rm, git commit, git push
|
99
|
+
def submit_changes(files, options)
|
100
|
+
message = options[:message]
|
101
|
+
branch = checkout_branch(options[:branch])
|
102
|
+
files.each do |file|
|
103
|
+
if repo.status.deleted.include?(file)
|
104
|
+
repo.remove(file)
|
105
|
+
elsif File.exist?("#{@directory}/#{file}")
|
106
|
+
repo.add(file)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
begin
|
110
|
+
opts_commit = {}
|
111
|
+
opts_push = {}
|
112
|
+
opts_commit = { :amend => true } if options[:amend]
|
113
|
+
opts_push = { :force => true } if options[:force]
|
114
|
+
if options[:pre_commit_script]
|
115
|
+
script = "#{File.dirname(File.dirname(__FILE__))}/../contrib/#{options[:pre_commit_script]}"
|
116
|
+
`#{script} #{@directory}`
|
117
|
+
end
|
118
|
+
repo.commit(message, opts_commit)
|
119
|
+
if options[:remote_branch]
|
120
|
+
if remote_branch_differ?(branch, options[:remote_branch])
|
121
|
+
repo.push('origin', "#{branch}:#{options[:remote_branch]}", opts_push)
|
122
|
+
end
|
123
|
+
else
|
124
|
+
repo.push('origin', branch, opts_push)
|
125
|
+
end
|
126
|
+
rescue Git::GitExecuteError => e
|
127
|
+
raise unless e.message.match?(/working (directory|tree) clean/)
|
128
|
+
|
129
|
+
puts "There were no changes in '#{@directory}'. Not committing."
|
130
|
+
return false
|
131
|
+
end
|
132
|
+
|
133
|
+
true
|
134
|
+
end
|
135
|
+
|
136
|
+
# Needed because of a bug in the git gem that lists ignored files as
|
137
|
+
# untracked under some circumstances
|
138
|
+
# https://github.com/schacon/ruby-git/issues/130
|
139
|
+
def untracked_unignored_files
|
140
|
+
ignore_path = "#{@directory}/.gitignore"
|
141
|
+
ignored = File.exist?(ignore_path) ? File.read(ignore_path).split : []
|
142
|
+
repo.status.untracked.keep_if { |f, _| ignored.none? { |i| File.fnmatch(i, f) } }
|
143
|
+
end
|
144
|
+
|
145
|
+
def show_changes(options)
|
146
|
+
checkout_branch(options[:branch])
|
147
|
+
|
148
|
+
puts 'Files changed:'
|
149
|
+
repo.diff('HEAD', '--').each do |diff|
|
150
|
+
puts diff.patch
|
151
|
+
end
|
152
|
+
|
153
|
+
puts 'Files added:'
|
154
|
+
untracked_unignored_files.each_key do |file|
|
155
|
+
puts file
|
156
|
+
end
|
157
|
+
|
158
|
+
puts "\n\n"
|
159
|
+
puts '--------------------------------'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|