307 lines
12 KiB
Lua
307 lines
12 KiB
Lua
|
-- Encapsulated Git functionality
|
||
|
|
||
|
module ("dist.git", package.seeall)
|
||
|
|
||
|
require "git"
|
||
|
local sys = require "dist.sys"
|
||
|
local cfg = require "dist.config"
|
||
|
|
||
|
|
||
|
-- Clone the repository from url to dest_dir
|
||
|
function clone(repository_url, dest_dir, depth, branch)
|
||
|
assert(type(repository_url) == "string", "git.clone: Argument 'repository_url' is not a string.")
|
||
|
assert(type(dest_dir) == "string", "git.clone: Argument 'dest_dir' is not a string.")
|
||
|
dest_dir = sys.abs_path(dest_dir)
|
||
|
|
||
|
local command = "git clone " .. repository_url
|
||
|
|
||
|
if depth then
|
||
|
assert(type(depth) == "number", "git.clone: Argument 'depth' is not a number.")
|
||
|
command = command .. " --depth " .. depth
|
||
|
end
|
||
|
|
||
|
if branch then
|
||
|
assert(type(branch) == "string", "git.clone: Argument 'branch' is not a string.")
|
||
|
command = command .. " -b " .. branch
|
||
|
end
|
||
|
|
||
|
command = command .. " " .. sys.quote(dest_dir)
|
||
|
if sys.exists(dest_dir) then sys.delete(dest_dir) end
|
||
|
sys.make_dir(dest_dir)
|
||
|
|
||
|
-- change the current working directory to dest_dir
|
||
|
local prev_current_dir = sys.current_dir()
|
||
|
sys.change_dir(dest_dir)
|
||
|
|
||
|
-- execute git clone
|
||
|
if not cfg.debug then command = command .. " -q " end
|
||
|
local ok, err = sys.exec(command)
|
||
|
|
||
|
-- change the current working directory back
|
||
|
sys.change_dir(prev_current_dir)
|
||
|
|
||
|
return ok, err
|
||
|
end
|
||
|
|
||
|
-- Return table of all refs of the remote repository at the 'git_url'. Ref_type can be "tags" or "heads".
|
||
|
local function get_remote_refs(git_url, ref_type)
|
||
|
assert(type(git_url) == "string", "git.get_remote_refs: Argument 'git_url' is not a string.")
|
||
|
assert(type(ref_type) == "string", "git.get_remote_refs: Argument 'ref_type' is not a string.")
|
||
|
assert(ref_type == "tags" or ref_type == "heads", "git.get_remote_refs: Argument 'ref_type' is not \"tags\" or \"heads\".")
|
||
|
|
||
|
local refs = {}
|
||
|
|
||
|
local ok, refs_or_err = pcall(git.protocol.remotes, git_url)
|
||
|
if not ok then return nil, "Error getting refs of the remote repository '" .. git_url .. "': " .. refs_or_err end
|
||
|
|
||
|
for ref, sha in pairs(refs_or_err) do
|
||
|
if ref:match("%S+/" .. ref_type .. "/%S+") and not ref:match("%^{}") then
|
||
|
table.insert(refs, ref:match("%S+/" .. ref_type .. "/(%S+)"))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return refs
|
||
|
end
|
||
|
|
||
|
-- Return table of all tags of the repository at the 'git_url'
|
||
|
function get_remote_tags(git_url)
|
||
|
return get_remote_refs(git_url, "tags")
|
||
|
end
|
||
|
|
||
|
-- Return table of all branches of the repository at the 'git_url'
|
||
|
function get_remote_branches(git_url)
|
||
|
return get_remote_refs(git_url, "heads")
|
||
|
end
|
||
|
|
||
|
-- Checkout specified ref in specified git_repo_dir
|
||
|
function checkout_ref(ref, git_repo_dir, orphaned)
|
||
|
git_repo_dir = git_repo_dir or sys.current_dir()
|
||
|
orphaned = orphaned or false
|
||
|
assert(type(ref) == "string", "git.checkout_ref: Argument 'ref' is not a string.")
|
||
|
assert(type(git_repo_dir) == "string", "git.checkout_ref: Argument 'git_repo_dir' is not a string.")
|
||
|
assert(type(orphaned) == "boolean", "git.checkout_ref: Argument 'orphaned' is not a boolean.")
|
||
|
git_repo_dir = sys.abs_path(git_repo_dir)
|
||
|
|
||
|
local command = "git checkout "
|
||
|
if orphaned then command = command .. " --orphan " end
|
||
|
command = command .. " " .. ref .. " -f"
|
||
|
if not cfg.debug then command = command .. " -q " end
|
||
|
|
||
|
local ok, err
|
||
|
if git_repo_dir ~= sys.current_dir() then
|
||
|
local prev_current_dir = sys.current_dir()
|
||
|
sys.change_dir(git_repo_dir)
|
||
|
ok, err = sys.exec(command)
|
||
|
sys.change_dir(prev_current_dir)
|
||
|
else
|
||
|
ok, err = sys.exec(command)
|
||
|
end
|
||
|
|
||
|
return ok, err
|
||
|
end
|
||
|
|
||
|
-- Checkout specified sha in specified git_repo_dir
|
||
|
function checkout_sha(sha, git_repo_dir)
|
||
|
git_repo_dir = git_repo_dir or sys.current_dir()
|
||
|
assert(type(sha) == "string", "git.checkout_sha: Argument 'sha' is not a string.")
|
||
|
assert(type(git_repo_dir) == "string", "git.checkout_sha: Argument 'git_repo_dir' is not a string.")
|
||
|
git_repo_dir = sys.abs_path(git_repo_dir)
|
||
|
|
||
|
local dir_changed, prev_current_dir
|
||
|
|
||
|
if git_repo_dir ~= sys.current_dir() then
|
||
|
prev_current_dir = sys.current_dir()
|
||
|
sys.change_dir(git_repo_dir)
|
||
|
dir_changed = true
|
||
|
end
|
||
|
|
||
|
local ok, repo_or_err = pcall(git.repo.open, git_repo_dir)
|
||
|
if not ok then return nil, "Error when opening the git repository '" .. git_repo_dir .. "': " .. repo_or_err end
|
||
|
|
||
|
local err
|
||
|
ok, err = pcall(repo_or_err.checkout, repo_or_err, sha, git_repo_dir)
|
||
|
if not ok then return nil, "Error when checking out the sha '" .. sha .. "' in the git repository '" .. git_repo_dir .. "': " .. err end
|
||
|
|
||
|
repo_or_err:close()
|
||
|
if dir_changed then sys.change_dir(prev_current_dir) end
|
||
|
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-- Create an empty git repository in given directory.
|
||
|
function init(dir)
|
||
|
dir = dir or sys.current_dir()
|
||
|
assert(type(dir) == "string", "git.init: Argument 'dir' is not a string.")
|
||
|
dir = sys.abs_path(dir)
|
||
|
|
||
|
-- create the 'dir' first, since it causes 'git init' to fail on Windows
|
||
|
-- when the parent directory of 'dir' doesn't exist
|
||
|
local ok, err = sys.make_dir(dir)
|
||
|
if not ok then return nil, err end
|
||
|
|
||
|
local command = "git init " .. sys.quote(dir)
|
||
|
if not cfg.debug then command = command .. " -q " end
|
||
|
return sys.exec(command)
|
||
|
end
|
||
|
|
||
|
-- Add all files in the 'repo_dir' to the git index. The 'repo_dir' must be
|
||
|
-- in the initialized git repository.
|
||
|
function add_all(repo_dir)
|
||
|
repo_dir = repo_dir or sys.current_dir()
|
||
|
assert(type(repo_dir) == "string", "git.add_all: Argument 'repo_dir' is not a string.")
|
||
|
repo_dir = sys.abs_path(repo_dir)
|
||
|
|
||
|
local ok, prev_dir, msg
|
||
|
ok, prev_dir = sys.change_dir(repo_dir);
|
||
|
if not ok then return nil, err end
|
||
|
|
||
|
ok, msg = sys.exec("git add -A -f " .. sys.quote(repo_dir))
|
||
|
sys.change_dir(prev_dir)
|
||
|
|
||
|
return ok, msg
|
||
|
end
|
||
|
|
||
|
-- Commit all indexed files in 'repo_dir' with the given commit 'message'.
|
||
|
-- The 'repo_dir' must be in the initialized git repository.
|
||
|
function commit(message, repo_dir)
|
||
|
repo_dir = repo_dir or sys.current_dir()
|
||
|
message = message or "commit by luadist-git"
|
||
|
assert(type(message) == "string", "git.commit: Argument 'message' is not a string.")
|
||
|
assert(type(repo_dir) == "string", "git.commit: Argument 'repo_dir' is not a string.")
|
||
|
repo_dir = sys.abs_path(repo_dir)
|
||
|
|
||
|
local ok, prev_dir, msg
|
||
|
ok, prev_dir = sys.change_dir(repo_dir);
|
||
|
if not ok then return nil, err end
|
||
|
|
||
|
local command = "git commit -m " .. sys.quote(message)
|
||
|
if not cfg.debug then command = command .. " -q " end
|
||
|
ok, msg = sys.exec(command)
|
||
|
sys.change_dir(prev_dir)
|
||
|
|
||
|
return ok, msg
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Rename branch 'old_name' to 'new_name'. -- The 'repo_dir' must be
|
||
|
-- in the initialized git repository and the branch 'new_name' must
|
||
|
-- not already exist in that repository.
|
||
|
function rename_branch(old_name, new_name, repo_dir)
|
||
|
repo_dir = repo_dir or sys.current_dir()
|
||
|
assert(type(old_name) == "string", "git.rename_branch: Argument 'old_name' is not a string.")
|
||
|
assert(type(new_name) == "string", "git.rename_branch: Argument 'new_name' is not a string.")
|
||
|
assert(type(repo_dir) == "string", "git.rename_branch: Argument 'repo_dir' is not a string.")
|
||
|
repo_dir = sys.abs_path(repo_dir)
|
||
|
|
||
|
local ok, prev_dir, msg
|
||
|
ok, prev_dir = sys.change_dir(repo_dir);
|
||
|
if not ok then return nil, err end
|
||
|
|
||
|
ok, msg = sys.exec("git branch -m " .. old_name .. " " .. new_name)
|
||
|
sys.change_dir(prev_dir)
|
||
|
|
||
|
return ok, msg
|
||
|
end
|
||
|
|
||
|
-- Push the ref 'ref_name' from the 'repo_dir' to the remote git
|
||
|
-- repository 'git_repo_url'. If 'all_tags' is set to true, all tags
|
||
|
-- will be pushed, in addition to the explicitly given ref.
|
||
|
-- If 'delete' is set to 'true' then the explicitly given remote ref
|
||
|
-- will be deleted, not pushed.
|
||
|
function push_ref(repo_dir, ref_name, git_repo_url, all_tags, delete)
|
||
|
repo_dir = repo_dir or sys.current_dir()
|
||
|
all_tags = all_tags or false
|
||
|
delete = delete or false
|
||
|
assert(type(repo_dir) == "string", "git.push_ref: Argument 'repo_dir' is not a string.")
|
||
|
assert(type(git_repo_url) == "string", "git.push_ref: Argument 'git_repo_url' is not a string.")
|
||
|
assert(type(ref_name) == "string", "git.push_ref: Argument 'ref_name' is not a string.")
|
||
|
assert(type(all_tags) == "boolean", "git.push_ref: Argument 'all_tags' is not a boolean.")
|
||
|
assert(type(delete) == "boolean", "git.push_ref: Argument 'delete' is not a boolean.")
|
||
|
repo_dir = sys.abs_path(repo_dir)
|
||
|
|
||
|
local ok, prev_dir, msg
|
||
|
ok, prev_dir = sys.change_dir(repo_dir);
|
||
|
if not ok then return nil, err end
|
||
|
|
||
|
local command = "git push " .. git_repo_url
|
||
|
if all_tags then command = command .. " --tags " end
|
||
|
if delete then command = command .. " --delete " end
|
||
|
command = command .. " " .. ref_name .. " -f "
|
||
|
if not cfg.debug then command = command .. " -q " end
|
||
|
|
||
|
ok, msg = sys.exec(command)
|
||
|
sys.change_dir(prev_dir)
|
||
|
|
||
|
return ok, msg
|
||
|
end
|
||
|
|
||
|
-- Creates the tag 'tag_name' in given 'repo_dir', which must be
|
||
|
-- in the initialized git repository
|
||
|
function create_tag(repo_dir, tag_name)
|
||
|
repo_dir = repo_dir or sys.current_dir()
|
||
|
assert(type(repo_dir) == "string", "git.create_tag: Argument 'repo_dir' is not a string.")
|
||
|
assert(type(tag_name) == "string", "git.create_tag: Argument 'tag_name' is not a string.")
|
||
|
repo_dir = sys.abs_path(repo_dir)
|
||
|
|
||
|
local ok, prev_dir, msg
|
||
|
ok, prev_dir = sys.change_dir(repo_dir);
|
||
|
if not ok then return nil, err end
|
||
|
|
||
|
ok, msg = sys.exec("git tag " .. tag_name .. " -f ")
|
||
|
sys.change_dir(prev_dir)
|
||
|
|
||
|
return ok, msg
|
||
|
end
|
||
|
|
||
|
-- Fetch given 'ref_name' from the remote 'git_repo_url' to the local repository
|
||
|
-- 'repo_dir' and return its sha. 'ref_type' can be "tag" or "head".
|
||
|
local function fetch_ref(repo_dir, git_repo_url, ref_name, ref_type)
|
||
|
repo_dir = repo_dir or sys.current_dir()
|
||
|
assert(type(repo_dir) == "string", "git.fetch_ref: Argument 'repo_dir' is not a string.")
|
||
|
assert(type(git_repo_url) == "string", "git.fetch_ref: Argument 'git_repo_url' is not a string.")
|
||
|
assert(type(ref_name) == "string", "git.fetch_ref: Argument 'ref_name' is not a string.")
|
||
|
assert(type(ref_type) == "string", "git.fetch_ref: Argument 'ref_type' is not a string.")
|
||
|
assert(ref_type == "tag" or ref_type == "head", "git.get_remote_refs: Argument 'ref_type' is not \"tag\" or \"head\".")
|
||
|
repo_dir = sys.abs_path(repo_dir)
|
||
|
|
||
|
local refstring = "refs/" .. ref_type .. "s/" .. ref_name
|
||
|
|
||
|
local suppress_fetch_progress = not cfg.debug
|
||
|
local ok, repo_or_err = pcall(git.repo.open, repo_dir)
|
||
|
if not ok then return nil, "Error when opening the git repository '" .. repo_dir .. "': " .. repo_or_err end
|
||
|
|
||
|
local ok, pack_or_err, sha = pcall(git.protocol.fetch, git_repo_url, repo_or_err, refstring, suppress_fetch_progress)
|
||
|
if not ok then return nil, "Error when fetching ref '" .. refstring .. "' from git repository '" .. git_repo_url .. "': " .. pack_or_err end
|
||
|
|
||
|
repo_or_err:close()
|
||
|
pack_or_err:close()
|
||
|
|
||
|
return sha
|
||
|
end
|
||
|
|
||
|
-- Fetch given 'tag_name' from the remote 'git_repo_url' to the local repository
|
||
|
-- 'repo_dir' and save it as a tag with the same 'tag_name'.
|
||
|
function fetch_tag(repo_dir, git_repo_url, tag_name)
|
||
|
return fetch_ref(repo_dir, git_repo_url, tag_name, "tag")
|
||
|
end
|
||
|
|
||
|
-- Fetch given 'branch_name' from the remote 'git_repo_url' to the local repository
|
||
|
-- 'repo_dir' and save it as a branch with the same 'branch_name'.
|
||
|
function fetch_branch(repo_dir, git_repo_url, branch_name)
|
||
|
return fetch_ref(repo_dir, git_repo_url, branch_name, "head")
|
||
|
end
|
||
|
|
||
|
-- Create the git repository and return the repo object (which can be used in checkout_sha etc.)
|
||
|
-- If the 'dir' exists, it's deleted prior to creating the git repository.
|
||
|
function create_repo(dir)
|
||
|
assert(type(dir) == "string", "git.create_repo: Argument 'dir' is not a string.")
|
||
|
|
||
|
if sys.exists(dir) then sys.delete(dir) end
|
||
|
|
||
|
local ok, repo_or_err = pcall(git.repo.create, dir)
|
||
|
if not ok then return nil, "Error when creating the git repository '" .. dir .. "': " .. repo_or_err end
|
||
|
|
||
|
repo_or_err:close()
|
||
|
return true
|
||
|
end
|