mirror of
https://github.com/KingDuckZ/dindexer.git
synced 2024-12-02 02:05:41 +00:00
King_DuckZ
2e5bb32439
It now has a clone subcommand that does the same thing as before, and a status subcommand that tells the user what submodules are flattened.
244 lines
7.8 KiB
Ruby
Executable file
244 lines
7.8 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
|
|
=begin
|
|
This is free and unencumbered software released into the public domain.
|
|
|
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
distribute this software, either in source code form or as a compiled
|
|
binary, for any purpose, commercial or non-commercial, and by any
|
|
means.
|
|
|
|
In jurisdictions that recognize copyright laws, the author or authors
|
|
of this software dedicate any and all copyright interest in the
|
|
software to the public domain. We make this dedication for the benefit
|
|
of the public at large and to the detriment of our heirs and
|
|
successors. We intend this dedication to be an overt act of
|
|
relinquishment in perpetuity of all present and future rights to this
|
|
software under copyright law.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
For more information, please refer to <http://unlicense.org/>
|
|
=end
|
|
|
|
require 'shellwords'
|
|
require 'pathname'
|
|
require 'yaml'
|
|
require 'uri'
|
|
|
|
GIT="git"
|
|
|
|
SubmoduleInfo = Struct.new(:path, :url, :status, :name, :hash)
|
|
|
|
def call_git(parTokens, parCaptureStdout=true, work_dir: nil)
|
|
git_c_param = ""
|
|
if work_dir then
|
|
raise ScriptError, "Tainted path received in call_git() function" if work_dir.tainted?
|
|
git_c_param = " -C #{work_dir}"
|
|
end
|
|
|
|
command_line = ""
|
|
if parTokens.is_a?String then
|
|
raise ScriptError, "Tainted token received in call_git() function" if parTokens.tainted?
|
|
command_line = parTokens
|
|
elsif parTokens.is_a?Array then
|
|
parTokens.each do |tok,idx|
|
|
raise ScriptError, "Tainted token at index #{idx} received in call_git() function" if tok.tainted?
|
|
end
|
|
command_line = parTokens.join(" ")
|
|
else
|
|
raise ScriptError, "Invalid parTokens received in call_git() function"
|
|
end
|
|
|
|
#puts "#{GIT}#{git_c_param} #{command_line}"
|
|
if parCaptureStdout then
|
|
return `#{GIT}#{git_c_param} #{command_line}`.chomp
|
|
else
|
|
return Kernel.system("#{GIT}#{git_c_param} #{command_line}")
|
|
end
|
|
end
|
|
|
|
def is_absolute_url?(parUrl)
|
|
return not(/^\.{1,2}\// =~ parUrl)
|
|
end
|
|
|
|
def sanitized(parString)
|
|
retval = Shellwords.escape(parString)
|
|
retval.untaint
|
|
return retval
|
|
end
|
|
|
|
def current_remote_url()
|
|
current_branch = call_git(["rev-parse", "--abbrev-ref", "HEAD"])
|
|
current_remote = call_git(["config", "branch.#{sanitized(current_branch)}.remote"])
|
|
remote_url = call_git(["config", "remote.#{sanitized(current_remote)}.url"])
|
|
return remote_url
|
|
end
|
|
|
|
def make_git_address(parBaseRemoteUrl, parSuffix)
|
|
regex_protocol = /^\w+:\/\//
|
|
m = regex_protocol.match(parBaseRemoteUrl)
|
|
if m then
|
|
URI.join(parBaseRemoteUrl + "/", parSuffix).to_s
|
|
else
|
|
Pathname.new(File.join(parBaseRemoteUrl, parSuffix)).cleanpath.to_s
|
|
end
|
|
end
|
|
|
|
def submodules_info(parBaseRemoteUrl)
|
|
regex_submodule = /^submodule\.(.+?)\.([^.]+)$/
|
|
submodules = Hash.new{ |hash, key| hash[key] = SubmoduleInfo.new }
|
|
call_git(["config", "--file", ".gitmodules", "--name-only", "--list"]).lines.map(&:chomp).each do |line|
|
|
m = regex_submodule.match(line)
|
|
next if m.nil?
|
|
|
|
new_value = call_git(["config", "--file", ".gitmodules", sanitized(line)])
|
|
case m[2]
|
|
when "url" then
|
|
submodules[m[1]].url = is_absolute_url?(new_value) ? new_value : make_git_address(parBaseRemoteUrl, new_value)
|
|
submodules[m[1]].name = File.basename(new_value, ".git")
|
|
when "path" then
|
|
submodules[m[1]].path = new_value
|
|
end
|
|
end
|
|
|
|
regex_status = /^(.)([0-9a-fA-F]{40})\s+(.+?)(?:\s+\(.+\))?$/
|
|
call_git(["submodule", "status"]).lines.map(&:chomp).each do |line|
|
|
m = regex_status.match(line)
|
|
next if m.nil?
|
|
|
|
submodules[m[3]].status = m[1]
|
|
submodules[m[3]].hash = m[2]
|
|
end
|
|
return submodules
|
|
end
|
|
|
|
def is_inside_git_repo(parPath)
|
|
reply = call_git("rev-parse --is-inside-work-tree 2> /dev/null || echo false", work_dir: parPath)
|
|
return "true" == reply
|
|
end
|
|
|
|
class FlatGit
|
|
def initialize(parCloneDir)
|
|
unless File.exists?(parCloneDir) && File.directory?(parCloneDir) then
|
|
raise ArgumentError, "Specified path \"#{parCloneDir}\" doesn't exist or is not a valid directory"
|
|
end
|
|
|
|
if is_inside_git_repo(sanitized(parCloneDir)) then
|
|
raise ArgumentError, "Specified path is invalid because it appears to be inside a git repository"
|
|
end
|
|
|
|
@clone_dir_abs = Pathname.new(parCloneDir).realpath
|
|
end
|
|
|
|
def clone_submodules()
|
|
inplace_submodules = if File.file?("flat_git.yml") then
|
|
local_settings = YAML.load_file("flat_git.yml")
|
|
lst = local_settings["inplace_submodules"]
|
|
lst = [lst] if lst.is_a?(String)
|
|
lst.is_a?(Array) && lst || Array.new
|
|
else
|
|
Array.new
|
|
end
|
|
|
|
submodules_info(current_remote_url()).each_value do |submod|
|
|
next unless submod.status == "-"
|
|
|
|
abs_guessed_clone_dir = File.join(@clone_dir_abs, submod.name)
|
|
if inplace_submodules.include?(submod.name) then
|
|
success = call_git(["submodule", "update", "--init", sanitized(submod.path)], false)
|
|
return false unless success
|
|
else
|
|
if !File.exists?(abs_guessed_clone_dir) || (File.directory?(abs_guessed_clone_dir) && Dir.entries(abs_guessed_clone_dir).empty?) then
|
|
success = call_git(["clone", sanitized(submod.url)], false, work_dir: sanitized(@clone_dir_abs))
|
|
return false unless success
|
|
|
|
if File.exists?(abs_guessed_clone_dir) && File.directory?(abs_guessed_clone_dir) then
|
|
call_git(["config", "core.worktree", ".."], work_dir: sanitized(abs_guessed_clone_dir))
|
|
call_git(["reset", "--hard", sanitized(submod.hash)], work_dir: sanitized(abs_guessed_clone_dir))
|
|
|
|
Dir.chdir(abs_guessed_clone_dir) do |path|
|
|
if File.file?(".gitmodules") then
|
|
success = clone_submodules()
|
|
return false unless success
|
|
end
|
|
end
|
|
else
|
|
raise RuntimeError, "Unable to guess where git just cloned the repo"
|
|
end
|
|
end
|
|
call_git(["submodule", "init", sanitized(submod.path)])
|
|
File.open(File.join(submod.path, ".git"), 'w') {|file| file.puts("gitdir: #{File.join(abs_guessed_clone_dir, ".git")}")}
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
def show_status()
|
|
submodules_info(current_remote_url()).each_value do |submod|
|
|
if submod.status == "-" then
|
|
puts "#{submod.name} is not initialized"
|
|
elsif submod.status == "+" || submod.status == " " then
|
|
print "#{submod.name} is initialized"
|
|
|
|
flattened = false
|
|
if File.file?(File.join(submod.path, ".git")) then
|
|
reported_abs_path = call_git(["rev-parse", "--show-toplevel"], work_dir: sanitized(submod.path))
|
|
reported_rel_path = Pathname.new(reported_abs_path).relative_path_from(Pathname.new(Dir.pwd)).to_s
|
|
flattened = true if reported_rel_path.start_with?("../")
|
|
end
|
|
if flattened then
|
|
print ", has local changes" if submod.status == "+"
|
|
puts " and seems to be flattened"
|
|
else
|
|
print " inplace"
|
|
print " and has local changes" if submod.status == "+"
|
|
Dir.chdir(submod.path) do |path|
|
|
if File.file?(".gitmodules") then
|
|
puts ", checking its submodules..."
|
|
show_status()
|
|
else
|
|
puts
|
|
end
|
|
end
|
|
end
|
|
else
|
|
puts "#{submod.name} reports unknown \"#{submod.status}\" status"
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
end
|
|
|
|
def main(parArgv)
|
|
unless ARGV.length > 0 then
|
|
$stderr.puts "Wrong number of arguments"
|
|
$stderr.puts "\t#{File.basename(__FILE__, ".rb")} (clone|status) [arguments...]"
|
|
return 2
|
|
end
|
|
|
|
clone_dir = (ARGV.size > 1 ? ARGV[1] : "..")
|
|
flat_git = FlatGit.new(clone_dir)
|
|
|
|
case ARGV[0]
|
|
when "status" then
|
|
return (flat_git.show_status() ? 0 : 1)
|
|
when "clone" then
|
|
if ARGV.size != 2 then
|
|
$stderr.puts "Missing path to directory where flattened submodules will be cloned"
|
|
return 2
|
|
end
|
|
return (flat_git.clone_submodules() ? 0 : 1)
|
|
end
|
|
|
|
$stderr.puts "Unknown command"
|
|
return 2
|
|
end
|
|
|
|
exit(main(ARGV))
|