f514602697
Part A — a fetch/pull/push against a remote that needs a login no longer dies on "terminal prompts disabled": the server answers 409 EGITAUTH with the host, the git bar asks for a username + password/access token and resends the same action. AgentHub keeps its OWN credential store (<CLAUDE_HOME>/agenthub-git-credentials/, 0700/0600), one login per host, shared by every user of the install, managed from Settings -> Git credentials. A login is only sent to one of the repo's own remote hosts, saved only after git accepted it, never returned by any route, and reaches git through an exclusive credential helper (never argv). Part B — merge a branch (or the diverged upstream) from the git bar; a merge that stops on conflicts lists them in the Changes panel with Keep mine / Take theirs / Mark resolved, then Complete merge or Abort merge (confirm gate). Clean tree, repo-root lane and an existing ref only; pull/switch/hunk actions are blocked mid-merge; staging a conflicted file with markers left is refused. Rebase/reset/stash stay out.
49 lines
1.9 KiB
JavaScript
49 lines
1.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* @file The git credential helper AgentHub installs on its own network git
|
|
* commands (`-c credential.helper=!node <this>`, see git-credentials.js).
|
|
* Speaks git's credential protocol: `get` reads `key=value` lines on stdin
|
|
* and answers `username=` / `password=` for a matching protocol + host —
|
|
* first from `AGENTHUB_GIT_ONCE` (a credential typed for this one command),
|
|
* then from the store file named by `AGENTHUB_GIT_CRED_FILE`. `store` and
|
|
* `erase` do nothing: AgentHub saves only what the user asked it to keep, and
|
|
* only after git has proved it works. Prints nothing else, ever.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const fs = require("node:fs");
|
|
|
|
if (process.argv[2] !== "get") process.exit(0);
|
|
|
|
let input = "";
|
|
process.stdin.on("data", (c) => (input += c));
|
|
process.stdin.on("end", () => {
|
|
const want = {};
|
|
for (const line of input.split("\n")) {
|
|
const i = line.indexOf("=");
|
|
if (i > 0) want[line.slice(0, i)] = line.slice(i + 1);
|
|
}
|
|
const protocol = String(want.protocol || "").toLowerCase();
|
|
const host = String(want.host || "").toLowerCase();
|
|
const matches = (r) => r && r.protocol === protocol && r.host === host && r.password;
|
|
|
|
let hit = null;
|
|
try {
|
|
const once = JSON.parse(process.env.AGENTHUB_GIT_ONCE || "null");
|
|
if (matches(once)) hit = once;
|
|
} catch {
|
|
/* malformed: fall through to the store */
|
|
}
|
|
if (!hit && process.env.AGENTHUB_GIT_CRED_FILE) {
|
|
try {
|
|
const rows = JSON.parse(fs.readFileSync(process.env.AGENTHUB_GIT_CRED_FILE, "utf8"));
|
|
hit = (Array.isArray(rows) ? rows : []).find(matches) || null;
|
|
} catch {
|
|
/* no store yet */
|
|
}
|
|
}
|
|
// A helper that answers nothing lets git fail with its own auth error,
|
|
// which the dashboard turns into the login form.
|
|
if (hit) process.stdout.write(`username=${hit.username}\npassword=${hit.password}\n`);
|
|
});
|