git-multirepo 1.0.0.beta56 → 1.0.0.beta57

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -2
  3. data/.gitbugtraq +3 -3
  4. data/.gitignore +38 -38
  5. data/.multirepo.meta +2 -2
  6. data/.rspec +2 -2
  7. data/.rubocop.yml +79 -79
  8. data/CHANGELOG.md +61 -57
  9. data/Gemfile +4 -4
  10. data/Gemfile.lock +42 -42
  11. data/LICENSE +22 -22
  12. data/README.md +179 -178
  13. data/Rakefile +1 -1
  14. data/bin/multi +11 -11
  15. data/docs/bug-repros/91565510-repro.sh +20 -20
  16. data/git-multirepo.gemspec +31 -31
  17. data/lib/git-multirepo.rb +3 -3
  18. data/lib/multirepo/commands/add-command.rb +51 -51
  19. data/lib/multirepo/commands/branch-command.rb +82 -82
  20. data/lib/multirepo/commands/checkout-command.rb +127 -127
  21. data/lib/multirepo/commands/clone-command.rb +68 -68
  22. data/lib/multirepo/commands/command.rb +87 -87
  23. data/lib/multirepo/commands/commands.rb +15 -15
  24. data/lib/multirepo/commands/do-command.rb +101 -101
  25. data/lib/multirepo/commands/graph-command.rb +43 -43
  26. data/lib/multirepo/commands/init-command.rb +121 -121
  27. data/lib/multirepo/commands/inspect-command.rb +48 -48
  28. data/lib/multirepo/commands/install-command.rb +161 -161
  29. data/lib/multirepo/commands/merge-command.rb +249 -249
  30. data/lib/multirepo/commands/open-command.rb +55 -55
  31. data/lib/multirepo/commands/remove-command.rb +48 -48
  32. data/lib/multirepo/commands/uninit-command.rb +18 -18
  33. data/lib/multirepo/commands/update-command.rb +112 -112
  34. data/lib/multirepo/config.rb +19 -19
  35. data/lib/multirepo/files/config-entry.rb +39 -39
  36. data/lib/multirepo/files/config-file.rb +46 -46
  37. data/lib/multirepo/files/lock-entry.rb +29 -29
  38. data/lib/multirepo/files/lock-file.rb +56 -56
  39. data/lib/multirepo/files/meta-file.rb +41 -41
  40. data/lib/multirepo/files/tracking-file.rb +9 -9
  41. data/lib/multirepo/files/tracking-files.rb +47 -47
  42. data/lib/multirepo/git/branch.rb +32 -32
  43. data/lib/multirepo/git/change.rb +11 -11
  44. data/lib/multirepo/git/commit.rb +7 -7
  45. data/lib/multirepo/git/git-runner.rb +56 -56
  46. data/lib/multirepo/git/git.rb +10 -10
  47. data/lib/multirepo/git/ref.rb +38 -38
  48. data/lib/multirepo/git/remote.rb +17 -17
  49. data/lib/multirepo/git/repo.rb +129 -129
  50. data/lib/multirepo/hooks/post-commit-hook.rb +23 -23
  51. data/lib/multirepo/hooks/pre-commit-hook.rb +35 -35
  52. data/lib/multirepo/info.rb +5 -5
  53. data/lib/multirepo/logic/dependency.rb +6 -6
  54. data/lib/multirepo/logic/merge-descriptor.rb +95 -95
  55. data/lib/multirepo/logic/node.rb +72 -72
  56. data/lib/multirepo/logic/performer.rb +55 -55
  57. data/lib/multirepo/logic/repo-selection.rb +25 -25
  58. data/lib/multirepo/logic/revision-selection.rb +15 -15
  59. data/lib/multirepo/logic/revision-selector.rb +23 -23
  60. data/lib/multirepo/multirepo-exception.rb +6 -6
  61. data/lib/multirepo/output/extra-output.rb +12 -12
  62. data/lib/multirepo/output/teamcity-extra-output.rb +11 -11
  63. data/lib/multirepo/utility/console.rb +52 -52
  64. data/lib/multirepo/utility/popen-runner.rb +27 -27
  65. data/lib/multirepo/utility/system-runner.rb +14 -14
  66. data/lib/multirepo/utility/utils.rb +99 -99
  67. data/lib/multirepo/utility/verbosity.rb +6 -6
  68. data/resources/.gitconfig +2 -2
  69. data/resources/post-commit +6 -6
  70. data/resources/pre-commit +6 -6
  71. data/spec/integration/init_spec.rb +19 -19
  72. data/spec/spec_helper.rb +89 -89
  73. metadata +3 -3
