fix(fetch): exit 0 on partial remote failure

Amolith created

Previously exited non-zero if ANY remote failed, even when others
succeeded. Now fetches each remote individually, prints warnings for
failures, and exits 0 if at least one remote succeeded.

Assisted-by: Claude Opus 4.5 via Amp

Change summary

src/wt/cmd/fetch.lua | 52 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 48 insertions(+), 4 deletions(-)

Detailed changes

src/wt/cmd/fetch.lua 🔗

@@ -9,7 +9,25 @@ local git = require("wt.git")
 ---@class wt.cmd.fetch
 local M = {}
 
----Fetch all remotes with pruning
+---Get list of configured remotes
+---@param git_dir string
+---@return string[] remotes
+local function get_remotes(git_dir)
+	local output, code = shell.run_cmd("GIT_DIR=" .. git_dir .. " git remote")
+	if code ~= 0 then
+		return {}
+	end
+	local remotes = {}
+	for line in output:gmatch("[^\n]+") do
+		local trimmed = line:match("^%s*(.-)%s*$")
+		if trimmed and trimmed ~= "" then
+			table.insert(remotes, trimmed)
+		end
+	end
+	return remotes
+end
+
+---Fetch all remotes with pruning, tolerating partial failures
 function M.cmd_fetch()
 	local root, err = git.find_project_root()
 	if not root then
@@ -18,9 +36,35 @@ function M.cmd_fetch()
 	end
 
 	local git_dir = root .. "/.bare"
-	local output, code = shell.run_cmd("GIT_DIR=" .. git_dir .. " git fetch --all --prune")
-	io.write(output)
-	if code ~= 0 then
+	local remotes = get_remotes(git_dir)
+
+	if #remotes == 0 then
+		shell.die("no remotes configured", exit.EXIT_USER_ERROR)
+		return
+	end
+
+	local succeeded = 0
+	local failures = {}
+
+	for _, remote in ipairs(remotes) do
+		local output, code = shell.run_cmd("GIT_DIR=" .. git_dir .. " git fetch --prune " .. shell.quote(remote))
+		if code == 0 then
+			succeeded = succeeded + 1
+			io.write(output)
+		else
+			table.insert(failures, { remote = remote, output = output })
+		end
+	end
+
+	for _, f in ipairs(failures) do
+		io.stderr:write("warning: failed to fetch " .. f.remote .. "\n")
+		if f.output and f.output ~= "" then
+			io.stderr:write(f.output)
+		end
+	end
+
+	if succeeded == 0 then
+		io.stderr:write("error: all remotes failed to fetch\n")
 		os.exit(exit.EXIT_SYSTEM_ERROR)
 	end
 end