git-multirepo 1.0.0.beta1

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +38 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +37 -0
  6. data/LICENSE +22 -0
  7. data/README.md +132 -0
  8. data/Rakefile +2 -0
  9. data/bin/multi +6 -0
  10. data/git-multirepo.gemspec +29 -0
  11. data/lib/commands.rb +11 -0
  12. data/lib/git-multirepo.rb +1 -0
  13. data/lib/info.rb +5 -0
  14. data/lib/multirepo/commands/add.rb +41 -0
  15. data/lib/multirepo/commands/checkout.rb +59 -0
  16. data/lib/multirepo/commands/command.rb +41 -0
  17. data/lib/multirepo/commands/edit.rb +22 -0
  18. data/lib/multirepo/commands/fetch.rb +24 -0
  19. data/lib/multirepo/commands/init.rb +54 -0
  20. data/lib/multirepo/commands/install.rb +61 -0
  21. data/lib/multirepo/commands/open.rb +26 -0
  22. data/lib/multirepo/commands/remove.rb +42 -0
  23. data/lib/multirepo/commands/uninit.rb +21 -0
  24. data/lib/multirepo/commands/update.rb +19 -0
  25. data/lib/multirepo/config.rb +10 -0
  26. data/lib/multirepo/files/config-entry.rb +38 -0
  27. data/lib/multirepo/files/config-file.rb +38 -0
  28. data/lib/multirepo/files/lock-entry.rb +26 -0
  29. data/lib/multirepo/files/lock-file.rb +35 -0
  30. data/lib/multirepo/git/branch.rb +17 -0
  31. data/lib/multirepo/git/change.rb +11 -0
  32. data/lib/multirepo/git/git.rb +33 -0
  33. data/lib/multirepo/git/remote.rb +16 -0
  34. data/lib/multirepo/git/repo.rb +67 -0
  35. data/lib/multirepo/hooks/pre-commit-hook.rb +23 -0
  36. data/lib/multirepo/multirepo-exception.rb +6 -0
  37. data/lib/multirepo/utility/console.rb +52 -0
  38. data/lib/multirepo/utility/runner.rb +21 -0
  39. data/lib/multirepo/utility/utils.rb +37 -0
  40. data/resources/pre-commit +6 -0
  41. data/spec/integration/init-spec.rb +23 -0
  42. data/spec/spec_helper.rb +89 -0
  43. metadata +178 -0