@@ -1,43 +1,43 @@
1
- require "os"
2
- require "graphviz"
3
-
4
- require "multirepo/utility/utils"
5
- require "multirepo/utility/console"
6
- require "multirepo/logic/node"
7
-
8
- module MultiRepo
9
- class GraphCommand < Command
10
- self.command = "graph"
11
- self.summary = "Graphs the dependency tree from the current repository."
12
-
13
- def run
14
- ensure_in_work_tree
15
- ensure_multirepo_enabled
16
-
17
- root = Node.new(".")
18
- graph = GraphViz.new(:G, :type => :digraph)
19
- build_graph_recursive(graph, root)
20
-
21
- path = File.expand_path("~/Desktop/#{root.name}-graph.png")
22
-
23
- begin
24
- graph.output(:png => path)
25
- Utils.open_in_default_app(path)
26
- rescue StandardError => e
27
- Console.log_error(e.message)
28
- raise MultiRepoException, "Could not generate graph image because an error occurred during graph generation"
29
- end
30
-
31
- Console.log_step("Generated graph image #{path}")
32
- end
33
-
34
- def build_graph_recursive(graph, node)
35
- parent_graph_node = graph.add_nodes(node.name)
36
- node.children.each do |child_node|
37
- child_graph_node = graph.add_node(child_node.name)
38
- graph.add_edge(parent_graph_node, child_graph_node)
39
- build_graph_recursive(graph, child_node)
40
- end
41
- end
42
- end
43
- end
1
+ require "os"
2
+ require "graphviz"
3
+
4
+ require "multirepo/utility/utils"
5
+ require "multirepo/utility/console"
6
+ require "multirepo/logic/node"
7
+
8
+ module MultiRepo
9
+ class GraphCommand < Command
10
+ self.command = "graph"
11
+ self.summary = "Graphs the dependency tree from the current repository."
12
+
13
+ def run
14
+ ensure_in_work_tree
15
+ ensure_multirepo_enabled
16
+
17
+ root = Node.new(".")
18
+ graph = GraphViz.new(:G, :type => :digraph)
19
+ build_graph_recursive(graph, root)
20
+
21
+ path = File.expand_path("~/Desktop/#{root.name}-graph.png")
22
+
23
+ begin
24
+ graph.output(:png => path)
25
+ Utils.open_in_default_app(path)
26
+ rescue StandardError => e
27
+ Console.log_error(e.message)
28
+ raise MultiRepoException, "Could not generate graph image because an error occurred during graph generation"
29
+ end
30
+
31
+ Console.log_step("Generated graph image #{path}")
32
+ end
33
+
34
+ def build_graph_recursive(graph, node)
35
+ parent_graph_node = graph.add_nodes(node.name)
36
+ node.children.each do |child_node|
37
+ child_graph_node = graph.add_node(child_node.name)
38
+ graph.add_edge(parent_graph_node, child_graph_node)
39
+ build_graph_recursive(graph, child_node)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,121 +1,121 @@
1
- require "multirepo/utility/console"
2
- require "multirepo/utility/utils"
3
- require "multirepo/files/config-file"
4
- require "multirepo/files/lock-file"
5
- require "multirepo/files/tracking-files"
6
- require "multirepo/commands/command"
7
-
8
- module MultiRepo
9
- class InitCommand < Command
10
- self.command = "init"
11
- self.summary = "Initialize the current repository as a multirepo project."
12
-
13
- def self.options
14
- [['[--extras]', 'Keep the current .multirepo config file as-is and initialize everything else.']].concat(super)
15
- end
16
-
17
- def initialize(argv)
18
- @only_extras = argv.flag?("extras")
19
- super
20
- end
21
-
22
- def run
23
- ensure_in_work_tree
24
-
25
- if @only_extras
26
- ensure_multirepo_enabled
27
- Console.log_step("Initializing extras...")
28
- initialize_extras_step
29
- else
30
- Console.log_step("Initializing multirepo...")
31
- full_initialize_step
32
- end
33
-
34
- Console.log_step("Done!")
35
- end
36
-
37
- def full_initialize_step
38
- if ConfigFile.new(".").exists?
39
- reinitialize = Console.ask(".multirepo file already exists. Reinitialize?")
40
- fail MultiRepoException, "Initialization aborted" unless reinitialize
41
- end
42
-
43
- unless add_sibling_repos_step
44
- fail MultiRepoException, "There are no sibling repositories to track as dependencies. Initialization aborted."
45
- end
46
-
47
- initialize_extras_step
48
- end
49
-
50
- def add_sibling_repos_step
51
- sibling_repos = Utils.sibling_repos
52
- return false unless sibling_repos.any?
53
-
54
- Console.log_substep("Creating new multirepo config...")
55
-
56
- valid_repos = find_valid_repos(sibling_repos)
57
- entries = create_entries(valid_repos)
58
-
59
- fail MultiRepoException, "No sibling repositories were added as dependencies; init aborted" unless entries.any?
60
-
61
- ConfigFile.new(".").save_entries(entries)
62
- return true
63
- end
64
-
65
- def initialize_extras_step
66
- install_hooks_step
67
- update_gitattributes_step
68
- update_gitconfig_step
69
- end
70
-
71
- def install_hooks_step
72
- install_hooks(".")
73
- Console.log_substep("Installed git hooks")
74
- end
75
-
76
- def update_gitattributes_step
77
- TrackingFiles.new(".").files.each do |f|
78
- filename = f.filename
79
- regex_escaped_filename = Regexp.quote(filename)
80
- Utils.append_if_missing("./.gitattributes", /^#{regex_escaped_filename} .*/, "#{filename} merge=ours")
81
- end
82
- Console.log_substep("Updated .gitattributes file")
83
- end
84
-
85
- def update_gitconfig_step
86
- update_gitconfig(".")
87
- Console.log_substep("Updated .git/config file")
88
- end
89
-
90
- def find_valid_repos(repos)
91
- repos.select do |repo|
92
- next true if repo.head_born?
93
- Console.log_warning("Ignoring repo '#{repo.path}' because its HEAD is unborn. You must perform at least one commit.")
94
- end
95
- end
96
-
97
- def create_entries(repos)
98
- entries = []
99
- repos.each do |repo|
100
- origin_url = repo.remote('origin').url
101
- current_branch_name = repo.current_branch.name
102
-
103
- next unless Console.ask("Do you want to add '#{repo.path}' as a dependency?\n [origin: #{origin_url || 'NONE'}, branch: #{current_branch_name}]")
104
-
105
- unless origin_url
106
- Console.log_warning("Repo 'origin' remote url is not set; skipping")
107
- next
108
- end
109
-
110
- entries.push(ConfigEntry.new(repo))
111
- Console.log_substep("Added the repository '#{repo.path}' to the .multirepo file")
112
- end
113
- return entries
114
- end
115
-
116
- def check_repo_exists
117
- fail MultiRepoException, "There is no folder at path '#{@repo.path}'" unless Dir.exist?(@repo.path)
118
- fail MultiRepoException, "'#{@repo.path}' is not a repository" unless @repo.exists?
119
- end
120
- end
121
- end
1
+ require "multirepo/utility/console"
2
+ require "multirepo/utility/utils"
3
+ require "multirepo/files/config-file"
4
+ require "multirepo/files/lock-file"
5
+ require "multirepo/files/tracking-files"
6
+ require "multirepo/commands/command"
7
+
8
+ module MultiRepo
9
+ class InitCommand < Command
10
+ self.command = "init"
11
+ self.summary = "Initialize the current repository as a multirepo project."
12
+
13
+ def self.options
14
+ [['[--extras]', 'Keep the current .multirepo config file as-is and initialize everything else.']].concat(super)
15
+ end
16
+
17
+ def initialize(argv)
18
+ @only_extras = argv.flag?("extras")
19
+ super
20
+ end
21
+
22
+ def run
23
+ ensure_in_work_tree
24
+
25
+ if @only_extras
26
+ ensure_multirepo_enabled
27
+ Console.log_step("Initializing extras...")
28
+ initialize_extras_step
29
+ else
30
+ Console.log_step("Initializing multirepo...")
31
+ full_initialize_step
32
+ end
33
+
34
+ Console.log_step("Done!")
35
+ end
36
+
37
+ def full_initialize_step
38
+ if ConfigFile.new(".").exists?
39
+ reinitialize = Console.ask(".multirepo file already exists. Reinitialize?")
40
+ fail MultiRepoException, "Initialization aborted" unless reinitialize
41
+ end
42
+
43
+ unless add_sibling_repos_step
44
+ fail MultiRepoException, "There are no sibling repositories to track as dependencies. Initialization aborted."
45
+ end
46
+
47
+ initialize_extras_step
48
+ end
49
+
50
+ def add_sibling_repos_step
51
+ sibling_repos = Utils.sibling_repos
52
+ return false unless sibling_repos.any?
53
+
54
+ Console.log_substep("Creating new multirepo config...")
55
+
56
+ valid_repos = find_valid_repos(sibling_repos)
57
+ entries = create_entries(valid_repos)
58
+
59
+ fail MultiRepoException, "No sibling repositories were added as dependencies; init aborted" unless entries.any?
60
+
61
+ ConfigFile.new(".").save_entries(entries)
62
+ return true
63
+ end
64
+
65
+ def initialize_extras_step
66
+ install_hooks_step
67
+ update_gitattributes_step
68
+ update_gitconfig_step
69
+ end
70
+
71
+ def install_hooks_step
72
+ install_hooks(".")
73
+ Console.log_substep("Installed git hooks")
74
+ end
75
+
76
+ def update_gitattributes_step
77
+ TrackingFiles.new(".").files.each do |f|
78
+ filename = f.filename
79
+ regex_escaped_filename = Regexp.quote(filename)
80
+ Utils.append_if_missing("./.gitattributes", /^#{regex_escaped_filename} .*/, "#{filename} merge=ours")
81
+ end
82
+ Console.log_substep("Updated .gitattributes file")
83
+ end
84
+
85
+ def update_gitconfig_step
86
+ update_gitconfig(".")
87
+ Console.log_substep("Updated .git/config file")
88
+ end
89
+
90
+ def find_valid_repos(repos)
91
+ repos.select do |repo|
92
+ next true if repo.head_born?
93
+ Console.log_warning("Ignoring repo '#{repo.path}' because its HEAD is unborn. You must perform at least one commit.")
94
+ end
95
+ end
96
+
97
+ def create_entries(repos)
98
+ entries = []
99
+ repos.each do |repo|
100
+ origin_url = repo.remote('origin').url
101
+ current_branch_name = repo.current_branch.name
102
+
103
+ next unless Console.ask("Do you want to add '#{repo.path}' as a dependency?\n [origin: #{origin_url || 'NONE'}, branch: #{current_branch_name}]")
104
+
105
+ unless origin_url
106
+ Console.log_warning("Repo 'origin' remote url is not set; skipping")
107
+ next
108
+ end
109
+
110
+ entries.push(ConfigEntry.new(repo))
111
+ Console.log_substep("Added the repository '#{repo.path}' to the .multirepo file")
112
+ end
113
+ return entries
114
+ end
115
+
116
+ def check_repo_exists
117
+ fail MultiRepoException, "There is no folder at path '#{@repo.path}'" unless Dir.exist?(@repo.path)
118
+ fail MultiRepoException, "'#{@repo.path}' is not a repository" unless @repo.exists?
119
+ end
120
+ end
121
+ end
@@ -1,48 +1,48 @@
1
- require "multirepo/utility/console"
2
- require "multirepo/utility/utils"
3
-
4
- module MultiRepo
5
- class InspectCommandStats
6
- VERSION = "version"
7
- TRACKED = "tracked"
8
- end
9
-
10
- class InspectCommand < Command
11
- self.command = "inspect"
12
- self.summary = "Outputs various information about multirepo-enabled repos. For use in scripting and CI scenarios."
13
-
14
- def self.options
15
- [['<stat name>', 'The name of the statistic to output.']].concat(super)
16
- end
17
-
18
- def initialize(argv)
19
- stat = argv.shift_argument
20
- @stat = stat ? stat.downcase : nil
21
- super
22
- end
23
-
24
- def validate!
25
- super
26
- help! "You must provide a valid stat name. Available stats: \n #{stats.join(', ')}" unless valid_stat?(@stat)
27
- end
28
-
29
- def stats
30
- InspectCommandStats.constants.map { |s| InspectCommandStats.const_get(s) }
31
- end
32
-
33
- def valid_stat?(stat)
34
- stats.include?(stat)
35
- end
36
-
37
- def run
38
- ensure_in_work_tree
39
-
40
- case @stat
41
- when InspectCommandStats::VERSION
42
- puts MetaFile.new(".").load.version
43
- when InspectCommandStats::TRACKED
44
- puts Utils.multirepo_tracked?(".").to_s
45
- end
46
- end
47
- end
48
- end
1
+ require "multirepo/utility/console"
2
+ require "multirepo/utility/utils"
3
+
4
+ module MultiRepo
5
+ class InspectCommandStats
6
+ VERSION = "version"
7
+ TRACKED = "tracked"
8
+ end
9
+
10
+ class InspectCommand < Command
11
+ self.command = "inspect"
12
+ self.summary = "Outputs various information about multirepo-enabled repos. For use in scripting and CI scenarios."
13
+
14
+ def self.options
15
+ [['<stat name>', 'The name of the statistic to output.']].concat(super)
16
+ end
17
+
18
+ def initialize(argv)
19
+ stat = argv.shift_argument
20
+ @stat = stat ? stat.downcase : nil
21
+ super
22
+ end
23
+
24
+ def validate!
25
+ super
26
+ help! "You must provide a valid stat name. Available stats: \n #{stats.join(', ')}" unless valid_stat?(@stat)
27
+ end
28
+
29
+ def stats
30
+ InspectCommandStats.constants.map { |s| InspectCommandStats.const_get(s) }
31
+ end
32
+
33
+ def valid_stat?(stat)
34
+ stats.include?(stat)
35
+ end
36
+
37
+ def run
38
+ ensure_in_work_tree
39
+
40
+ case @stat
41
+ when InspectCommandStats::VERSION
42
+ puts MetaFile.new(".").load.version
43
+ when InspectCommandStats::TRACKED
44
+ puts Utils.multirepo_tracked?(".").to_s
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,161 +1,161 @@
1
- require "terminal-table"
2
-
3
- require "multirepo/utility/console"
4
- require "multirepo/utility/utils"
5
- require "multirepo/output/extra-output"
6
- require "multirepo/git/repo"
7
- require "multirepo/logic/performer"
8
- require "multirepo/commands/checkout-command"
9
-
10
- module MultiRepo
11
- class InstallCommand < Command
12
- self.command = "install"
13
- self.summary = "Clones and checks out dependencies as defined in the version-controlled multirepo metadata files and installs git-multirepo's local git hooks."
14
-
15
- def self.options
16
- [
17
- ['[--hooks]', 'Only install local git hooks.'],
18
- ['[--ci]', 'Perform a continuous-integration-aware install (such as on a CI build server or agent).']
19
- ].concat(super)
20
- end
21
-
22
- def initialize(argv)
23
- @hooks = argv.flag?("hooks")
24
- @ci = argv.flag?("ci")
25
- super
26
- end
27
-
28
- def validate!
29
- super
30
- unless Utils.only_one_true?(@hooks, @ci)
31
- help! "You can't provide more than one operation modifier (--hooks, --ci, etc.)"
32
- end
33
- end
34
-
35
- def run
36
- ensure_in_work_tree unless @ci
37
- ensure_multirepo_tracked
38
-
39
- if @hooks
40
- Console.log_step("Installing hooks in main repo and all dependencies...")
41
- install_hooks_step
42
- else
43
- Console.log_step("Installing dependencies...")
44
- log_ci_info if @ci
45
- full_install
46
- end
47
-
48
- Console.log_step("Done!")
49
- end
50
-
51
- def log_ci_info
52
- Console.log_info("Performing continuous-integration-aware install")
53
- Console.log_info("Using git-multirepo #{MultiRepo::VERSION}")
54
-
55
- main_repo = Repo.new(".")
56
- main_repo_branch = main_repo.current_branch
57
- meta_file = MetaFile.new(".").load
58
-
59
- if main_repo.head.merge_commit?
60
- Console.log_warning("[MERGE COMMIT] The checked-out main repo revision is a merge commit.")
61
- Console.log_warning("[MERGE COMMIT] Lock file might not represent a valid project state.")
62
- end
63
-
64
- table = Terminal::Table.new do |t|
65
- t.title = "Revision Info"
66
- t.add_row ["Tracked Using", "git-multirepo #{meta_file.version}"]
67
- t.add_separator
68
- t.add_row ["Main Repo", commit_info(main_repo.head.commit_id, (main_repo_branch.name rescue nil))]
69
- t.add_separator
70
- LockFile.new(".").load_entries.each do |lock_entry|
71
- branch_name = lock_entry.branch
72
- t.add_row [lock_entry.name, commit_info(lock_entry.head, branch_name)]
73
- end
74
- end
75
- puts table
76
- end
77
-
78
- def commit_info(commit_id, branch_name)
79
- commit_id + (branch_name ? " (on branch #{branch_name})" : "")
80
- end
81
-
82
- def full_install
83
- install_dependencies_step
84
- install_hooks_step unless @ci
85
- update_gitconfigs_step unless @ci
86
- end
87
-
88
- def install_dependencies_step
89
- # Read config entries as-is on disk, without prior checkout
90
- config_entries = ConfigFile.new(".").load_entries
91
- Console.log_substep("Installing #{config_entries.count} dependencies...")
92
-
93
- # Clone or fetch all configured dependencies to make sure nothing is missing locally
94
- Performer.dependencies.each { |d| clone_or_fetch(d) }
95
-
96
- # Checkout the appropriate branches as specified in the lock file
97
- ExtraOutput.progress("Checking out appropriate dependency revisions") if @ci
98
- checkout_command = CheckoutCommand.new(CLAide::ARGV.new([]))
99
- mode = @ci ? RevisionSelection::AS_LOCK : RevisionSelection::LATEST
100
- checkout_command.dependencies_checkout_step(mode)
101
- end
102
-
103
- def install_hooks_step
104
- perform_in_main_repo_and_dependencies("Installed git hooks") { |repo| install_hooks(repo) }
105
- end
106
-
107
- def update_gitconfigs_step
108
- perform_in_main_repo_and_dependencies("Updated .git/config file") { |repo| update_gitconfig(repo) }
109
- end
110
-
111
- def perform_in_main_repo_and_dependencies(message_prefix, &operation)
112
- operation.call(".")
113
- Console.log_substep("#{message_prefix} in main repo")
114
-
115
- multirepo_enabled_dependencies.each do |config_entry|
116
- operation.call(config_entry.repo.path)
117
- Console.log_substep("#{message_prefix} in multirepo-enabled dependency '#{config_entry.repo.path}'")
118
- end
119
- end
120
-
121
- # Repo operations
122
-
123
- def clone_or_fetch(dependency)
124
- if dependency.config_entry.repo.exists?
125
- check_repo_validity(dependency)
126
-
127
- Console.log_substep("Working copy '#{dependency.config_entry.repo.path}' already exists, fetching...")
128
- ExtraOutput.progress("Fetching #{dependency.config_entry.repo.basename}") if @ci
129
- fetch_repo(dependency)
130
- else
131
- Console.log_substep("Cloning #{dependency.config_entry.url} into '#{dependency.config_entry.repo.path}'")
132
- ExtraOutput.progress("Cloning into #{dependency.config_entry.repo.basename}") if @ci
133
- clone_repo(dependency)
134
- end
135
- end
136
-
137
- def fetch_repo(dependency)
138
- unless dependency.config_entry.repo.fetch
139
- ExtraOutput.error("Failed to fetch #{dependency.config_entry.repo.basename}") if @ci
140
- fail MultiRepoException, "Could not fetch from remote #{dependency.config_entry.repo.remote('origin').url}"
141
- end
142
- end
143
-
144
- def clone_repo(dependency)
145
- options = { :branch => dependency.lock_entry.branch, :quiet => @ci }
146
- unless dependency.config_entry.repo.clone(dependency.config_entry.url, options)
147
- ExtraOutput.error("Failed to clone #{dependency.config_entry.repo.basename}") if @ci
148
- fail MultiRepoException, "Could not clone remote #{dependency.config_entry.url} with branch #{dependency.config_entry.branch}"
149
- end
150
- end
151
-
152
- # Validation
153
-
154
- def check_repo_validity(dependency)
155
- unless dependency.config_entry.repo.remote("origin").url == dependency.config_entry.url
156
- ExtraOutput.error("Repo #{dependency.config_entry.path} origin URL does not match config") if @ci
157
- fail MultiRepoException, "'#{dependency.config_entry.path}' origin URL (#{dependency.config_entry.repo.remote('origin').url}) does not match entry (#{dependency.config_entry.url})!"
158
- end
159
- end
160
- end
161
- end
1
+ require "terminal-table"
2
+
3
+ require "multirepo/utility/console"
4
+ require "multirepo/utility/utils"
5
+ require "multirepo/output/extra-output"
6
+ require "multirepo/git/repo"
7
+ require "multirepo/logic/performer"
8
+ require "multirepo/commands/checkout-command"
9
+
10
+ module MultiRepo
11
+ class InstallCommand < Command
12
+ self.command = "install"
13
+ self.summary = "Clones and checks out dependencies as defined in the version-controlled multirepo metadata files and installs git-multirepo's local git hooks."
14
+
15
+ def self.options
16
+ [
17
+ ['[--hooks]', 'Only install local git hooks.'],
18
+ ['[--ci]', 'Perform a continuous-integration-aware install (such as on a CI build server or agent).']
19
+ ].concat(super)
20
+ end
21
+
22
+ def initialize(argv)
23
+ @hooks = argv.flag?("hooks")
24
+ @ci = argv.flag?("ci")
25
+ super
26
+ end
27
+
28
+ def validate!
29
+ super
30
+ unless Utils.only_one_true?(@hooks, @ci)
31
+ help! "You can't provide more than one operation modifier (--hooks, --ci, etc.)"
32
+ end
33
+ end
34
+
35
+ def run
36
+ ensure_in_work_tree unless @ci
37
+ ensure_multirepo_tracked
38
+
39
+ if @hooks
40
+ Console.log_step("Installing hooks in main repo and all dependencies...")
41
+ install_hooks_step
42
+ else
43
+ Console.log_step("Installing dependencies...")
44
+ log_ci_info if @ci
45
+ full_install
46
+ end
47
+
48
+ Console.log_step("Done!")
49
+ end
50
+
51
+ def log_ci_info
52
+ Console.log_info("Performing continuous-integration-aware install")
53
+ Console.log_info("Using git-multirepo #{MultiRepo::VERSION}")
54
+
55
+ main_repo = Repo.new(".")
56
+ main_repo_branch = main_repo.current_branch
57
+ meta_file = MetaFile.new(".").load
58
+
59
+ if main_repo.head.merge_commit?
60
+ Console.log_warning("[MERGE COMMIT] The checked-out main repo revision is a merge commit.")
61
+ Console.log_warning("[MERGE COMMIT] Lock file might not represent a valid project state.")
62
+ end
63
+
64
+ table = Terminal::Table.new do |t|
65
+ t.title = "Revision Info"
66
+ t.add_row ["Tracked Using", "git-multirepo #{meta_file.version}"]
67
+ t.add_separator
68
+ t.add_row ["Main Repo", commit_info(main_repo.head.commit_id, (main_repo_branch.name rescue nil))]
69
+ t.add_separator
70
+ LockFile.new(".").load_entries.each do |lock_entry|
71
+ branch_name = lock_entry.branch
72
+ t.add_row [lock_entry.name, commit_info(lock_entry.head, branch_name)]
73
+ end
74
+ end
75
+ puts table
76
+ end
77
+
78
+ def commit_info(commit_id, branch_name)
79
+ commit_id + (branch_name ? " (on branch #{branch_name})" : "")
80
+ end
81
+
82
+ def full_install
83
+ install_dependencies_step
84
+ install_hooks_step unless @ci
85
+ update_gitconfigs_step unless @ci
86
+ end
87
+
88
+ def install_dependencies_step
89
+ # Read config entries as-is on disk, without prior checkout
90
+ config_entries = ConfigFile.new(".").load_entries
91
+ Console.log_substep("Installing #{config_entries.count} dependencies...")
92
+
93
+ # Clone or fetch all configured dependencies to make sure nothing is missing locally
94
+ Performer.dependencies.each { |d| clone_or_fetch(d) }
95
+
96
+ # Checkout the appropriate branches as specified in the lock file
97
+ ExtraOutput.progress("Checking out appropriate dependency revisions") if @ci
98
+ checkout_command = CheckoutCommand.new(CLAide::ARGV.new([]))
99
+ mode = @ci ? RevisionSelection::AS_LOCK : RevisionSelection::LATEST
100
+ checkout_command.dependencies_checkout_step(mode)
101
+ end
102
+
103
+ def install_hooks_step
104
+ perform_in_main_repo_and_dependencies("Installed git hooks") { |repo| install_hooks(repo) }
105
+ end
106
+
107
+ def update_gitconfigs_step
108
+ perform_in_main_repo_and_dependencies("Updated .git/config file") { |repo| update_gitconfig(repo) }
109
+ end
110
+
111
+ def perform_in_main_repo_and_dependencies(message_prefix, &operation)
112
+ operation.call(".")
113
+ Console.log_substep("#{message_prefix} in main repo")
114
+
115
+ multirepo_enabled_dependencies.each do |config_entry|
116
+ operation.call(config_entry.repo.path)
117
+ Console.log_substep("#{message_prefix} in multirepo-enabled dependency '#{config_entry.repo.path}'")
118
+ end
119
+ end
120
+
121
+ # Repo operations
122
+
123
+ def clone_or_fetch(dependency)
124
+ if dependency.config_entry.repo.exists?
125
+ check_repo_validity(dependency)
126
+
127
+ Console.log_substep("Working copy '#{dependency.config_entry.repo.path}' already exists, fetching...")
128
+ ExtraOutput.progress("Fetching #{dependency.config_entry.repo.basename}") if @ci
129
+ fetch_repo(dependency)
130
+ else
131
+ Console.log_substep("Cloning #{dependency.config_entry.url} into '#{dependency.config_entry.repo.path}'")
132
+ ExtraOutput.progress("Cloning into #{dependency.config_entry.repo.basename}") if @ci
133
+ clone_repo(dependency)
134
+ end
135
+ end
136
+
137
+ def fetch_repo(dependency)
138
+ unless dependency.config_entry.repo.fetch
139
+ ExtraOutput.error("Failed to fetch #{dependency.config_entry.repo.basename}") if @ci
140
+ fail MultiRepoException, "Could not fetch from remote #{dependency.config_entry.repo.remote('origin').url}"
141
+ end
142
+ end
143
+
144
+ def clone_repo(dependency)
145
+ options = { :branch => dependency.lock_entry.branch, :quiet => @ci }
146
+ unless dependency.config_entry.repo.clone(dependency.config_entry.url, options)
147
+ ExtraOutput.error("Failed to clone #{dependency.config_entry.repo.basename}") if @ci
148
+ fail MultiRepoException, "Could not clone remote #{dependency.config_entry.url} with branch #{dependency.lock_entry.branch}"
149
+ end
150
+ end
151
+
152
+ # Validation
153
+
154
+ def check_repo_validity(dependency)
155
+ unless dependency.config_entry.repo.remote("origin").url == dependency.config_entry.url
156
+ ExtraOutput.error("Repo #{dependency.config_entry.path} origin URL does not match config") if @ci
157
+ fail MultiRepoException, "'#{dependency.config_entry.path}' origin URL (#{dependency.config_entry.repo.remote('origin').url}) does not match entry (#{dependency.config_entry.url})!"
158
+ end
159
+ end
160
+ end
161
+ end