git-multirepo 1.0.0.beta34 → 1.0.0.beta37

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -2
  3. data/.gitignore +38 -38
  4. data/.multirepo.meta +2 -2
  5. data/.rspec +2 -2
  6. data/Gemfile +4 -4
  7. data/Gemfile.lock +38 -38
  8. data/LICENSE +22 -22
  9. data/README.md +143 -143
  10. data/Rakefile +2 -2
  11. data/bin/multi +10 -10
  12. data/docs/bug-repros/91565510-repro.sh +20 -20
  13. data/docs/git-multirepo-node-dependency-depth-algorithm.pdf +0 -0
  14. data/docs/graphs/dependencies-on-multiple-levels.graffle +0 -0
  15. data/docs/graphs/dependencies-on-one-level.graffle +0 -0
  16. data/git-multirepo.gemspec +29 -29
  17. data/lib/commands.rb +13 -12
  18. data/lib/git-multirepo.rb +1 -1
  19. data/lib/info.rb +4 -4
  20. data/lib/multirepo/commands/add-command.rb +53 -51
  21. data/lib/multirepo/commands/branch-command.rb +65 -60
  22. data/lib/multirepo/commands/checkout-command.rb +119 -140
  23. data/lib/multirepo/commands/clean-command.rb +31 -31
  24. data/lib/multirepo/commands/clone-command.rb +70 -70
  25. data/lib/multirepo/commands/command.rb +71 -71
  26. data/lib/multirepo/commands/fetch-command.rb +30 -30
  27. data/lib/multirepo/commands/init-command.rb +119 -119
  28. data/lib/multirepo/commands/install-command.rb +103 -103
  29. data/lib/multirepo/commands/merge-command.rb +126 -0
  30. data/lib/multirepo/commands/open-command.rb +26 -26
  31. data/lib/multirepo/commands/remove-command.rb +50 -49
  32. data/lib/multirepo/commands/uninit-command.rb +20 -20
  33. data/lib/multirepo/commands/update-command.rb +59 -59
  34. data/lib/multirepo/config.rb +15 -15
  35. data/lib/multirepo/files/config-entry.rb +34 -34
  36. data/lib/multirepo/files/config-file.rb +45 -34
  37. data/lib/multirepo/files/lock-entry.rb +24 -25
  38. data/lib/multirepo/files/lock-file.rb +39 -28
  39. data/lib/multirepo/files/meta-file.rb +41 -33
  40. data/lib/multirepo/files/tracking-file.rb +8 -8
  41. data/lib/multirepo/files/tracking-files.rb +41 -43
  42. data/lib/multirepo/git/branch.rb +27 -27
  43. data/lib/multirepo/git/change.rb +10 -10
  44. data/lib/multirepo/git/commit.rb +17 -17
  45. data/lib/multirepo/git/git.rb +39 -39
  46. data/lib/multirepo/git/remote.rb +16 -16
  47. data/lib/multirepo/git/repo.rb +77 -77
  48. data/lib/multirepo/hooks/post-commit-hook.rb +22 -22
  49. data/lib/multirepo/hooks/pre-commit-hook.rb +29 -29
  50. data/lib/multirepo/logic/node.rb +41 -0
  51. data/lib/multirepo/logic/performer.rb +59 -0
  52. data/lib/multirepo/logic/revision-selector.rb +27 -0
  53. data/lib/multirepo/multirepo-exception.rb +5 -5
  54. data/lib/multirepo/utility/console.rb +51 -51
  55. data/lib/multirepo/utility/runner.rb +34 -34
  56. data/lib/multirepo/utility/utils.rb +65 -66
  57. data/resources/.gitconfig +2 -2
  58. data/resources/post-commit +5 -5
  59. data/resources/pre-commit +5 -5
  60. data/spec/integration/init_spec.rb +18 -18
  61. data/spec/spec_helper.rb +89 -89
  62. metadata +10 -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.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(CheckoutCommand::CheckoutMode::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
@@ -0,0 +1,126 @@
1
+ require "multirepo/utility/console"
2
+ require "multirepo/logic/node"
3
+ require "multirepo/logic/revision-selector"
4
+ require "multirepo/logic/performer"
5
+
6
+ module MultiRepo
7
+ class MergeCommand < Command
8
+ self.command = "merge"
9
+ self.summary = "Performs a git merge on all dependencies and the main repo, in the proper order."
10
+
11
+ def self.options
12
+ [
13
+ ['<ref>', 'The main repo tag, branch or commit id to merge.'],
14
+ ['[--latest]', 'Merge the HEAD of each stored dependency branch instead of the commits recorded in the lock file.'],
15
+ ['[--exact]', 'Merge the exact specified ref for each repo, regardless of what\'s stored in the lock file.']
16
+ ].concat(super)
17
+ end
18
+
19
+ def initialize(argv)
20
+ @ref = argv.shift_argument
21
+ @checkout_latest = argv.flag?("latest")
22
+ @checkout_exact = argv.flag?("exact")
23
+ super
24
+ end
25
+
26
+ def validate!
27
+ super
28
+ help! "You must specify a ref to merge" unless @ref
29
+ help! "You can't provide more than one operation modifier (--latest, --exact, etc.)" if @checkout_latest && @checkout_exact
30
+ end
31
+
32
+ def run
33
+ super
34
+ ensure_in_work_tree
35
+ ensure_multirepo_enabled
36
+
37
+ Console.log_step("Merging #{@ref} ...")
38
+
39
+ main_repo = Repo.new(".")
40
+
41
+ # Keep the initial revision because we're going to need to come back to it later
42
+ initial_revision = main_repo.current_branch || main_repo.head_hash
43
+
44
+ # Find out the checkout mode based on command-line options
45
+ mode = RevisionSelector.mode_for_args(@checkout_latest, @checkout_exact)
46
+
47
+ begin
48
+ merge_core(main_repo, initial_revision, mode)
49
+ rescue MultiRepoException => e
50
+ Performer.perform_main_repo_checkout(main_repo, initial_revision)
51
+ raise e
52
+ end
53
+
54
+ Console.log_step("Done!")
55
+ rescue MultiRepoException => e
56
+ Console.log_error(e.message)
57
+ end
58
+
59
+ def merge_core(main_repo, initial_revision, mode)
60
+ config_file = ConfigFile.new(".")
61
+
62
+ # Load entries prior to checkout so that we can compare them later
63
+ pre_checkout_config_entries = config_file.load_entries
64
+
65
+ # Ensure the main repo is clean
66
+ raise MultiRepoException, "Main repo is not clean; merge aborted" unless main_repo.clean?
67
+
68
+ # Ensure dependencies are clean
69
+ unless Utils.ensure_dependencies_clean(pre_checkout_config_entries)
70
+ raise MultiRepoException, "Dependencies are not clean; merge aborted"
71
+ end
72
+
73
+ # Checkout the specified main repo ref to find out which dependency refs to merge
74
+ Performer.perform_main_repo_checkout(main_repo, @ref)
75
+
76
+ # Load config entries for the ref we're going to merge
77
+ post_checkout_config_entries = config_file.load_entries
78
+
79
+ # Auto-merge would be too complex if the specified ref does not have the same dependencies
80
+ ensure_dependencies_match(pre_checkout_config_entries, post_checkout_config_entries)
81
+
82
+ Console.log_substep("Merging would do the following:")
83
+
84
+ Performer.perform_on_dependencies do |config_entry, lock_entry|
85
+ revision = RevisionSelector.revision_for_mode(mode, @ref, lock_entry)
86
+ Console.log_info("#{lock_entry.name}: Merge #{revision} into current branch")
87
+ end
88
+ Console.log_info("[main repo]: Merge #{@ref} into current branch")
89
+
90
+ raise MultiRepoException, "Merge aborted" unless Console.ask_yes_no("Proceed?")
91
+
92
+ Console.log_step("Performing merge...")
93
+
94
+ # Checkout the initial revision to perform the merge in it
95
+ Performer.perform_main_repo_checkout(main_repo, initial_revision)
96
+
97
+ # Merge dependencies
98
+ Performer.perform_on_dependencies do |config_entry, lock_entry|
99
+ revision = RevisionSelector.revision_for_mode(mode, @ref, lock_entry)
100
+ Console.log_substep("#{lock_entry.name}: Merging #{revision} into current branch...")
101
+ Git.run_in_working_dir(config_entry.path, "merge #{revision}", Runner::Verbosity::OUTPUT_ALWAYS)
102
+ end
103
+
104
+ # Merge the main repo
105
+ Console.log_substep("[main repo]: Merging #{@ref} into current branch...")
106
+ Git.run_in_current_dir("merge #{@ref}", Runner::Verbosity::OUTPUT_ALWAYS)
107
+ end
108
+
109
+ def ensure_dependencies_match(pre_checkout_config_entries, post_checkout_config_entries)
110
+ perfect_match = true
111
+ pre_checkout_config_entries.each do |pre_entry|
112
+ found = post_checkout_config_entries.find { |post_entry| post_entry.id = pre_entry.id }
113
+ perfect_match &= found
114
+ Console.log_warning("Dependency '#{pre_entry.repo.path}' was not found in the target ref") unless found
115
+ end
116
+
117
+ unless perfect_match
118
+ raise MultiRepoException, "Dependencies differ, please merge manually"
119
+ end
120
+
121
+ if post_checkout_config_entries.count > pre_checkout_config_entries.count
122
+ raise MultiRepoException, "There are more dependencies in the specified ref, please merge manually"
123
+ end
124
+ end
125
+ end
126
+ end
@@ -1,27 +1,27 @@
1
- require "os"
2
-
3
- require "multirepo/utility/console"
4
- require "multirepo/utility/utils"
5
-
6
- module MultiRepo
7
- class OpenCommand < Command
8
- self.command = "open"
9
- self.summary = "Opens all dependencies in the current OS's file explorer."
10
-
11
- def run
12
- super
13
- ensure_in_work_tree
14
- ensure_multirepo_enabled
15
-
16
- ConfigFile.load_entries.each do |entry|
17
- if OS.osx?
18
- `open "#{entry.repo.path}"`
19
- elsif OS.windows?
20
- `explorer "#{Utils.convert_to_windows_path(entry.repo.path)}"`
21
- end
22
- end
23
- rescue MultiRepoException => e
24
- Console.log_error(e.message)
25
- end
26
- end
1
+ require "os"
2
+
3
+ require "multirepo/utility/console"
4
+ require "multirepo/utility/utils"
5
+
6
+ module MultiRepo
7
+ class OpenCommand < Command
8
+ self.command = "open"
9
+ self.summary = "Opens all dependencies in the current OS's file explorer."
10
+
11
+ def run
12
+ super
13
+ ensure_in_work_tree
14
+ ensure_multirepo_enabled
15
+
16
+ ConfigFile.new(".").load_entries.each do |entry|
17
+ if OS.osx?
18
+ `open "#{entry.repo.path}"`
19
+ elsif OS.windows?
20
+ `explorer "#{Utils.convert_to_windows_path(entry.repo.path)}"`
21
+ end
22
+ end
23
+ rescue MultiRepoException => e
24
+ Console.log_error(e.message)
25
+ end
26
+ end
27
27
  end
@@ -1,50 +1,51 @@
1
- require "multirepo/utility/console"
2
- require "multirepo/files/config-file"
3
-
4
- module MultiRepo
5
- class RemoveCommand < Command
6
- self.command = "remove"
7
- self.summary = "Removes the specified dependency from multirepo."
8
-
9
- def self.options
10
- [
11
- ['<path>', 'The relative path to the dependency to remove (e.g. ../MyOldDependency).'],
12
- ['[--delete]', 'Delete the dependency on disk in addition to removing it from the multirepo config.']
13
- ].concat(super)
14
- end
15
-
16
- def initialize(argv)
17
- @path = argv.shift_argument
18
- @delete = argv.flag?("delete")
19
- super
20
- end
21
-
22
- def validate!
23
- super
24
- help! "You must specify a dependency repository to remove" unless @path
25
- end
26
-
27
- def run
28
- super
29
- ensure_in_work_tree
30
- ensure_multirepo_enabled
31
-
32
- repo = Repo.new(@path)
33
- entry = ConfigEntry.new(repo)
34
-
35
- if ConfigFile.entry_exists?(entry)
36
- ConfigFile.remove_entry(entry)
37
- Console.log_step("Removed '#{@path}' from the .multirepo file")
38
-
39
- if @delete
40
- FileUtils.rm_rf(@path)
41
- Console.log_step("Deleted '#{@path}' from disk")
42
- end
43
- else
44
- raise MultiRepoException, "'#{@path}' isn't tracked by multirepo"
45
- end
46
- rescue MultiRepoException => e
47
- Console.log_error(e.message)
48
- end
49
- end
1
+ require "multirepo/utility/console"
2
+ require "multirepo/files/config-file"
3
+
4
+ module MultiRepo
5
+ class RemoveCommand < Command
6
+ self.command = "remove"
7
+ self.summary = "Removes the specified dependency from multirepo."
8
+
9
+ def self.options
10
+ [
11
+ ['<path>', 'The relative path to the dependency to remove (e.g. ../MyOldDependency).'],
12
+ ['[--delete]', 'Delete the dependency on disk in addition to removing it from the multirepo config.']
13
+ ].concat(super)
14
+ end
15
+
16
+ def initialize(argv)
17
+ @path = argv.shift_argument
18
+ @delete = argv.flag?("delete")
19
+ super
20
+ end
21
+
22
+ def validate!
23
+ super
24
+ help! "You must specify a dependency repository to remove" unless @path
25
+ end
26
+
27
+ def run
28
+ super
29
+ ensure_in_work_tree
30
+ ensure_multirepo_enabled
31
+
32
+ repo = Repo.new(@path)
33
+ entry = ConfigEntry.new(repo)
34
+
35
+ config_file = ConfigFile.new(".")
36
+ if config_file.entry_exists?(entry)
37
+ config_file.remove_entry(entry)
38
+ Console.log_step("Removed '#{@path}' from the .multirepo file")
39
+
40
+ if @delete
41
+ FileUtils.rm_rf(@path)
42
+ Console.log_step("Deleted '#{@path}' from disk")
43
+ end
44
+ else
45
+ raise MultiRepoException, "'#{@path}' isn't tracked by multirepo"
46
+ end
47
+ rescue MultiRepoException => e
48
+ Console.log_error(e.message)
49
+ end
50
+ end
50
51
  end