local M = {} local fn = vim.fn local api = vim.api local function job_async(argv, opts) opts = opts or {} local on_stdout = opts.on_stdout local on_stderr = opts.on_stderr local on_exit = opts.on_exit local id = fn.jobstart(argv, { stdout_buffered = true, stderr_buffered = true, on_stdout = vim.schedule_wrap(function(_, data, _) if on_stdout then on_stdout(data) end end), on_stderr = vim.schedule_wrap(function(_, data, _) if on_stderr then on_stderr(data) end end), on_exit = vim.schedule_wrap(function(_, code, _) if on_exit then on_exit(code) end end), }) return id end -- get current branch name of repo_path; cb(err, branch) function M.get_branch(repo_path, cb) cb = cb or function() end if not repo_path or repo_path == '' then return cb('no_repo') end job_async({'git','-C',repo_path,'rev-parse','--abbrev-ref','HEAD'}, { on_stdout = function(data) local out = table.concat(data, '\n'):gsub('\n+$','') cb(nil, out) end, on_stderr = function(data) -- ignore stderr unless needed end, on_exit = function(code) if code ~= 0 then cb('git_error') end end, }) end -- status porcelain; cb(err, lines_table) function M.status_porcelain(repo_path, cb) cb = cb or function() end job_async({'git','-C',repo_path,'status','--porcelain'}, { on_stdout = function(data) -- data is table of lines cb(nil, data) end, on_exit = function(code) if code ~= 0 then cb('git_error') end end, }) end -- commit_if_dirty: if dirty and auto_commit true, run add+commit -- opts = { auto_commit = true, message = 'msg' } -- cb(err, result) function M.commit_if_dirty(repo_path, opts, cb) cb = cb or function() end opts = opts or {} local auto_commit = opts.auto_commit local message = opts.message or ("Auto REPL state: " .. os.date("%Y-%m-%d %H:%M:%S")) M.status_porcelain(repo_path, function(err, lines) if err then return cb('status_error') end -- lines may contain empty string elements local dirty = false for _, l in ipairs(lines) do if l and l:match('%S') then dirty = true; break end end if not dirty then return cb(nil, {committed = false, reason = 'no_changes'}) end if not auto_commit then return cb(nil, {committed = false, reason = 'auto_commit_disabled'}) end -- run git add -A && git commit -m message -- We'll run as a shell command via sh -c to combine commands easily local cmd = {'sh','-c', "git -C " .. fn.shellescape(repo_path) .. " add -A && git -C " .. fn.shellescape(repo_path) .. " commit -m " .. fn.shellescape(message)} local stdout_acc = {} local stderr_acc = {} job_async(cmd, { on_stdout = function(data) for _, ln in ipairs(data) do table.insert(stdout_acc, ln) end end, on_stderr = function(data) for _, ln in ipairs(data) do table.insert(stderr_acc, ln) end end, on_exit = function(code) if code == 0 then cb(nil, {committed = true, success = true, stdout = stdout_acc, stderr = stderr_acc}) else cb('commit_failed', {committed = true, success = false, stdout = stdout_acc, stderr = stderr_acc, code = code}) end end, }) end) end function M.branch_exists(repo_path, branch, cb) cb = cb or function() end job_async({'git','-C',repo_path,'rev-parse','--verify','--quiet','refs/heads/' .. branch}, { on_exit = function(code) if code == 0 then cb(nil, true) else cb(nil, false) end end, }) end function M.checkout_branch(repo_path, branch, cb) cb = cb or function() end job_async({'git','-C',repo_path,'checkout',branch}, { on_stdout = function(_) end, on_stderr = function(_) end, on_exit = function(code) if code == 0 then cb(nil, true) else cb('checkout_failed', false) end end, }) end -- create branch from 'from' (string), prefer using checkout then checkout -b function M.create_branch(repo_path, branch, from, cb) cb = cb or function() end from = from or 'HEAD' -- do two commands in shell: git -C repo checkout && git -C repo checkout -b local cmd = {'sh','-c', "git -C " .. fn.shellescape(repo_path) .. " checkout " .. fn.shellescape(from) .. " && git -C " .. fn.shellescape(repo_path) .. " checkout -b " .. fn.shellescape(branch)} job_async(cmd, { on_exit = function(code) if code == 0 then cb(nil, true) else cb('create_failed', false) end end, }) end return M