@@ -0,0 +1,22 @@
1
+ require "os"
2
+
3
+ module MultiRepo
4
+ class Edit < Command
5
+ self.command = "edit"
6
+ self.summary = "Opens the .multirepo file in the default text editor."
7
+
8
+ def run
9
+ super
10
+ ensure_multirepo_initialized
11
+
12
+ if OS.posix?
13
+ editor = `echo ${FCEDIT:-${VISUAL:-${EDITOR:-vi}}}`.strip
14
+ system(editor, ".multirepo")
15
+ elsif OS.windows?
16
+ raise "The edit command is not implemented on Window yet."
17
+ end
18
+ rescue MultiRepoException => e
19
+ Console.log_error(e.message)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ require "multirepo/utility/console"
2
+
3
+ module MultiRepo
4
+ class Fetch < Command
5
+ self.command = "fetch"
6
+ self.summary = "Performs a git fetch on all dependencies."
7
+
8
+ def run
9
+ super
10
+ ensure_multirepo_initialized
11
+
12
+ Console.log_step("Fetching dependencies...")
13
+
14
+ ConfigFile.load.each do |entry|
15
+ Console.log_substep("Fetching from #{entry.repo.remote('origin').url}...")
16
+ entry.repo.fetch
17
+ end
18
+
19
+ Console.log_step("Done!")
20
+ rescue MultiRepoException => e
21
+ Console.log_error(e.message)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,54 @@
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/commands/command"
6
+
7
+ module MultiRepo
8
+ class Init < Command
9
+ self.command = "init"
10
+ self.summary = "Initialize the current repository as a multirepo project."
11
+
12
+ def run
13
+ super
14
+ Console.log_step("Initializing new multirepo config...")
15
+
16
+ if ConfigFile.exists?
17
+ return unless Console.ask_yes_no(".multirepo file already exists. Reinitialize?")
18
+ end
19
+
20
+ sibling_repos = Utils.sibling_repos
21
+
22
+ if sibling_repos.any?
23
+ entries = []
24
+ sibling_repos.each do |repo|
25
+ if Console.ask_yes_no("Do you want to add '#{repo.path}' (#{repo.remote('origin').url} #{repo.current_branch}) as a dependency?")
26
+ entries.push(ConfigEntry.new(repo))
27
+ Console.log_substep("Added the repository '#{repo.path}' to the .multirepo file")
28
+ end
29
+ end
30
+ ConfigFile.save(entries)
31
+
32
+ ConfigFile.stage
33
+
34
+ uncommitted = Utils.warn_of_uncommitted_changes(entries)
35
+ raise MultiRepoException, "Can't finish initialization!" if uncommitted
36
+
37
+ self.update_lock_file
38
+ else
39
+ Console.log_info("There are no sibling repositories to add")
40
+ end
41
+
42
+ self.install_pre_commit_hook
43
+
44
+ Console.log_step("Done!")
45
+ rescue MultiRepoException => e
46
+ Console.log_error(e.message)
47
+ end
48
+
49
+ def check_repo_exists
50
+ if !Dir.exists?(@repo.path) then raise MultiRepoException, "There is no folder at path '#{@repo.path}'" end
51
+ if !@repo.exists? then raise MultiRepoException, "'#{@repo.path}' is not a repository" end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,61 @@
1
+ require "multirepo/utility/console"
2
+ require "multirepo/utility/utils"
3
+ require "multirepo/git/repo"
4
+
5
+ module MultiRepo
6
+ class Setup < Command
7
+ self.command = "install"
8
+ self.summary = "Clones and checks out dependencies as defined in the .multirepo file, and installs git-multirepo's local pre-commit hook."
9
+
10
+ def run
11
+ super
12
+ ensure_multirepo_initialized
13
+
14
+ Console.log_step("Cloning dependencies and installing hook...")
15
+
16
+ ConfigFile.load.each { |e| install(e) }
17
+
18
+ self.install_pre_commit_hook
19
+
20
+ Console.log_step("Done!")
21
+ rescue MultiRepoException => e
22
+ Console.log_error(e.message)
23
+ end
24
+
25
+ def install(entry)
26
+ if entry.repo.exists?
27
+ check_repo_validity(entry)
28
+ fetch_repo(entry)
29
+ else
30
+ clone_repo(entry)
31
+ end
32
+ checkout_branch(entry)
33
+ end
34
+
35
+ # Repo operations
36
+
37
+ def fetch_repo(entry)
38
+ Console.log_substep("Working copy '#{entry.repo.path}' already exists, fetching instead...")
39
+ raise MultiRepoException, "Could not fetch from remote #{entry.repo.remote('origin').url}" unless entry.repo.fetch
40
+ end
41
+
42
+ def clone_repo(entry)
43
+ Console.log_substep("Cloning '#{entry.url} to #{entry.repo.path}'")
44
+ raise MultiRepoException, "Could not clone remote #{entry.url}" unless entry.repo.clone(entry.url)
45
+ end
46
+
47
+ def checkout_branch(entry)
48
+ branch = entry.repo.branch(entry.branch);
49
+ raise MultiRepoException, "Could not checkout branch #{branch.name}" unless branch.checkout
50
+ Console.log_substep("Checked out branch #{branch.name} -> origin/#{branch.name}")
51
+ end
52
+
53
+ # Validation
54
+
55
+ def check_repo_validity(entry)
56
+ unless entry.repo.remote("origin").url == entry.url
57
+ raise MultiRepoException, "'#{entry.path}' origin URL (#{entry.repo.remote('origin').url}) does not match entry (#{entry.url})!"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ require "os"
2
+
3
+ require "multirepo/utility/console"
4
+ require "multirepo/utility/utils"
5
+
6
+ module MultiRepo
7
+ class Open < 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_multirepo_initialized
14
+
15
+ ConfigFile.load.each do |entry|
16
+ if OS.osx?
17
+ `open "#{entry.repo.path}"`
18
+ elsif OS.windows?
19
+ `explorer "#{Utils.convert_to_windows_path(entry.repo.path)}"`
20
+ end
21
+ end
22
+ rescue MultiRepoException => e
23
+ Console.log_error(e.message)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ require "multirepo/utility/console"
2
+ require "multirepo/files/config-file"
3
+
4
+ module MultiRepo
5
+ class Remove < Command
6
+ self.command = "remove"
7
+ self.summary = "Removes the specified dependency from multirepo."
8
+
9
+ def initialize(argv)
10
+ @path = argv.shift_argument
11
+ @delete = argv.flag?("delete")
12
+ super
13
+ end
14
+
15
+ def validate!
16
+ super
17
+ help! "You must specify a dependency repository to remove" unless @path
18
+ end
19
+
20
+ def run
21
+ super
22
+ ensure_multirepo_initialized
23
+
24
+ repo = Repo.new(@path)
25
+ entry = ConfigEntry.new(repo)
26
+
27
+ if ConfigFile.entry_exists?(entry)
28
+ ConfigFile.remove_entry(entry)
29
+ Console.log_step("Removed '#{@path}' from the .multirepo file")
30
+
31
+ if @delete
32
+ FileUtils.rm_rf(@path)
33
+ Console.log_step("Deleted '#{@path}' from disk")
34
+ end
35
+ else
36
+ raise MultiRepoException, "'#{@path}' isn't tracked by multirepo"
37
+ end
38
+ rescue MultiRepoException => e
39
+ Console.log_error(e.message)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ require "multirepo/utility/console"
2
+
3
+ module MultiRepo
4
+ class Uninit < Command
5
+ self.command = "uninit"
6
+ self.summary = "Removes all traces of multirepo in the current multirepo repository."
7
+
8
+ def run
9
+ super
10
+ ensure_multirepo_initialized
11
+
12
+ File.delete(".multirepo")
13
+ File.delete(".multirepo.lock")
14
+ File.delete(".git/hooks/pre-commit")
15
+
16
+ Console.log_step("All traces of multirepo have been removed from this repository")
17
+ rescue MultiRepoException => e
18
+ Console.log_error(e.message)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require "multirepo/utility/console"
2
+
3
+ module MultiRepo
4
+ class Update < Command
5
+ self.command = "update"
6
+ self.summary = "Force-updates the multirepo lock file."
7
+
8
+ def run
9
+ super
10
+ ensure_multirepo_initialized
11
+
12
+ LockFile.update
13
+
14
+ Console.log_step("Lock file updated")
15
+ rescue MultiRepoException => e
16
+ Console.log_error(e.message)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ require "singleton"
2
+
3
+ module MultiRepo
4
+ class Config
5
+ include Singleton
6
+
7
+ attr_accessor :verbose
8
+ @verbose = false
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+ require "securerandom"
2
+
3
+ require "multirepo/utility/console"
4
+ require "multirepo/git/repo"
5
+
6
+ module MultiRepo
7
+ class ConfigEntry
8
+ attr_accessor :id
9
+ attr_accessor :path
10
+ attr_accessor :url
11
+ attr_accessor :branch
12
+ attr_accessor :repo
13
+
14
+ def encode_with(coder)
15
+ coder["id"] = @id
16
+ coder["path"] = @path
17
+ coder["url"] = @url
18
+ coder["branch"] = @branch
19
+ end
20
+
21
+ def ==(entry)
22
+ entry_path = Pathname.new(entry.path)
23
+ self_path = Pathname.new(self.path)
24
+ entry_path.exist? && self_path.exist? && entry_path.realpath == self_path.realpath
25
+ end
26
+
27
+ def initialize(repo)
28
+ @id = SecureRandom.uuid
29
+ @path = repo.path
30
+ @url = repo.exists? ? repo.remote('origin').url : nil
31
+ @branch = repo.exists? ? repo.current_branch : nil
32
+ end
33
+
34
+ def repo
35
+ Repo.new(path)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+
4
+ require_relative "config-entry"
5
+
6
+ module MultiRepo
7
+ class ConfigFile
8
+ FILE = Pathname.new(".multirepo")
9
+
10
+ def self.exists?
11
+ FILE.exist?
12
+ end
13
+
14
+ def self.load
15
+ Psych.load(FILE.read)
16
+ end
17
+
18
+ def self.save(entries)
19
+ File.write(FILE.to_s, Psych.dump(entries))
20
+ end
21
+
22
+ def self.entry_exists?(entry)
23
+ load.any? { |e| e == entry }
24
+ end
25
+
26
+ def self.add_entry(entry)
27
+ save(load.push(entry))
28
+ end
29
+
30
+ def self.remove_entry(entry)
31
+ save(load.delete_if { |e| e == entry })
32
+ end
33
+
34
+ def self.stage
35
+ Git.run_in_current_dir("add -A -f #{FILE.to_s}", false)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ require "multirepo/utility/console"
2
+ require "multirepo/git/repo"
3
+
4
+ module MultiRepo
5
+ class LockEntry
6
+ attr_accessor :config_entry
7
+ attr_accessor :name
8
+ attr_accessor :id
9
+ attr_accessor :head
10
+ attr_accessor :branch
11
+
12
+ def encode_with(coder)
13
+ coder["name"] = @name
14
+ coder["id"] = @id
15
+ coder["head"] = @head
16
+ coder["branch"] = @branch
17
+ end
18
+
19
+ def initialize(config_entry)
20
+ @name = config_entry.repo.basename
21
+ @id = config_entry.id
22
+ @head = config_entry.repo.head_hash
23
+ @branch = config_entry.repo.current_branch
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ require "pathname"
2
+ require "psych"
3
+
4
+ require "multirepo/git/git"
5
+ require_relative "lock-entry"
6
+ require_relative "config-file"
7
+
8
+ module MultiRepo
9
+ class LockFile
10
+ FILE = Pathname.new(".multirepo.lock")
11
+
12
+ def self.exists?
13
+ FILE.exist?
14
+ end
15
+
16
+ def self.load
17
+ Psych.load(FILE.read)
18
+ end
19
+
20
+ def self.update
21
+ config_entries = ConfigFile.load
22
+ lock_entries = config_entries.map { |c| LockEntry.new(c) }
23
+
24
+ File.write(FILE.to_s, Psych.dump(lock_entries))
25
+
26
+ Git.run_in_current_dir("add -A -f #{FILE.to_s}", false)
27
+ end
28
+
29
+ def self.validate_components(line, components)
30
+ unless components.count == 2
31
+ raise MultiRepoException, "Wrong entry format in .multirepo.lock file: #{line}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ require_relative "git"
2
+
3
+ module MultiRepo
4
+ class Branch
5
+ attr_accessor :name
6
+
7
+ def initialize(repo, name)
8
+ @repo = repo
9
+ @name = name
10
+ end
11
+
12
+ def checkout
13
+ Git.run_in_working_dir(@repo.path, "checkout #{@name}", false)
14
+ Git.last_command_succeeded
15
+ end
16
+ end
17
+ end