git-multirepo 1.0.0.beta39 → 1.0.0.beta40
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/.gitattributes +2 -2
- data/.gitbugtraq +3 -3
- data/.gitignore +38 -38
- data/.multirepo.meta +2 -2
- data/.rspec +2 -2
- data/Gemfile +4 -4
- data/Gemfile.lock +42 -42
- data/LICENSE +22 -22
- data/README.md +146 -145
- data/Rakefile +2 -2
- data/bin/multi +10 -10
- data/docs/bug-repros/91565510-repro.sh +20 -20
- data/git-multirepo.gemspec +31 -31
- data/lib/commands.rb +15 -14
- data/lib/git-multirepo.rb +2 -2
- data/lib/info.rb +4 -4
- data/lib/multirepo/commands/add-command.rb +53 -53
- data/lib/multirepo/commands/branch-command.rb +82 -82
- data/lib/multirepo/commands/checkout-command.rb +122 -122
- data/lib/multirepo/commands/clean-command.rb +31 -31
- data/lib/multirepo/commands/clone-command.rb +70 -70
- data/lib/multirepo/commands/command.rb +75 -75
- data/lib/multirepo/commands/do-command.rb +76 -0
- data/lib/multirepo/commands/fetch-command.rb +30 -30
- data/lib/multirepo/commands/graph-command.rb +45 -45
- data/lib/multirepo/commands/init-command.rb +119 -119
- data/lib/multirepo/commands/install-command.rb +103 -103
- data/lib/multirepo/commands/merge-command.rb +167 -167
- data/lib/multirepo/commands/open-command.rb +57 -57
- data/lib/multirepo/commands/remove-command.rb +50 -50
- data/lib/multirepo/commands/uninit-command.rb +20 -20
- data/lib/multirepo/commands/update-command.rb +60 -60
- data/lib/multirepo/config.rb +15 -15
- data/lib/multirepo/files/config-entry.rb +38 -38
- data/lib/multirepo/files/config-file.rb +45 -45
- data/lib/multirepo/files/lock-entry.rb +24 -24
- data/lib/multirepo/files/lock-file.rb +38 -38
- data/lib/multirepo/files/meta-file.rb +40 -40
- data/lib/multirepo/files/tracking-file.rb +8 -8
- data/lib/multirepo/files/tracking-files.rb +46 -46
- data/lib/multirepo/git/branch.rb +30 -30
- data/lib/multirepo/git/change.rb +10 -10
- data/lib/multirepo/git/commit.rb +17 -17
- data/lib/multirepo/git/git-runner.rb +46 -46
- data/lib/multirepo/git/remote.rb +16 -16
- data/lib/multirepo/git/repo.rb +77 -77
- data/lib/multirepo/hooks/post-commit-hook.rb +22 -22
- data/lib/multirepo/hooks/pre-commit-hook.rb +31 -31
- data/lib/multirepo/logic/merge-descriptor.rb +12 -12
- data/lib/multirepo/logic/node.rb +44 -44
- data/lib/multirepo/logic/performer.rb +62 -62
- data/lib/multirepo/logic/revision-selector.rb +34 -34
- data/lib/multirepo/multirepo-exception.rb +5 -5
- data/lib/multirepo/utility/console.rb +51 -51
- data/lib/multirepo/utility/runner.rb +34 -34
- data/lib/multirepo/utility/utils.rb +81 -81
- data/resources/.gitconfig +2 -2
- data/resources/post-commit +5 -5
- data/resources/pre-commit +5 -5
- data/spec/integration/init_spec.rb +18 -18
- data/spec/spec_helper.rb +89 -89
- metadata +4 -3
@@ -1,104 +1,104 @@
|
|
1
|
-
require "multirepo/utility/console"
|
2
|
-
require "multirepo/utility/utils"
|
3
|
-
require "multirepo/git/repo"
|
4
|
-
require "multirepo/commands/checkout-command"
|
5
|
-
|
6
|
-
module MultiRepo
|
7
|
-
class InstallCommand < Command
|
8
|
-
self.command = "install"
|
9
|
-
self.summary = "Clones and checks out dependencies as defined in the version-controlled multirepo metadata files and installs git-multirepo's local git hooks."
|
10
|
-
|
11
|
-
def self.options
|
12
|
-
[['[--hooks]', 'Only install local git hooks.']].concat(super)
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize(argv)
|
16
|
-
@hooks = argv.flag?("hooks")
|
17
|
-
super
|
18
|
-
end
|
19
|
-
|
20
|
-
def run
|
21
|
-
super
|
22
|
-
ensure_in_work_tree
|
23
|
-
ensure_multirepo_tracked
|
24
|
-
|
25
|
-
if @hooks
|
26
|
-
Console.log_step("Installing hooks in main repo and all dependencies...")
|
27
|
-
install_hooks_step
|
28
|
-
else
|
29
|
-
Console.log_step("Cloning dependencies and installing hooks...")
|
30
|
-
full_install
|
31
|
-
end
|
32
|
-
|
33
|
-
Console.log_step("Done!")
|
34
|
-
rescue MultiRepoException => e
|
35
|
-
Console.log_error(e.message)
|
36
|
-
end
|
37
|
-
|
38
|
-
def full_install
|
39
|
-
install_dependencies_step
|
40
|
-
install_hooks_step
|
41
|
-
update_gitconfigs_step
|
42
|
-
end
|
43
|
-
|
44
|
-
def install_dependencies_step
|
45
|
-
# Read config entries as-is on disk, without prior checkout
|
46
|
-
config_entries = ConfigFile.new(".").load_entries
|
47
|
-
Console.log_substep("Installing #{config_entries.count} dependencies...");
|
48
|
-
|
49
|
-
# Clone or fetch all configured dependencies to make sure nothing is missing locally
|
50
|
-
config_entries.each { |entry| clone_or_fetch(entry) }
|
51
|
-
|
52
|
-
# Checkout the appropriate branches as specified in the lock file
|
53
|
-
checkout_command = CheckoutCommand.new(CLAide::ARGV.new([]))
|
54
|
-
checkout_command.dependencies_checkout_step(RevisionSelectionMode::LATEST)
|
55
|
-
end
|
56
|
-
|
57
|
-
def install_hooks_step
|
58
|
-
perform_in_main_repo_and_dependencies("Installed git hooks") { |repo| install_hooks(repo) }
|
59
|
-
end
|
60
|
-
|
61
|
-
def update_gitconfigs_step
|
62
|
-
perform_in_main_repo_and_dependencies("Updated .git/config file") { |repo| update_gitconfig(repo) }
|
63
|
-
end
|
64
|
-
|
65
|
-
def perform_in_main_repo_and_dependencies(message_prefix, &operation)
|
66
|
-
operation.call(".")
|
67
|
-
Console.log_substep("#{message_prefix} in main repo")
|
68
|
-
|
69
|
-
multirepo_enabled_dependencies.each do |entry|
|
70
|
-
operation.call(entry.repo.path)
|
71
|
-
Console.log_substep("#{message_prefix} in multirepo-enabled dependency '#{entry.repo.path}'")
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def clone_or_fetch(entry)
|
76
|
-
if entry.repo.exists?
|
77
|
-
check_repo_validity(entry)
|
78
|
-
fetch_repo(entry)
|
79
|
-
else
|
80
|
-
clone_repo(entry)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# Repo operations
|
85
|
-
|
86
|
-
def fetch_repo(entry)
|
87
|
-
Console.log_substep("Working copy '#{entry.repo.path}' already exists, fetching instead...")
|
88
|
-
raise MultiRepoException, "Could not fetch from remote #{entry.repo.remote('origin').url}" unless entry.repo.fetch
|
89
|
-
end
|
90
|
-
|
91
|
-
def clone_repo(entry)
|
92
|
-
Console.log_substep("Cloning #{entry.url} into '#{entry.repo.path}'")
|
93
|
-
raise MultiRepoException, "Could not clone remote #{entry.url}" unless entry.repo.clone(entry.url)
|
94
|
-
end
|
95
|
-
|
96
|
-
# Validation
|
97
|
-
|
98
|
-
def check_repo_validity(entry)
|
99
|
-
unless entry.repo.remote("origin").url == entry.url
|
100
|
-
raise MultiRepoException, "'#{entry.path}' origin URL (#{entry.repo.remote('origin').url}) does not match entry (#{entry.url})!"
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
1
|
+
require "multirepo/utility/console"
|
2
|
+
require "multirepo/utility/utils"
|
3
|
+
require "multirepo/git/repo"
|
4
|
+
require "multirepo/commands/checkout-command"
|
5
|
+
|
6
|
+
module MultiRepo
|
7
|
+
class InstallCommand < Command
|
8
|
+
self.command = "install"
|
9
|
+
self.summary = "Clones and checks out dependencies as defined in the version-controlled multirepo metadata files and installs git-multirepo's local git hooks."
|
10
|
+
|
11
|
+
def self.options
|
12
|
+
[['[--hooks]', 'Only install local git hooks.']].concat(super)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(argv)
|
16
|
+
@hooks = argv.flag?("hooks")
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
super
|
22
|
+
ensure_in_work_tree
|
23
|
+
ensure_multirepo_tracked
|
24
|
+
|
25
|
+
if @hooks
|
26
|
+
Console.log_step("Installing hooks in main repo and all dependencies...")
|
27
|
+
install_hooks_step
|
28
|
+
else
|
29
|
+
Console.log_step("Cloning dependencies and installing hooks...")
|
30
|
+
full_install
|
31
|
+
end
|
32
|
+
|
33
|
+
Console.log_step("Done!")
|
34
|
+
rescue MultiRepoException => e
|
35
|
+
Console.log_error(e.message)
|
36
|
+
end
|
37
|
+
|
38
|
+
def full_install
|
39
|
+
install_dependencies_step
|
40
|
+
install_hooks_step
|
41
|
+
update_gitconfigs_step
|
42
|
+
end
|
43
|
+
|
44
|
+
def install_dependencies_step
|
45
|
+
# Read config entries as-is on disk, without prior checkout
|
46
|
+
config_entries = ConfigFile.new(".").load_entries
|
47
|
+
Console.log_substep("Installing #{config_entries.count} dependencies...");
|
48
|
+
|
49
|
+
# Clone or fetch all configured dependencies to make sure nothing is missing locally
|
50
|
+
config_entries.each { |entry| clone_or_fetch(entry) }
|
51
|
+
|
52
|
+
# Checkout the appropriate branches as specified in the lock file
|
53
|
+
checkout_command = CheckoutCommand.new(CLAide::ARGV.new([]))
|
54
|
+
checkout_command.dependencies_checkout_step(RevisionSelectionMode::LATEST)
|
55
|
+
end
|
56
|
+
|
57
|
+
def install_hooks_step
|
58
|
+
perform_in_main_repo_and_dependencies("Installed git hooks") { |repo| install_hooks(repo) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_gitconfigs_step
|
62
|
+
perform_in_main_repo_and_dependencies("Updated .git/config file") { |repo| update_gitconfig(repo) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def perform_in_main_repo_and_dependencies(message_prefix, &operation)
|
66
|
+
operation.call(".")
|
67
|
+
Console.log_substep("#{message_prefix} in main repo")
|
68
|
+
|
69
|
+
multirepo_enabled_dependencies.each do |entry|
|
70
|
+
operation.call(entry.repo.path)
|
71
|
+
Console.log_substep("#{message_prefix} in multirepo-enabled dependency '#{entry.repo.path}'")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def clone_or_fetch(entry)
|
76
|
+
if entry.repo.exists?
|
77
|
+
check_repo_validity(entry)
|
78
|
+
fetch_repo(entry)
|
79
|
+
else
|
80
|
+
clone_repo(entry)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Repo operations
|
85
|
+
|
86
|
+
def fetch_repo(entry)
|
87
|
+
Console.log_substep("Working copy '#{entry.repo.path}' already exists, fetching instead...")
|
88
|
+
raise MultiRepoException, "Could not fetch from remote #{entry.repo.remote('origin').url}" unless entry.repo.fetch
|
89
|
+
end
|
90
|
+
|
91
|
+
def clone_repo(entry)
|
92
|
+
Console.log_substep("Cloning #{entry.url} into '#{entry.repo.path}'")
|
93
|
+
raise MultiRepoException, "Could not clone remote #{entry.url}" unless entry.repo.clone(entry.url)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Validation
|
97
|
+
|
98
|
+
def check_repo_validity(entry)
|
99
|
+
unless entry.repo.remote("origin").url == entry.url
|
100
|
+
raise MultiRepoException, "'#{entry.path}' origin URL (#{entry.repo.remote('origin').url}) does not match entry (#{entry.url})!"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
104
|
end
|
@@ -1,168 +1,168 @@
|
|
1
|
-
require "terminal-table"
|
2
|
-
|
3
|
-
require "multirepo/utility/console"
|
4
|
-
require "multirepo/logic/node"
|
5
|
-
require "multirepo/logic/revision-selector"
|
6
|
-
require "multirepo/logic/performer"
|
7
|
-
require "multirepo/logic/merge-descriptor"
|
8
|
-
|
9
|
-
module MultiRepo
|
10
|
-
class MergeCommand < Command
|
11
|
-
self.command = "merge"
|
12
|
-
self.summary = "Performs a git merge on all dependencies and the main repo, in the proper order."
|
13
|
-
|
14
|
-
def self.options
|
15
|
-
[
|
16
|
-
['<ref>', 'The main repo tag, branch or commit id to merge.'],
|
17
|
-
['[--latest]', 'Merge the HEAD of each stored dependency branch instead of the commits recorded in the lock file.'],
|
18
|
-
['[--exact]', 'Merge the exact specified ref for each repo, regardless of what\'s stored in the lock file.']
|
19
|
-
].concat(super)
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(argv)
|
23
|
-
@ref = argv.shift_argument
|
24
|
-
@checkout_latest = argv.flag?("latest")
|
25
|
-
@checkout_exact = argv.flag?("exact")
|
26
|
-
super
|
27
|
-
end
|
28
|
-
|
29
|
-
def validate!
|
30
|
-
super
|
31
|
-
help! "You must specify a ref to merge" unless @ref
|
32
|
-
unless validate_only_one_flag(@checkout_latest, @checkout_exact)
|
33
|
-
help! "You can't provide more than one operation modifier (--latest, --exact, etc.)"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def run
|
38
|
-
super
|
39
|
-
ensure_in_work_tree
|
40
|
-
ensure_multirepo_enabled
|
41
|
-
|
42
|
-
# Find out the checkout mode based on command-line options
|
43
|
-
mode = RevisionSelector.mode_for_args(@checkout_latest, @checkout_exact)
|
44
|
-
|
45
|
-
strategy_name = RevisionSelectionMode.name_for_mode(mode)
|
46
|
-
Console.log_step("Merging #{@ref} with '#{strategy_name}' strategy...")
|
47
|
-
|
48
|
-
main_repo = Repo.new(".")
|
49
|
-
|
50
|
-
# Keep the initial revision because we're going to need to come back to it later
|
51
|
-
initial_revision = main_repo.current_branch || main_repo.head_hash
|
52
|
-
|
53
|
-
begin
|
54
|
-
merge_core(main_repo, initial_revision, mode)
|
55
|
-
rescue MultiRepoException => e
|
56
|
-
# Revert to the initial revision only if necessary
|
57
|
-
unless main_repo.current_branch == initial_revision || main_repo.head_hash == initial_revision
|
58
|
-
Console.log_substep("Restoring working copy to #{initial_revision}")
|
59
|
-
main_repo.checkout(initial_revision)
|
60
|
-
end
|
61
|
-
raise e
|
62
|
-
end
|
63
|
-
|
64
|
-
Console.log_step("Done!")
|
65
|
-
rescue MultiRepoException => e
|
66
|
-
Console.log_error(e.message)
|
67
|
-
end
|
68
|
-
|
69
|
-
def merge_core(main_repo, initial_revision, mode)
|
70
|
-
config_file = ConfigFile.new(".")
|
71
|
-
|
72
|
-
# Load entries prior to checkout so that we can compare them later
|
73
|
-
pre_checkout_config_entries = config_file.load_entries
|
74
|
-
|
75
|
-
# Ensure the main repo is clean
|
76
|
-
raise MultiRepoException, "Main repo is not clean; merge aborted" unless main_repo.clean?
|
77
|
-
|
78
|
-
# Ensure dependencies are clean
|
79
|
-
unless Utils.ensure_dependencies_clean(pre_checkout_config_entries)
|
80
|
-
raise MultiRepoException, "Dependencies are not clean; merge aborted"
|
81
|
-
end
|
82
|
-
|
83
|
-
# Fetch repos to make sure we have the latest history in each.
|
84
|
-
# Fetching pre-checkout dependency repositories is sufficient because
|
85
|
-
# we make sure that the same dependencies are configured post-checkout.
|
86
|
-
Console.log_substep("Fetching repositories before proceeding with merge...")
|
87
|
-
main_repo.fetch
|
88
|
-
pre_checkout_config_entries.each { |e| e.repo.fetch }
|
89
|
-
|
90
|
-
# Checkout the specified main repo ref to find out which dependency refs to merge
|
91
|
-
Performer.perform_main_repo_checkout(main_repo, @ref)
|
92
|
-
|
93
|
-
# Load config entries for the ref we're going to merge
|
94
|
-
post_checkout_config_entries = config_file.load_entries
|
95
|
-
|
96
|
-
# Auto-merge would be too complex to implement (due to lots of edge cases)
|
97
|
-
# if the specified ref does not have the same dependencies
|
98
|
-
ensure_dependencies_match(pre_checkout_config_entries, post_checkout_config_entries)
|
99
|
-
|
100
|
-
# Create a merge descriptor for each would-be merge
|
101
|
-
descriptors = []
|
102
|
-
Performer.perform_on_dependencies do |config_entry, lock_entry|
|
103
|
-
revision = RevisionSelector.revision_for_mode(mode, @ref, lock_entry)
|
104
|
-
descriptors.push(MergeDescriptor.new(config_entry.name, config_entry.path, revision))
|
105
|
-
end
|
106
|
-
descriptors.push(MergeDescriptor.new("Main Repo", main_repo.path, @ref))
|
107
|
-
|
108
|
-
# Log merge operations to the console before the fact
|
109
|
-
Console.log_warning("Merging would #{message_for_mode(mode, @ref)}:")
|
110
|
-
log_merges(descriptors)
|
111
|
-
|
112
|
-
raise MultiRepoException, "Merge aborted" unless Console.ask_yes_no("Proceed?")
|
113
|
-
|
114
|
-
Console.log_step("Performing merge...")
|
115
|
-
|
116
|
-
# Checkout the initial revision to perform the merge in it
|
117
|
-
Performer.perform_main_repo_checkout(main_repo, initial_revision)
|
118
|
-
|
119
|
-
# Merge dependencies and the main repo
|
120
|
-
perform_merges(descriptors)
|
121
|
-
end
|
122
|
-
|
123
|
-
def ensure_dependencies_match(pre_checkout_config_entries, post_checkout_config_entries)
|
124
|
-
perfect_match = true
|
125
|
-
pre_checkout_config_entries.each do |pre_entry|
|
126
|
-
found = post_checkout_config_entries.find { |post_entry| post_entry.id = pre_entry.id }
|
127
|
-
perfect_match &= found
|
128
|
-
Console.log_warning("Dependency '#{pre_entry.repo.path}' was not found in the target ref") unless found
|
129
|
-
end
|
130
|
-
|
131
|
-
unless perfect_match
|
132
|
-
raise MultiRepoException, "Dependencies differ, please merge manually"
|
133
|
-
end
|
134
|
-
|
135
|
-
if post_checkout_config_entries.count > pre_checkout_config_entries.count
|
136
|
-
raise MultiRepoException, "There are more dependencies in the specified ref, please merge manually"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def log_merges(descriptors)
|
141
|
-
table = Terminal::Table.new do |t|
|
142
|
-
descriptors.reverse.each_with_index do |descriptor, index|
|
143
|
-
t.add_row [descriptor.name, "Merge '#{descriptor.revision}'"]
|
144
|
-
t.add_separator unless index == descriptors.count - 1
|
145
|
-
end
|
146
|
-
end
|
147
|
-
puts table
|
148
|
-
end
|
149
|
-
|
150
|
-
def message_for_mode(mode, ref)
|
151
|
-
case mode
|
152
|
-
when RevisionSelectionMode::AS_LOCK
|
153
|
-
"merge specific commits as stored in the lock file for main repo revision #{ref}"
|
154
|
-
when RevisionSelectionMode::LATEST
|
155
|
-
"merge each branch as stored in the lock file of main repo revision #{ref}"
|
156
|
-
when RevisionSelectionMode::EXACT
|
157
|
-
"merge #{ref} for each repository, ignoring the contents of the lock file"
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def perform_merges(descriptors)
|
162
|
-
descriptors.each do |descriptor|
|
163
|
-
Console.log_substep("#{descriptor.name} : Merging #{descriptor.revision} into current branch...")
|
164
|
-
GitRunner.run_in_working_dir(descriptor.path, "merge #{descriptor.revision}", Runner::Verbosity::OUTPUT_ALWAYS)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
1
|
+
require "terminal-table"
|
2
|
+
|
3
|
+
require "multirepo/utility/console"
|
4
|
+
require "multirepo/logic/node"
|
5
|
+
require "multirepo/logic/revision-selector"
|
6
|
+
require "multirepo/logic/performer"
|
7
|
+
require "multirepo/logic/merge-descriptor"
|
8
|
+
|
9
|
+
module MultiRepo
|
10
|
+
class MergeCommand < Command
|
11
|
+
self.command = "merge"
|
12
|
+
self.summary = "Performs a git merge on all dependencies and the main repo, in the proper order."
|
13
|
+
|
14
|
+
def self.options
|
15
|
+
[
|
16
|
+
['<ref>', 'The main repo tag, branch or commit id to merge.'],
|
17
|
+
['[--latest]', 'Merge the HEAD of each stored dependency branch instead of the commits recorded in the lock file.'],
|
18
|
+
['[--exact]', 'Merge the exact specified ref for each repo, regardless of what\'s stored in the lock file.']
|
19
|
+
].concat(super)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(argv)
|
23
|
+
@ref = argv.shift_argument
|
24
|
+
@checkout_latest = argv.flag?("latest")
|
25
|
+
@checkout_exact = argv.flag?("exact")
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate!
|
30
|
+
super
|
31
|
+
help! "You must specify a ref to merge" unless @ref
|
32
|
+
unless validate_only_one_flag(@checkout_latest, @checkout_exact)
|
33
|
+
help! "You can't provide more than one operation modifier (--latest, --exact, etc.)"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
super
|
39
|
+
ensure_in_work_tree
|
40
|
+
ensure_multirepo_enabled
|
41
|
+
|
42
|
+
# Find out the checkout mode based on command-line options
|
43
|
+
mode = RevisionSelector.mode_for_args(@checkout_latest, @checkout_exact)
|
44
|
+
|
45
|
+
strategy_name = RevisionSelectionMode.name_for_mode(mode)
|
46
|
+
Console.log_step("Merging #{@ref} with '#{strategy_name}' strategy...")
|
47
|
+
|
48
|
+
main_repo = Repo.new(".")
|
49
|
+
|
50
|
+
# Keep the initial revision because we're going to need to come back to it later
|
51
|
+
initial_revision = main_repo.current_branch || main_repo.head_hash
|
52
|
+
|
53
|
+
begin
|
54
|
+
merge_core(main_repo, initial_revision, mode)
|
55
|
+
rescue MultiRepoException => e
|
56
|
+
# Revert to the initial revision only if necessary
|
57
|
+
unless main_repo.current_branch == initial_revision || main_repo.head_hash == initial_revision
|
58
|
+
Console.log_substep("Restoring working copy to #{initial_revision}")
|
59
|
+
main_repo.checkout(initial_revision)
|
60
|
+
end
|
61
|
+
raise e
|
62
|
+
end
|
63
|
+
|
64
|
+
Console.log_step("Done!")
|
65
|
+
rescue MultiRepoException => e
|
66
|
+
Console.log_error(e.message)
|
67
|
+
end
|
68
|
+
|
69
|
+
def merge_core(main_repo, initial_revision, mode)
|
70
|
+
config_file = ConfigFile.new(".")
|
71
|
+
|
72
|
+
# Load entries prior to checkout so that we can compare them later
|
73
|
+
pre_checkout_config_entries = config_file.load_entries
|
74
|
+
|
75
|
+
# Ensure the main repo is clean
|
76
|
+
raise MultiRepoException, "Main repo is not clean; merge aborted" unless main_repo.clean?
|
77
|
+
|
78
|
+
# Ensure dependencies are clean
|
79
|
+
unless Utils.ensure_dependencies_clean(pre_checkout_config_entries)
|
80
|
+
raise MultiRepoException, "Dependencies are not clean; merge aborted"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Fetch repos to make sure we have the latest history in each.
|
84
|
+
# Fetching pre-checkout dependency repositories is sufficient because
|
85
|
+
# we make sure that the same dependencies are configured post-checkout.
|
86
|
+
Console.log_substep("Fetching repositories before proceeding with merge...")
|
87
|
+
main_repo.fetch
|
88
|
+
pre_checkout_config_entries.each { |e| e.repo.fetch }
|
89
|
+
|
90
|
+
# Checkout the specified main repo ref to find out which dependency refs to merge
|
91
|
+
Performer.perform_main_repo_checkout(main_repo, @ref)
|
92
|
+
|
93
|
+
# Load config entries for the ref we're going to merge
|
94
|
+
post_checkout_config_entries = config_file.load_entries
|
95
|
+
|
96
|
+
# Auto-merge would be too complex to implement (due to lots of edge cases)
|
97
|
+
# if the specified ref does not have the same dependencies
|
98
|
+
ensure_dependencies_match(pre_checkout_config_entries, post_checkout_config_entries)
|
99
|
+
|
100
|
+
# Create a merge descriptor for each would-be merge
|
101
|
+
descriptors = []
|
102
|
+
Performer.perform_on_dependencies do |config_entry, lock_entry|
|
103
|
+
revision = RevisionSelector.revision_for_mode(mode, @ref, lock_entry)
|
104
|
+
descriptors.push(MergeDescriptor.new(config_entry.name, config_entry.path, revision))
|
105
|
+
end
|
106
|
+
descriptors.push(MergeDescriptor.new("Main Repo", main_repo.path, @ref))
|
107
|
+
|
108
|
+
# Log merge operations to the console before the fact
|
109
|
+
Console.log_warning("Merging would #{message_for_mode(mode, @ref)}:")
|
110
|
+
log_merges(descriptors)
|
111
|
+
|
112
|
+
raise MultiRepoException, "Merge aborted" unless Console.ask_yes_no("Proceed?")
|
113
|
+
|
114
|
+
Console.log_step("Performing merge...")
|
115
|
+
|
116
|
+
# Checkout the initial revision to perform the merge in it
|
117
|
+
Performer.perform_main_repo_checkout(main_repo, initial_revision)
|
118
|
+
|
119
|
+
# Merge dependencies and the main repo
|
120
|
+
perform_merges(descriptors)
|
121
|
+
end
|
122
|
+
|
123
|
+
def ensure_dependencies_match(pre_checkout_config_entries, post_checkout_config_entries)
|
124
|
+
perfect_match = true
|
125
|
+
pre_checkout_config_entries.each do |pre_entry|
|
126
|
+
found = post_checkout_config_entries.find { |post_entry| post_entry.id = pre_entry.id }
|
127
|
+
perfect_match &= found
|
128
|
+
Console.log_warning("Dependency '#{pre_entry.repo.path}' was not found in the target ref") unless found
|
129
|
+
end
|
130
|
+
|
131
|
+
unless perfect_match
|
132
|
+
raise MultiRepoException, "Dependencies differ, please merge manually"
|
133
|
+
end
|
134
|
+
|
135
|
+
if post_checkout_config_entries.count > pre_checkout_config_entries.count
|
136
|
+
raise MultiRepoException, "There are more dependencies in the specified ref, please merge manually"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def log_merges(descriptors)
|
141
|
+
table = Terminal::Table.new do |t|
|
142
|
+
descriptors.reverse.each_with_index do |descriptor, index|
|
143
|
+
t.add_row [descriptor.name, "Merge '#{descriptor.revision}'"]
|
144
|
+
t.add_separator unless index == descriptors.count - 1
|
145
|
+
end
|
146
|
+
end
|
147
|
+
puts table
|
148
|
+
end
|
149
|
+
|
150
|
+
def message_for_mode(mode, ref)
|
151
|
+
case mode
|
152
|
+
when RevisionSelectionMode::AS_LOCK
|
153
|
+
"merge specific commits as stored in the lock file for main repo revision #{ref}"
|
154
|
+
when RevisionSelectionMode::LATEST
|
155
|
+
"merge each branch as stored in the lock file of main repo revision #{ref}"
|
156
|
+
when RevisionSelectionMode::EXACT
|
157
|
+
"merge #{ref} for each repository, ignoring the contents of the lock file"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def perform_merges(descriptors)
|
162
|
+
descriptors.each do |descriptor|
|
163
|
+
Console.log_substep("#{descriptor.name} : Merging #{descriptor.revision} into current branch...")
|
164
|
+
GitRunner.run_in_working_dir(descriptor.path, "merge #{descriptor.revision}", Runner::Verbosity::OUTPUT_ALWAYS)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
168
|
end
|