@skaile/workspaces 4.24.0 → 4.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/{asset-feeds-65J4N2TB.js → asset-feeds-72RJOE4E.js} +4 -4
- package/dist/{asset-feeds-65J4N2TB.js.map → asset-feeds-72RJOE4E.js.map} +1 -1
- package/dist/asset-manager/index.js +1 -1
- package/dist/{chunk-SMXHEEJP.js → chunk-253427M3.js} +3 -3
- package/dist/{chunk-SMXHEEJP.js.map → chunk-253427M3.js.map} +1 -1
- package/dist/{chunk-4FFUB4ZB.js → chunk-2JGUFDKZ.js} +3 -3
- package/dist/{chunk-4FFUB4ZB.js.map → chunk-2JGUFDKZ.js.map} +1 -1
- package/dist/{chunk-VFGEIFRT.js → chunk-2N6NQUVB.js} +5 -5
- package/dist/{chunk-VFGEIFRT.js.map → chunk-2N6NQUVB.js.map} +1 -1
- package/dist/{chunk-3SZLZ56M.js → chunk-EZOFQW3G.js} +761 -234
- package/dist/chunk-EZOFQW3G.js.map +1 -0
- package/dist/{chunk-OTRHFPUF.js → chunk-H4XNX2FG.js} +2 -2
- package/dist/{chunk-OTRHFPUF.js.map → chunk-H4XNX2FG.js.map} +1 -1
- package/dist/{chunk-SWXUBDTV.js → chunk-IHFQTKUP.js} +2 -2
- package/dist/{chunk-SWXUBDTV.js.map → chunk-IHFQTKUP.js.map} +1 -1
- package/dist/{chunk-VTTBDFXM.js → chunk-OQRSSFTU.js} +3 -3
- package/dist/{chunk-VTTBDFXM.js.map → chunk-OQRSSFTU.js.map} +1 -1
- package/dist/{chunk-MTNIVJWB.js → chunk-SB4U7HLP.js} +6 -6
- package/dist/{chunk-MTNIVJWB.js.map → chunk-SB4U7HLP.js.map} +1 -1
- package/dist/{chunk-QD4ZCIPQ.js → chunk-SSODNBSC.js} +3 -3
- package/dist/{chunk-QD4ZCIPQ.js.map → chunk-SSODNBSC.js.map} +1 -1
- package/dist/{chunk-JCT4UMGQ.js → chunk-XZCRIPWT.js} +3 -3
- package/dist/{chunk-JCT4UMGQ.js.map → chunk-XZCRIPWT.js.map} +1 -1
- package/dist/{chunk-4U3NSLGV.js → chunk-YR3CLEQG.js} +6 -6
- package/dist/{chunk-4U3NSLGV.js.map → chunk-YR3CLEQG.js.map} +1 -1
- package/dist/cli/index.js +28 -28
- package/dist/connectors/index.js +1 -1
- package/dist/connectors/src/connector-prompt.d.ts.map +1 -1
- package/dist/{ensure-sources-TKY7PTWF.js → ensure-sources-HEOQJ4CS.js} +4 -4
- package/dist/{ensure-sources-TKY7PTWF.js.map → ensure-sources-HEOQJ4CS.js.map} +1 -1
- package/dist/factory-assets/connectors/box.js +1 -1
- package/dist/factory-assets/connectors/deploy.js +1 -1
- package/dist/factory-assets/connectors/devserver.js +1 -1
- package/dist/factory-assets/connectors/flow/adapter.js +1 -1
- package/dist/factory-assets/connectors/flow/run-flow.js +2 -2
- package/dist/factory-assets/connectors/flow.js +1 -1
- package/dist/factory-assets/connectors/git/CONNECTOR.md +63 -6
- package/dist/factory-assets/connectors/git/driver.d.ts +111 -27
- package/dist/factory-assets/connectors/git/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/git.js +1 -1
- package/dist/factory-assets/connectors/gmail.js +1 -1
- package/dist/factory-assets/connectors/googledrive.js +1 -1
- package/dist/factory-assets/connectors/local.js +1 -1
- package/dist/factory-assets/connectors/mattermost.js +1 -1
- package/dist/factory-assets/connectors/memory.js +1 -1
- package/dist/factory-assets/connectors/minio.js +1 -1
- package/dist/factory-assets/connectors/postgres.js +1 -1
- package/dist/factory-assets/connectors/s3.js +1 -1
- package/dist/factory-assets/connectors/sharepoint.js +1 -1
- package/dist/factory-assets/connectors/sqlite.js +1 -1
- package/dist/factory-assets/connectors/static-server.js +1 -1
- package/dist/factory-assets/connectors/tunnel.js +1 -1
- package/dist/factory-assets/connectors/webdav.js +1 -1
- package/dist/factory-assets/connectors/xstate-store.js +1 -1
- package/dist/factory-assets/connectors/xstate.js +1 -1
- package/dist/{helpers-5GMEJWMX.js → helpers-VZJCQSLY.js} +4 -4
- package/dist/{helpers-5GMEJWMX.js.map → helpers-VZJCQSLY.js.map} +1 -1
- package/dist/{library-publish-FLQICZF4.js → library-publish-VBLSTO7S.js} +6 -6
- package/dist/{library-publish-FLQICZF4.js.map → library-publish-VBLSTO7S.js.map} +1 -1
- package/dist/{open-library-OOUZG24B.js → open-library-SJ54ZBGJ.js} +5 -5
- package/dist/{open-library-OOUZG24B.js.map → open-library-SJ54ZBGJ.js.map} +1 -1
- package/dist/{project-ZUYKJX3V.js → project-GLZR4SLT.js} +5 -5
- package/dist/{project-ZUYKJX3V.js.map → project-GLZR4SLT.js.map} +1 -1
- package/dist/runner/index.js +4 -4
- package/dist/sdk/asset-manager.js +1 -1
- package/dist/sdk/index.js +4 -4
- package/dist/sdk/runner.js +4 -4
- package/dist/{setup-RERVKTPI.js → setup-X73AWRMD.js} +5 -5
- package/dist/{setup-RERVKTPI.js.map → setup-X73AWRMD.js.map} +1 -1
- package/dist/{source-SUHVAXK6.js → source-HYXJO2IE.js} +6 -6
- package/dist/{source-SUHVAXK6.js.map → source-HYXJO2IE.js.map} +1 -1
- package/dist/{source-manifest-publish-VMW6ZHW6.js → source-manifest-publish-V5LCVOYZ.js} +7 -7
- package/dist/{source-manifest-publish-VMW6ZHW6.js.map → source-manifest-publish-V5LCVOYZ.js.map} +1 -1
- package/dist/tui/index.js +4 -4
- package/dist/workspace-plugin/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3SZLZ56M.js.map +0 -1
|
@@ -1568,6 +1568,69 @@ async function commitIdentityArgs(cwd) {
|
|
|
1568
1568
|
function gitCommitting(args, cwd, timeout = 3e4) {
|
|
1569
1569
|
return commitIdentityArgs(cwd).then((identity) => git([...identity, ...args], cwd, timeout));
|
|
1570
1570
|
}
|
|
1571
|
+
async function pullByMerge(branch, cwd, auth, timeout = 3e4) {
|
|
1572
|
+
const pending = await unfinishedGitOperation(cwd);
|
|
1573
|
+
if (pending !== null) {
|
|
1574
|
+
throw new Error(
|
|
1575
|
+
`the checkout has an unfinished merge or rebase (${pending}); nothing was pulled. Finish or abort it first`
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
await runRemoteGit(["fetch", "origin", branch], auth, {
|
|
1579
|
+
cwd,
|
|
1580
|
+
safeDir: cwd,
|
|
1581
|
+
timeout,
|
|
1582
|
+
env: cLocaleEnv()
|
|
1583
|
+
});
|
|
1584
|
+
try {
|
|
1585
|
+
await gitCommitting(["merge", "--ff", "--no-edit", "FETCH_HEAD"], cwd, timeout);
|
|
1586
|
+
} catch (err) {
|
|
1587
|
+
const unmerged = await gitOrNull(["diff", "--name-only", "--diff-filter=U"], cwd) ?? "";
|
|
1588
|
+
if (await gitOrNull(["rev-parse", "--quiet", "--verify", "MERGE_HEAD"], cwd) !== null) {
|
|
1589
|
+
await gitOrNull(["merge", "--abort"], cwd);
|
|
1590
|
+
}
|
|
1591
|
+
const files = unmerged.split("\n").filter(Boolean);
|
|
1592
|
+
if (files.length > 0) {
|
|
1593
|
+
throw new Error(
|
|
1594
|
+
`origin/${branch} and the local branch both have new commits that conflict in ${files.join(", ")}; the merge was aborted and the checkout is unchanged`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
throw err;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
var UNFINISHED_OPERATION_PATHS = [
|
|
1601
|
+
"MERGE_HEAD",
|
|
1602
|
+
"CHERRY_PICK_HEAD",
|
|
1603
|
+
"REVERT_HEAD",
|
|
1604
|
+
"rebase-merge",
|
|
1605
|
+
"rebase-apply"
|
|
1606
|
+
];
|
|
1607
|
+
async function unfinishedGitOperation(cwd) {
|
|
1608
|
+
const paths = await gitOrNull(
|
|
1609
|
+
["rev-parse", ...UNFINISHED_OPERATION_PATHS.flatMap((p) => ["--git-path", p])],
|
|
1610
|
+
cwd
|
|
1611
|
+
);
|
|
1612
|
+
if (paths === null) return "the git dir could not be resolved";
|
|
1613
|
+
const found = await Promise.all(
|
|
1614
|
+
paths.split("\n").map(async (p) => {
|
|
1615
|
+
try {
|
|
1616
|
+
return await existsOrThrow(resolve(cwd, p)) ? "is present" : null;
|
|
1617
|
+
} catch (err) {
|
|
1618
|
+
return `could not be checked (${err.code ?? "deadline or error"})`;
|
|
1619
|
+
}
|
|
1620
|
+
})
|
|
1621
|
+
);
|
|
1622
|
+
const index = found.findIndex((state) => state !== null);
|
|
1623
|
+
if (index >= 0) return `${UNFINISHED_OPERATION_PATHS[index]} ${found[index]}`;
|
|
1624
|
+
const unmerged = await gitOrNull(["diff", "--name-only", "--diff-filter=U"], cwd);
|
|
1625
|
+
if (unmerged) return `unmerged paths: ${unmerged.split("\n").join(", ")}`;
|
|
1626
|
+
return null;
|
|
1627
|
+
}
|
|
1628
|
+
async function refuseUnfinishedOperation(cwd, log) {
|
|
1629
|
+
const reason = await unfinishedGitOperation(cwd);
|
|
1630
|
+
if (reason === null) return false;
|
|
1631
|
+
log.warn("not committing: the checkout has an unfinished merge or rebase", { reason });
|
|
1632
|
+
return true;
|
|
1633
|
+
}
|
|
1571
1634
|
async function ensureSkaileExcluded(cwd, log) {
|
|
1572
1635
|
try {
|
|
1573
1636
|
const commonDir = await git(["rev-parse", "--git-common-dir"], cwd, 5e3);
|
|
@@ -1638,6 +1701,208 @@ async function runGit(args, options) {
|
|
|
1638
1701
|
function git(args, cwd, timeout = 3e4) {
|
|
1639
1702
|
return runGit([...gitPrefix(cwd), ...args], { cwd, timeout });
|
|
1640
1703
|
}
|
|
1704
|
+
var TOKEN_USERNAME = "x-access-token";
|
|
1705
|
+
var NETWORK_GIT_ARGS = [
|
|
1706
|
+
"-c",
|
|
1707
|
+
"credential.helper=",
|
|
1708
|
+
"-c",
|
|
1709
|
+
"core.hooksPath=/dev/null",
|
|
1710
|
+
"-c",
|
|
1711
|
+
"core.askPass=",
|
|
1712
|
+
"-c",
|
|
1713
|
+
"core.fsmonitor=false",
|
|
1714
|
+
"-c",
|
|
1715
|
+
"core.alternateRefsCommand=",
|
|
1716
|
+
"-c",
|
|
1717
|
+
"push.gpgSign=false",
|
|
1718
|
+
"-c",
|
|
1719
|
+
"gc.auto=0",
|
|
1720
|
+
"-c",
|
|
1721
|
+
"maintenance.auto=false"
|
|
1722
|
+
];
|
|
1723
|
+
function plainRemoteUrl(auth) {
|
|
1724
|
+
if (!auth.token || !auth.url.startsWith("https://")) return auth.url;
|
|
1725
|
+
const u = new URL(auth.url);
|
|
1726
|
+
u.username = "";
|
|
1727
|
+
u.password = "";
|
|
1728
|
+
return u.toString();
|
|
1729
|
+
}
|
|
1730
|
+
function networkGitEnv(auth, base = process.env) {
|
|
1731
|
+
const env = { ...base, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
|
|
1732
|
+
if (!auth?.token || !auth.url.startsWith("https://")) return env;
|
|
1733
|
+
const { protocol, host } = new URL(auth.url);
|
|
1734
|
+
const basic = Buffer.from(`${TOKEN_USERNAME}:${auth.token}`).toString("base64");
|
|
1735
|
+
const existing = Number.parseInt(base.GIT_CONFIG_COUNT ?? "", 10);
|
|
1736
|
+
const index = Number.isInteger(existing) && existing > 0 ? existing : 0;
|
|
1737
|
+
const scope = auth.repoScoped ? comparableRemoteUrl(auth.url) : `${protocol}//${host}/`;
|
|
1738
|
+
env[`GIT_CONFIG_KEY_${index}`] = `http.${scope}.extraHeader`;
|
|
1739
|
+
env[`GIT_CONFIG_VALUE_${index}`] = `Authorization: Basic ${basic}`;
|
|
1740
|
+
env.GIT_CONFIG_COUNT = String(index + 1);
|
|
1741
|
+
env.GIT_ALLOW_PROTOCOL = "https";
|
|
1742
|
+
return env;
|
|
1743
|
+
}
|
|
1744
|
+
async function runRemoteGit(args, auth, options) {
|
|
1745
|
+
const keys = subcommandOf(args) === "submodule" ? CHECKOUT_UNSAFE_KEY_RE : TLS_TRUST_KEY_RE;
|
|
1746
|
+
const withheld = auth?.token ? await tlsTrustOverride(options, keys) : null;
|
|
1747
|
+
if (withheld !== null) {
|
|
1748
|
+
authLog.warn(
|
|
1749
|
+
"not sending the git credential: the checkout's config changes TLS trust or names a filter",
|
|
1750
|
+
{
|
|
1751
|
+
key: withheld
|
|
1752
|
+
}
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
const recursion = RECURSING_SUBCOMMANDS.has(subcommandOf(args)) ? NO_SUBMODULE_RECURSION : [];
|
|
1756
|
+
return runGit([...gitPrefix(options.safeDir), ...NETWORK_GIT_ARGS, ...recursion, ...args], {
|
|
1757
|
+
...options.cwd === void 0 ? {} : { cwd: options.cwd },
|
|
1758
|
+
timeout: options.timeout,
|
|
1759
|
+
env: networkGitEnv(withheld === null ? auth : void 0, options.env)
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
var RECURSING_SUBCOMMANDS = /* @__PURE__ */ new Set(["fetch", "pull", "push", "submodule", "clone"]);
|
|
1763
|
+
var NO_SUBMODULE_RECURSION = [
|
|
1764
|
+
"-c",
|
|
1765
|
+
"submodule.recurse=false",
|
|
1766
|
+
"-c",
|
|
1767
|
+
"fetch.recurseSubmodules=false",
|
|
1768
|
+
"-c",
|
|
1769
|
+
"push.recurseSubmodules=no"
|
|
1770
|
+
];
|
|
1771
|
+
function subcommandOf(args) {
|
|
1772
|
+
let i = 0;
|
|
1773
|
+
while (args[i] === "-c") i += 2;
|
|
1774
|
+
return args[i] ?? "";
|
|
1775
|
+
}
|
|
1776
|
+
var authLog = createLogger({ kind: "connector", subkind: "git" });
|
|
1777
|
+
var TLS_TRUST_KEY_RE = "^http\\.(.*\\.)?(sslverify|sslcainfo|sslcapath)$";
|
|
1778
|
+
var CHECKOUT_UNSAFE_KEY_RE = `(${TLS_TRUST_KEY_RE})|(^filter\\..*\\.(smudge|clean|process)$)|(^lfs\\.(customtransfer\\..*\\.path|standalonetransferagent)$)`;
|
|
1779
|
+
async function tlsTrustOverride(options, keyRe = TLS_TRUST_KEY_RE) {
|
|
1780
|
+
try {
|
|
1781
|
+
const own = await configMatch(
|
|
1782
|
+
gitPrefix(options.safeDir),
|
|
1783
|
+
options.cwd,
|
|
1784
|
+
[...options.cwd === void 0 ? ["--global"] : [], "--show-scope"],
|
|
1785
|
+
keyRe
|
|
1786
|
+
);
|
|
1787
|
+
const line = own.find(isUnsafeConfigLine);
|
|
1788
|
+
if (line !== void 0) return line.split(/\s+/)[1] ?? "unknown";
|
|
1789
|
+
if (options.cwd === void 0) return null;
|
|
1790
|
+
for (const dir of await submoduleGitDirs(options.cwd)) {
|
|
1791
|
+
const hits = await configMatch(
|
|
1792
|
+
["-c", "safe.directory=*", `--git-dir=${dir}`],
|
|
1793
|
+
options.cwd,
|
|
1794
|
+
["--show-scope"],
|
|
1795
|
+
keyRe
|
|
1796
|
+
);
|
|
1797
|
+
const hit = hits.find(isUnsafeConfigLine);
|
|
1798
|
+
if (hit !== void 0) return `${hit.split(/\s+/)[1] ?? "unknown"} (${dir})`;
|
|
1799
|
+
}
|
|
1800
|
+
return null;
|
|
1801
|
+
} catch {
|
|
1802
|
+
return "unreadable config";
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
function isUnsafeConfigLine(line) {
|
|
1806
|
+
return !/^(system|command)\s/.test(line);
|
|
1807
|
+
}
|
|
1808
|
+
async function configMatch(globalArgs, cwd, selector, keyRe) {
|
|
1809
|
+
try {
|
|
1810
|
+
const out = await runGit(
|
|
1811
|
+
[...globalArgs, "config", ...selector, "--includes", "--get-regexp", keyRe],
|
|
1812
|
+
{ ...cwd === void 0 ? {} : { cwd }, timeout: 5e3 }
|
|
1813
|
+
);
|
|
1814
|
+
return out.split("\n").filter((l) => l !== "");
|
|
1815
|
+
} catch (err) {
|
|
1816
|
+
if (err.code === 1) return [];
|
|
1817
|
+
throw err;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
var MAX_SUBMODULE_GIT_DIRS = 256;
|
|
1821
|
+
var MAX_SUBMODULE_SCAN_ENTRIES = 4096;
|
|
1822
|
+
var MAX_SUBMODULE_DEPTH = 8;
|
|
1823
|
+
async function submoduleGitDirs(cwd) {
|
|
1824
|
+
const found = /* @__PURE__ */ new Set();
|
|
1825
|
+
let scanned = 0;
|
|
1826
|
+
const add = (dir) => {
|
|
1827
|
+
found.add(dir);
|
|
1828
|
+
if (found.size > MAX_SUBMODULE_GIT_DIRS) throw new Error("too many submodule git dirs");
|
|
1829
|
+
};
|
|
1830
|
+
const walkModules = async (dir, depth) => {
|
|
1831
|
+
if (depth > MAX_SUBMODULE_DEPTH) throw new Error("submodule git dirs nested too deep");
|
|
1832
|
+
for (const name of await listDirOrEmpty(dir)) {
|
|
1833
|
+
scanned += 1;
|
|
1834
|
+
if (scanned > MAX_SUBMODULE_SCAN_ENTRIES) throw new Error("submodule scan too large");
|
|
1835
|
+
const child = join(dir, name);
|
|
1836
|
+
const st = await withFsDeadline(`stat ${child}`, () => stat(child));
|
|
1837
|
+
if (!st.isDirectory()) continue;
|
|
1838
|
+
if (await existsOrThrow(join(child, "HEAD"))) {
|
|
1839
|
+
add(child);
|
|
1840
|
+
await walkModules(join(child, "modules"), depth + 1);
|
|
1841
|
+
} else {
|
|
1842
|
+
await walkModules(child, depth + 1);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
const walkWorktree = async (worktree, depth) => {
|
|
1847
|
+
if (depth > MAX_SUBMODULE_DEPTH) throw new Error("submodules nested too deep");
|
|
1848
|
+
const gitmodules = join(worktree, ".gitmodules");
|
|
1849
|
+
if (!await existsOrThrow(gitmodules)) return;
|
|
1850
|
+
let listed = "";
|
|
1851
|
+
try {
|
|
1852
|
+
listed = await runGit(
|
|
1853
|
+
["config", "--file", gitmodules, "--get-regexp", "^submodule\\..*\\.path$"],
|
|
1854
|
+
{ cwd: worktree, timeout: 5e3 }
|
|
1855
|
+
);
|
|
1856
|
+
} catch (err) {
|
|
1857
|
+
if (err.code !== 1) throw err;
|
|
1858
|
+
}
|
|
1859
|
+
for (const line of listed.split("\n").filter((l) => l !== "")) {
|
|
1860
|
+
const path = line.slice(line.indexOf(" ") + 1);
|
|
1861
|
+
const subWorktree = resolve(worktree, path);
|
|
1862
|
+
const dotGit = join(subWorktree, ".git");
|
|
1863
|
+
let st;
|
|
1864
|
+
try {
|
|
1865
|
+
st = await withFsDeadline(`stat ${dotGit}`, () => stat(dotGit));
|
|
1866
|
+
} catch (err) {
|
|
1867
|
+
if (err.code === "ENOENT") continue;
|
|
1868
|
+
throw err;
|
|
1869
|
+
}
|
|
1870
|
+
if (st.isDirectory()) {
|
|
1871
|
+
add(dotGit);
|
|
1872
|
+
} else {
|
|
1873
|
+
const content = await withFsDeadline(`read ${dotGit}`, () => readFile(dotGit, "utf-8"));
|
|
1874
|
+
const m = /^gitdir:\s*(.+?)\s*$/m.exec(content);
|
|
1875
|
+
if (!m?.[1]) throw new Error(`unrecognised gitfile ${dotGit}`);
|
|
1876
|
+
add(resolve(dirname(dotGit), m[1]));
|
|
1877
|
+
}
|
|
1878
|
+
await walkWorktree(subWorktree, depth + 1);
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
const commonDir = resolve(cwd, await git(["rev-parse", "--git-common-dir"], cwd, 5e3));
|
|
1882
|
+
await walkModules(join(commonDir, "modules"), 0);
|
|
1883
|
+
await walkWorktree(cwd, 0);
|
|
1884
|
+
return [...found];
|
|
1885
|
+
}
|
|
1886
|
+
async function listDirOrEmpty(dir) {
|
|
1887
|
+
try {
|
|
1888
|
+
return await withFsDeadline(`readdir ${dir}`, () => readdir(dir));
|
|
1889
|
+
} catch (err) {
|
|
1890
|
+
if (err.code === "ENOENT") return [];
|
|
1891
|
+
throw err;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
async function existsOrThrow(path) {
|
|
1895
|
+
try {
|
|
1896
|
+
await withFsDeadline(`stat ${path}`, () => stat(path));
|
|
1897
|
+
return true;
|
|
1898
|
+
} catch (err) {
|
|
1899
|
+
if (err.code === "ENOENT") return false;
|
|
1900
|
+
throw err;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
function gitRemote(args, cwd, auth, timeout = 3e4) {
|
|
1904
|
+
return runRemoteGit(args, auth, { cwd, safeDir: cwd, timeout });
|
|
1905
|
+
}
|
|
1641
1906
|
function pathExists(path) {
|
|
1642
1907
|
return tryWithFsDeadline(`stat ${path}`, () => stat(path).then(() => true), false);
|
|
1643
1908
|
}
|
|
@@ -1682,16 +1947,27 @@ function gitLocalCheckout(args, cwd) {
|
|
|
1682
1947
|
throw typeof err?.code === "number" ? withConnectErrorKind(err, "repo-state") : err;
|
|
1683
1948
|
});
|
|
1684
1949
|
}
|
|
1685
|
-
async function syncSubmodules(cwd, log) {
|
|
1950
|
+
async function syncSubmodules(cwd, log, auth) {
|
|
1686
1951
|
if (!await pathExists(join(cwd, ".gitmodules"))) return;
|
|
1687
1952
|
try {
|
|
1688
|
-
await
|
|
1953
|
+
await gitRemote(
|
|
1954
|
+
["submodule", "update", "--init", "--recursive", "--checkout"],
|
|
1955
|
+
cwd,
|
|
1956
|
+
auth,
|
|
1957
|
+
12e4
|
|
1958
|
+
);
|
|
1689
1959
|
} catch (err) {
|
|
1690
1960
|
log.warn("git submodule update failed \u2014 checkout may be incomplete", {
|
|
1691
1961
|
error: redactCredentials(err instanceof Error ? err.message : String(err))
|
|
1692
1962
|
});
|
|
1693
1963
|
}
|
|
1694
1964
|
}
|
|
1965
|
+
async function checkoutSubmodulesOffline(cwd) {
|
|
1966
|
+
if (!await pathExists(join(cwd, ".gitmodules"))) return [];
|
|
1967
|
+
await gitOrNull(["submodule", "update", "--recursive", "--checkout", "--no-fetch"], cwd);
|
|
1968
|
+
const status = await gitOrNull(["submodule", "status", "--recursive"], cwd) ?? "";
|
|
1969
|
+
return status.split("\n").map((l) => /^[+-][0-9a-f]+ (.+?)(?: \([^)]*\))?$/.exec(l)?.[1]).filter((p) => p !== void 0);
|
|
1970
|
+
}
|
|
1695
1971
|
function isValidGitRefOrSha(ref) {
|
|
1696
1972
|
if (ref.length === 0 || ref.length > 255) return false;
|
|
1697
1973
|
if (/[\s\\~^:?*[\x00-\x1f\x7f`$;&|'"<>()]/.test(ref)) return false;
|
|
@@ -1736,6 +2012,40 @@ async function resolveUpstreamRef(cwd, branch) {
|
|
|
1736
2012
|
const ref = `refs/remotes/${remote}/${name}`;
|
|
1737
2013
|
return await gitOrNull(["rev-parse", "--verify", "--quiet", ref], cwd) !== null ? ref : null;
|
|
1738
2014
|
}
|
|
2015
|
+
async function assertOriginIsMount(s, cwd, op) {
|
|
2016
|
+
const expected = comparableRemoteUrl(s.url);
|
|
2017
|
+
for (const args of [
|
|
2018
|
+
["remote", "get-url", "origin"],
|
|
2019
|
+
["remote", "get-url", "--push", "origin"]
|
|
2020
|
+
]) {
|
|
2021
|
+
const actual = await gitOrNull(args, cwd);
|
|
2022
|
+
if (actual === null || comparableRemoteUrl(actual) !== expected) {
|
|
2023
|
+
throw new Error(
|
|
2024
|
+
`git ${op}: origin${args.includes("--push") ? " (push)" : ""} resolves to ${actual === null ? "nothing" : redactCredentials(actual)}, not this mount's remote ${expected}. Remove the pushurl / insteadOf / url override from the git config first.`
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
function comparableRemoteUrl(url) {
|
|
2030
|
+
if (!url.startsWith("https://")) return url;
|
|
2031
|
+
try {
|
|
2032
|
+
const u = new URL(url);
|
|
2033
|
+
return `${u.protocol}//${u.host}${u.pathname}`;
|
|
2034
|
+
} catch {
|
|
2035
|
+
return url;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
function repoScopedAuth(s) {
|
|
2039
|
+
return {
|
|
2040
|
+
get url() {
|
|
2041
|
+
return s.url;
|
|
2042
|
+
},
|
|
2043
|
+
get token() {
|
|
2044
|
+
return s.token;
|
|
2045
|
+
},
|
|
2046
|
+
repoScoped: true
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
1739
2049
|
function trackingRefspec(branch) {
|
|
1740
2050
|
return `+refs/heads/${branch}:refs/remotes/origin/${branch}`;
|
|
1741
2051
|
}
|
|
@@ -1762,51 +2072,15 @@ async function dropTrackingRefspec(branch, cwd) {
|
|
|
1762
2072
|
async function isAncestor(ancestor, descendant, cwd) {
|
|
1763
2073
|
return await gitOrNull(["merge-base", "--is-ancestor", ancestor, descendant], cwd) !== null;
|
|
1764
2074
|
}
|
|
1765
|
-
async function integrateOriginBaseBranch(branch, cwd) {
|
|
2075
|
+
async function integrateOriginBaseBranch(branch, cwd, auth) {
|
|
1766
2076
|
if (await gitOrNull(["symbolic-ref", "-q", "HEAD"], cwd) !== `refs/heads/${branch}`) return;
|
|
1767
|
-
if (await
|
|
2077
|
+
if (await unfinishedGitOperation(cwd) !== null) return;
|
|
1768
2078
|
try {
|
|
1769
|
-
await
|
|
1770
|
-
cwd,
|
|
1771
|
-
timeout: 6e4,
|
|
1772
|
-
env: cLocaleEnv()
|
|
1773
|
-
});
|
|
2079
|
+
await pullByMerge(branch, cwd, auth, 6e4);
|
|
1774
2080
|
} catch (err) {
|
|
1775
2081
|
if (/couldn't find remote ref/.test(err instanceof Error ? err.message : String(err))) return;
|
|
1776
2082
|
throw err;
|
|
1777
2083
|
}
|
|
1778
|
-
if (await isAncestor("FETCH_HEAD", "HEAD", cwd)) return;
|
|
1779
|
-
try {
|
|
1780
|
-
await gitCommitting(["merge", "--ff", "--no-edit", "FETCH_HEAD"], cwd, 6e4);
|
|
1781
|
-
} catch (err) {
|
|
1782
|
-
const unmerged = await gitOrNull(["diff", "--name-only", "--diff-filter=U"], cwd) ?? "";
|
|
1783
|
-
if (await gitOrNull(["rev-parse", "--quiet", "--verify", "MERGE_HEAD"], cwd) !== null) {
|
|
1784
|
-
await gitOrNull(["merge", "--abort"], cwd);
|
|
1785
|
-
}
|
|
1786
|
-
const files = unmerged.split("\n").filter(Boolean);
|
|
1787
|
-
if (files.length > 0) {
|
|
1788
|
-
throw new Error(
|
|
1789
|
-
`origin/${branch} and the local branch both have new commits that conflict in ${files.join(", ")}; the merge was aborted and nothing was pushed`
|
|
1790
|
-
);
|
|
1791
|
-
}
|
|
1792
|
-
throw err;
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
async function hasUnfinishedOperation(cwd) {
|
|
1796
|
-
for (const ref of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
|
|
1797
|
-
if (await gitOrNull(["rev-parse", "--quiet", "--verify", ref], cwd) !== null) return true;
|
|
1798
|
-
}
|
|
1799
|
-
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
1800
|
-
const path = await gitOrNull(["rev-parse", "--git-path", dir], cwd);
|
|
1801
|
-
if (path === null) continue;
|
|
1802
|
-
const found = await tryWithFsDeadline(
|
|
1803
|
-
`stat ${dir}`,
|
|
1804
|
-
() => stat(resolve(cwd, path)).then(() => true),
|
|
1805
|
-
false
|
|
1806
|
-
);
|
|
1807
|
-
if (found) return true;
|
|
1808
|
-
}
|
|
1809
|
-
return false;
|
|
1810
2084
|
}
|
|
1811
2085
|
async function originTrackingRef(branch, cwd) {
|
|
1812
2086
|
const ref = `refs/remotes/origin/${branch}`;
|
|
@@ -1852,11 +2126,34 @@ async function isDirEmpty(dir) {
|
|
|
1852
2126
|
const entries = await tryWithFsDeadline(`readdir ${dir}`, () => readdir(dir), []);
|
|
1853
2127
|
return entries.length === 0;
|
|
1854
2128
|
}
|
|
1855
|
-
async function commitAll(cwd, message) {
|
|
2129
|
+
async function commitAll(cwd, message, log) {
|
|
2130
|
+
if (await refuseUnfinishedOperation(cwd, log)) return null;
|
|
1856
2131
|
if (!await hasChanges(cwd)) return null;
|
|
1857
|
-
await
|
|
2132
|
+
await stageAll(cwd, log);
|
|
2133
|
+
if (await gitOrNull(["diff", "--cached", "--quiet"], cwd) !== null) return null;
|
|
1858
2134
|
return gitCommitting(["commit", "-m", message], cwd);
|
|
1859
2135
|
}
|
|
2136
|
+
async function stageAll(cwd, log) {
|
|
2137
|
+
await git(["add", "-A"], cwd);
|
|
2138
|
+
const raw = await gitOrNull(["diff", "--cached", "--raw", "--no-abbrev", "--no-renames", "-z"], cwd) ?? "";
|
|
2139
|
+
const fields = raw.split("\0");
|
|
2140
|
+
for (let i = 0; i + 1 < fields.length; i += 2) {
|
|
2141
|
+
const m = /^:160000 160000 ([0-9a-f]+) ([0-9a-f]+) M$/.exec(fields[i] ?? "");
|
|
2142
|
+
const path = fields[i + 1];
|
|
2143
|
+
if (m === null || !path) continue;
|
|
2144
|
+
const pinned = m[1];
|
|
2145
|
+
const staged = m[2];
|
|
2146
|
+
const dir = join(cwd, path);
|
|
2147
|
+
const known = await gitOrNull(["cat-file", "-e", `${pinned}^{commit}`], dir) !== null;
|
|
2148
|
+
if (!known || await isAncestor(staged, pinned, dir)) {
|
|
2149
|
+
await gitOrNull(["reset", "-q", "--", path], cwd);
|
|
2150
|
+
log.info("left a submodule pin out of the commit", {
|
|
2151
|
+
path,
|
|
2152
|
+
reason: known ? "would move the pin backwards" : "pinned commit is not local"
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
1860
2157
|
var TokenMediatorError = class extends Error {
|
|
1861
2158
|
/** Every mediator refusal is fixed where the credential lives, whatever its code. */
|
|
1862
2159
|
skaileConnectErrorKind = "credential";
|
|
@@ -1886,6 +2183,27 @@ var GitConnector = class extends AbstractConnector {
|
|
|
1886
2183
|
accessLevel: "read",
|
|
1887
2184
|
args: [],
|
|
1888
2185
|
examples: ["sync_status"]
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
name: "push",
|
|
2189
|
+
description: "Push this mount's branch (the session branch on a branching mount) to origin, authenticated by the mount. Pushes commits only: commit with the git CLI first. Never forces; a push origin refuses comes back as status 'rejected' (run 'pull', then push again). The checkout must be on the mount's branch.",
|
|
2190
|
+
accessLevel: "write",
|
|
2191
|
+
args: [],
|
|
2192
|
+
examples: ["push"]
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
name: "pull",
|
|
2196
|
+
description: "Fetch origin and merge origin's copy of this mount's branch into it (a merge, never a rebase). On a conflict the merge is aborted, the checkout is left as it was, and the conflicting files are listed (status 'conflict'); the fetched commits stay at origin/<branch> for a local `git merge`. Local changes that the merge would overwrite, or a merge or rebase already in progress, give status 'blocked'.",
|
|
2197
|
+
accessLevel: "read",
|
|
2198
|
+
args: [],
|
|
2199
|
+
examples: ["pull"]
|
|
2200
|
+
},
|
|
2201
|
+
{
|
|
2202
|
+
name: "fetch",
|
|
2203
|
+
description: "Fetch origin, authenticated by the mount, updating origin/<branch> refs only. The working tree is not touched.",
|
|
2204
|
+
accessLevel: "read",
|
|
2205
|
+
args: [],
|
|
2206
|
+
examples: ["fetch"]
|
|
1889
2207
|
}
|
|
1890
2208
|
];
|
|
1891
2209
|
}
|
|
@@ -1899,10 +2217,163 @@ var GitConnector = class extends AbstractConnector {
|
|
|
1899
2217
|
const state = handle.state;
|
|
1900
2218
|
return JSON.stringify(await readGitSyncStatus(cwd, state.branch));
|
|
1901
2219
|
}
|
|
2220
|
+
case "push":
|
|
2221
|
+
case "pull":
|
|
2222
|
+
case "fetch":
|
|
2223
|
+
return JSON.stringify(await this.agentRemoteOp(handle, operation));
|
|
1902
2224
|
default:
|
|
1903
2225
|
throw new Error(`GitConnector has no operation: ${operation}`);
|
|
1904
2226
|
}
|
|
1905
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Run `fn` after every earlier job on this mount has settled. Network git and
|
|
2230
|
+
* index-writing git on one checkout must not overlap: two of them contend for
|
|
2231
|
+
* `.git/index.lock`, and a push racing a pull can push a half-merged branch.
|
|
2232
|
+
* Not re-entrant — a job must never wait on the lock it runs under.
|
|
2233
|
+
*/
|
|
2234
|
+
withMountLock(s, fn) {
|
|
2235
|
+
const run = (s.mountLock ?? Promise.resolve()).then(fn);
|
|
2236
|
+
s.mountLock = run.then(
|
|
2237
|
+
() => void 0,
|
|
2238
|
+
() => void 0
|
|
2239
|
+
);
|
|
2240
|
+
return run;
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* The agent's network git: the driver runs it with the mount's credential, so
|
|
2244
|
+
* an agent on a mount without `exposeAccessToken` can push and pull without
|
|
2245
|
+
* ever holding the token. Acts only on the mount's own branch, never forces.
|
|
2246
|
+
*/
|
|
2247
|
+
async agentRemoteOp(handle, op) {
|
|
2248
|
+
const cwd = handle.mountPath;
|
|
2249
|
+
if (!cwd || handle.status !== "connected") {
|
|
2250
|
+
throw new Error(`git ${op}: the mount is not connected (${handle.status})`);
|
|
2251
|
+
}
|
|
2252
|
+
const s = handle.state;
|
|
2253
|
+
if (s.fleetManaged) {
|
|
2254
|
+
throw new Error(`git ${op}: the host syncs this mount; there is nothing to run here`);
|
|
2255
|
+
}
|
|
2256
|
+
if (op === "push" && handle.access === "read-only") {
|
|
2257
|
+
throw new Error("git push: this mount is read-only");
|
|
2258
|
+
}
|
|
2259
|
+
return this.withMountLock(s, async () => {
|
|
2260
|
+
if (s.disposed) throw new Error(`git ${op}: the mount is disconnected`);
|
|
2261
|
+
await assertOriginIsMount(s, cwd, op);
|
|
2262
|
+
if (op !== "fetch") {
|
|
2263
|
+
const head = await gitOrNull(["symbolic-ref", "--quiet", "--short", "HEAD"], cwd);
|
|
2264
|
+
if (head !== s.activeBranch) {
|
|
2265
|
+
throw new Error(
|
|
2266
|
+
`git ${op}: the checkout is on ${head === null ? "a detached HEAD" : `"${head}"`}, but this mount pushes and pulls only "${s.activeBranch}". Check it out first.`
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
let outcome;
|
|
2271
|
+
try {
|
|
2272
|
+
outcome = op === "push" ? await this.agentPush(s, cwd) : op === "pull" ? await this.agentPull(s, cwd) : await this.agentFetch(s, cwd);
|
|
2273
|
+
} catch (err) {
|
|
2274
|
+
throw new Error(
|
|
2275
|
+
`git ${op} failed: ${redactCredentials(err instanceof Error ? err.message : String(err))}`
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
this.log.info("agent git op", { op, branch: s.activeBranch, status: outcome.status });
|
|
2279
|
+
return {
|
|
2280
|
+
op,
|
|
2281
|
+
branch: s.activeBranch,
|
|
2282
|
+
...outcome,
|
|
2283
|
+
sync: await readGitSyncStatus(cwd, s.branch)
|
|
2284
|
+
};
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
/** Fetch origin's base branch and, on a branching mount, the session branch. */
|
|
2288
|
+
async fetchActiveBranch(s, cwd) {
|
|
2289
|
+
const auth = repoScopedAuth(s);
|
|
2290
|
+
await this.fetchOrigin(s, cwd, auth);
|
|
2291
|
+
if (s.activeBranch === s.branch) return;
|
|
2292
|
+
if (await this.trackSessionBranch(s.activeBranch, cwd, auth) === "failed") {
|
|
2293
|
+
throw new Error(`could not fetch origin/${s.activeBranch}`);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
async agentFetch(s, cwd) {
|
|
2297
|
+
await this.fetchActiveBranch(s, cwd);
|
|
2298
|
+
return { status: "fetched" };
|
|
2299
|
+
}
|
|
2300
|
+
async agentPush(s, cwd) {
|
|
2301
|
+
const b = s.activeBranch;
|
|
2302
|
+
const args = ["push", "origin", `refs/heads/${b}:refs/heads/${b}`];
|
|
2303
|
+
try {
|
|
2304
|
+
await runRemoteGit(args, repoScopedAuth(s), {
|
|
2305
|
+
cwd,
|
|
2306
|
+
safeDir: cwd,
|
|
2307
|
+
timeout: 6e4,
|
|
2308
|
+
env: cLocaleEnv()
|
|
2309
|
+
});
|
|
2310
|
+
} catch (err) {
|
|
2311
|
+
const message = redactCredentials(err instanceof Error ? err.message : String(err));
|
|
2312
|
+
if (/\[rejected\]|non-fast-forward|fetch first/.test(message)) {
|
|
2313
|
+
return {
|
|
2314
|
+
status: "rejected",
|
|
2315
|
+
message: `origin has commits on "${b}" that this checkout lacks, so the push was refused (no force). Run the "pull" op, then push again.`
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
const remote = /\[remote rejected\][^\n]*/.exec(message)?.[0];
|
|
2319
|
+
if (remote !== void 0) {
|
|
2320
|
+
return { status: "rejected", message: `origin refused the push: ${remote.trim()}` };
|
|
2321
|
+
}
|
|
2322
|
+
throw err;
|
|
2323
|
+
}
|
|
2324
|
+
if (b === s.branch) {
|
|
2325
|
+
await gitOrNull(["update-ref", `refs/remotes/origin/${b}`, `refs/heads/${b}`], cwd);
|
|
2326
|
+
} else {
|
|
2327
|
+
await setSessionUpstream(b, cwd);
|
|
2328
|
+
await this.recordPushedSessionBranch(b, cwd);
|
|
2329
|
+
}
|
|
2330
|
+
return { status: "pushed" };
|
|
2331
|
+
}
|
|
2332
|
+
async agentPull(s, cwd) {
|
|
2333
|
+
const b = s.activeBranch;
|
|
2334
|
+
await this.fetchActiveBranch(s, cwd);
|
|
2335
|
+
const pending = await unfinishedGitOperation(cwd);
|
|
2336
|
+
if (pending !== null) {
|
|
2337
|
+
return {
|
|
2338
|
+
status: "blocked",
|
|
2339
|
+
message: `the checkout has an unfinished merge or rebase (${pending}), so nothing was merged. Finish it (resolve, \`git add\`, \`git commit\`) or abort it, then pull again.`
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
const upstream = await originTrackingRef(b, cwd);
|
|
2343
|
+
if (upstream === null) {
|
|
2344
|
+
return {
|
|
2345
|
+
status: "no-upstream",
|
|
2346
|
+
message: `origin has no branch "${b}" yet; nothing to pull. "push" publishes it.`
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
if (await isAncestor(upstream, "HEAD", cwd)) return { status: "up-to-date" };
|
|
2350
|
+
try {
|
|
2351
|
+
await gitCommitting(["merge", "--ff", "--no-edit", upstream], cwd, 6e4);
|
|
2352
|
+
} catch (err) {
|
|
2353
|
+
const unmerged = await gitOrNull(["diff", "--name-only", "--diff-filter=U"], cwd);
|
|
2354
|
+
const conflicts = (unmerged ?? "").split("\n").filter(Boolean);
|
|
2355
|
+
if (await gitOrNull(["rev-parse", "--quiet", "--verify", "MERGE_HEAD"], cwd) !== null) {
|
|
2356
|
+
await git(["merge", "--abort"], cwd);
|
|
2357
|
+
}
|
|
2358
|
+
if (conflicts.length > 0) {
|
|
2359
|
+
return {
|
|
2360
|
+
status: "conflict",
|
|
2361
|
+
conflicts,
|
|
2362
|
+
message: `merging origin/${b} conflicts, so the merge was aborted and the checkout is as it was. The fetched commits are at origin/${b}: run \`git merge origin/${b}\`, resolve, commit, then "push".`
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
return {
|
|
2366
|
+
status: "blocked",
|
|
2367
|
+
message: `the merge did not start: ${redactCredentials(err instanceof Error ? err.message : String(err))}`
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
const stale = await checkoutSubmodulesOffline(cwd);
|
|
2371
|
+
if (stale.length === 0) return { status: "merged" };
|
|
2372
|
+
return {
|
|
2373
|
+
status: "merged",
|
|
2374
|
+
message: `submodules ${stale.join(", ")} were not updated: their new commits need a fetch, which happens at the next platform sync. Do not stage or commit them until then.`
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
1906
2377
|
async connect(declaration, ctx) {
|
|
1907
2378
|
const targetDir = ctx.mountTarget;
|
|
1908
2379
|
if (!targetDir) {
|
|
@@ -1945,11 +2416,10 @@ var GitConnector = class extends AbstractConnector {
|
|
|
1945
2416
|
targetDir,
|
|
1946
2417
|
tokenMediator: initial.authRef?.kind === "backend" ? tokenMediator : void 0
|
|
1947
2418
|
};
|
|
1948
|
-
|
|
1949
|
-
await this.prepareCheckout(targetDir, url, branch, authUrl, declaration.access, started);
|
|
2419
|
+
await this.prepareCheckout(targetDir, url, branch, state, declaration.access, started);
|
|
1950
2420
|
if (session.enabled && !session.isMain && session.slug) {
|
|
1951
2421
|
if (declaration.mount?.exposeAccessToken !== true) {
|
|
1952
|
-
await this.
|
|
2422
|
+
await this.reconcileRemoteUrl(targetDir, state);
|
|
1953
2423
|
}
|
|
1954
2424
|
await this.checkoutSessionBranch(state, session, targetDir);
|
|
1955
2425
|
}
|
|
@@ -1969,7 +2439,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
1969
2439
|
}
|
|
1970
2440
|
}
|
|
1971
2441
|
if (!state.managedGitconfig) {
|
|
1972
|
-
await this.
|
|
2442
|
+
await this.reconcileRemoteUrl(targetDir, state);
|
|
1973
2443
|
if (initial.token) {
|
|
1974
2444
|
const providerKind = initial.authRef?.kind ?? "pat";
|
|
1975
2445
|
const skipReason = providerKind !== "backend" ? "non-backend-credential" : !state.tokenMediator ? "no-token-mediator" : !initial.expiresAt ? "no-expiry" : null;
|
|
@@ -1989,13 +2459,17 @@ var GitConnector = class extends AbstractConnector {
|
|
|
1989
2459
|
}
|
|
1990
2460
|
}
|
|
1991
2461
|
if (state.sessionBranchFetch === "failed") {
|
|
1992
|
-
state.sessionBranchFetch = await this.trackSessionBranch(
|
|
2462
|
+
state.sessionBranchFetch = await this.trackSessionBranch(
|
|
2463
|
+
state.activeBranch,
|
|
2464
|
+
targetDir,
|
|
2465
|
+
state
|
|
2466
|
+
);
|
|
1993
2467
|
if (state.sessionBranchFetch === "fetched") {
|
|
1994
2468
|
if (state.sessionBranchCreated) {
|
|
1995
|
-
await this.adoptOriginSessionBranch(state.activeBranch, targetDir);
|
|
2469
|
+
await this.adoptOriginSessionBranch(state, state.activeBranch, targetDir);
|
|
1996
2470
|
} else {
|
|
1997
2471
|
await this.reconcileLocalSessionBranch(state, state.activeBranch, targetDir);
|
|
1998
|
-
await syncSubmodules(targetDir, this.log);
|
|
2472
|
+
await syncSubmodules(targetDir, this.log, state);
|
|
1999
2473
|
}
|
|
2000
2474
|
}
|
|
2001
2475
|
}
|
|
@@ -2041,55 +2515,57 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2041
2515
|
* reset, no clean. Existing files become uncommitted-on-init; the
|
|
2042
2516
|
* agent or `pushAfterCommit` reconciles with remote later.
|
|
2043
2517
|
*/
|
|
2044
|
-
async prepareCheckout(targetDir, url, branch,
|
|
2518
|
+
async prepareCheckout(targetDir, url, branch, auth, access, started) {
|
|
2045
2519
|
try {
|
|
2046
2520
|
if (await pathExists(join(targetDir, ".git"))) {
|
|
2047
2521
|
await clearStaleIndexLock(targetDir, this.log);
|
|
2048
2522
|
if (await this.isInitialisedCheckout(targetDir)) {
|
|
2049
2523
|
this.log.info("found existing checkout \u2014 using as-is", { branch, targetDir });
|
|
2050
2524
|
} else {
|
|
2051
|
-
if (await this.countRemoteHeads(targetDir,
|
|
2525
|
+
if (await this.countRemoteHeads(targetDir, auth) === 0) {
|
|
2052
2526
|
this.log.info("empty .git against a ref-less remote \u2014 bootstrapping branch", {
|
|
2053
2527
|
branch,
|
|
2054
2528
|
access
|
|
2055
2529
|
});
|
|
2056
|
-
await this.bootstrapEmptyRemote(targetDir, branch,
|
|
2530
|
+
await this.bootstrapEmptyRemote(targetDir, branch, auth, access);
|
|
2057
2531
|
} else {
|
|
2058
2532
|
this.log.info("found empty .git \u2014 bootstrapping from origin", { branch });
|
|
2059
|
-
await this.bootstrapFromOrigin(targetDir, branch,
|
|
2533
|
+
await this.bootstrapFromOrigin(targetDir, branch, auth);
|
|
2060
2534
|
}
|
|
2061
2535
|
this.log.info("bootstrap ok", { branch, durationMs: Date.now() - started });
|
|
2062
2536
|
}
|
|
2063
2537
|
} else {
|
|
2064
2538
|
await withFsDeadline(`mkdir ${targetDir}`, () => mkdir(targetDir, { recursive: true }));
|
|
2065
2539
|
if (await isDirEmpty(targetDir)) {
|
|
2066
|
-
if (await this.countRemoteHeads(targetDir,
|
|
2540
|
+
if (await this.countRemoteHeads(targetDir, auth) === 0) {
|
|
2067
2541
|
this.log.info("remote has no refs \u2014 bootstrapping branch", { url, branch, access });
|
|
2068
|
-
await this.bootstrapEmptyRemote(targetDir, branch,
|
|
2542
|
+
await this.bootstrapEmptyRemote(targetDir, branch, auth, access);
|
|
2069
2543
|
this.log.info("empty-remote bootstrap ok", {
|
|
2070
2544
|
branch,
|
|
2071
2545
|
durationMs: Date.now() - started
|
|
2072
2546
|
});
|
|
2073
2547
|
} else {
|
|
2074
2548
|
this.log.info("clone start", { url, branch });
|
|
2075
|
-
await
|
|
2549
|
+
await runRemoteGit(
|
|
2076
2550
|
[
|
|
2077
|
-
...gitPrefix(targetDir),
|
|
2078
2551
|
"clone",
|
|
2079
2552
|
"--branch",
|
|
2080
2553
|
branch,
|
|
2081
2554
|
"--single-branch",
|
|
2082
|
-
"--
|
|
2083
|
-
|
|
2555
|
+
"--no-checkout",
|
|
2556
|
+
plainRemoteUrl(auth),
|
|
2084
2557
|
targetDir
|
|
2085
2558
|
],
|
|
2086
|
-
|
|
2559
|
+
auth,
|
|
2560
|
+
{ safeDir: targetDir, timeout: 12e4 }
|
|
2087
2561
|
);
|
|
2562
|
+
await git(["checkout", "-f", branch], targetDir, 12e4);
|
|
2563
|
+
await syncSubmodules(targetDir, this.log, auth);
|
|
2088
2564
|
this.log.info("clone ok", { branch, durationMs: Date.now() - started });
|
|
2089
2565
|
}
|
|
2090
2566
|
} else {
|
|
2091
2567
|
this.log.warn("target non-empty without .git \u2014 initialising in place", { targetDir });
|
|
2092
|
-
await this.initInPlace(targetDir,
|
|
2568
|
+
await this.initInPlace(targetDir, plainRemoteUrl(auth));
|
|
2093
2569
|
this.log.info("init in place ok", { durationMs: Date.now() - started });
|
|
2094
2570
|
}
|
|
2095
2571
|
}
|
|
@@ -2113,7 +2589,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2113
2589
|
"config"
|
|
2114
2590
|
);
|
|
2115
2591
|
}
|
|
2116
|
-
state.sessionBranchFetch = await this.trackSessionBranch(sessionBranch, targetDir);
|
|
2592
|
+
state.sessionBranchFetch = await this.trackSessionBranch(sessionBranch, targetDir, state);
|
|
2117
2593
|
let created = false;
|
|
2118
2594
|
if (await localBranchExists(sessionBranch, targetDir)) {
|
|
2119
2595
|
await gitLocalCheckout([sessionBranch], targetDir);
|
|
@@ -2130,20 +2606,20 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2130
2606
|
}
|
|
2131
2607
|
if (originHasBranch) {
|
|
2132
2608
|
await gitLocalCheckout(["-B", sessionBranch, `origin/${sessionBranch}`], targetDir);
|
|
2133
|
-
} else if (!await this.branchFromRef(session, sessionBranch, targetDir)) {
|
|
2609
|
+
} else if (!await this.branchFromRef(state, session, sessionBranch, targetDir)) {
|
|
2134
2610
|
await gitLocalCheckout(["-b", sessionBranch], targetDir);
|
|
2135
2611
|
created = true;
|
|
2136
2612
|
} else {
|
|
2137
2613
|
created = true;
|
|
2138
2614
|
}
|
|
2139
2615
|
}
|
|
2140
|
-
await syncSubmodules(targetDir, this.log);
|
|
2616
|
+
await syncSubmodules(targetDir, this.log, state);
|
|
2141
2617
|
state.activeBranch = sessionBranch;
|
|
2142
2618
|
state.sessionBranchCreated = created;
|
|
2143
2619
|
this.log.info("session branch checked out", { sessionBranch });
|
|
2144
2620
|
if (created && state.lifecycle.publishOnCreate) {
|
|
2145
2621
|
try {
|
|
2146
|
-
await
|
|
2622
|
+
await gitRemote(["push", "-u", "origin", sessionBranch], targetDir, state, 6e4);
|
|
2147
2623
|
await this.recordPushedSessionBranch(sessionBranch, targetDir);
|
|
2148
2624
|
this.log.info("published new session branch", { sessionBranch });
|
|
2149
2625
|
} catch (err) {
|
|
@@ -2165,11 +2641,12 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2165
2641
|
* every plain `git fetch origin` fail. Runs on every connect, so it also
|
|
2166
2642
|
* repairs a checkout cloned before the refspec existed.
|
|
2167
2643
|
*/
|
|
2168
|
-
async trackSessionBranch(sessionBranch, targetDir) {
|
|
2644
|
+
async trackSessionBranch(sessionBranch, targetDir, auth) {
|
|
2169
2645
|
let outcome;
|
|
2170
2646
|
try {
|
|
2171
|
-
await
|
|
2647
|
+
await runRemoteGit(["fetch", "origin", trackingRefspec(sessionBranch)], auth, {
|
|
2172
2648
|
cwd: targetDir,
|
|
2649
|
+
safeDir: targetDir,
|
|
2173
2650
|
timeout: 6e4,
|
|
2174
2651
|
env: cLocaleEnv()
|
|
2175
2652
|
});
|
|
@@ -2204,10 +2681,10 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2204
2681
|
* checkout is refused (for example local changes in the way), the branch
|
|
2205
2682
|
* stays as it is and the push later fails as non-fast-forward.
|
|
2206
2683
|
*/
|
|
2207
|
-
async adoptOriginSessionBranch(sessionBranch, targetDir) {
|
|
2684
|
+
async adoptOriginSessionBranch(state, sessionBranch, targetDir) {
|
|
2208
2685
|
try {
|
|
2209
2686
|
await gitLocalCheckout(["-B", sessionBranch, `origin/${sessionBranch}`], targetDir);
|
|
2210
|
-
await syncSubmodules(targetDir, this.log);
|
|
2687
|
+
await syncSubmodules(targetDir, this.log, state);
|
|
2211
2688
|
this.log.info("session branch re-pointed at origin after auth retry", { sessionBranch });
|
|
2212
2689
|
} catch (err) {
|
|
2213
2690
|
this.log.warn("could not re-point session branch at origin", {
|
|
@@ -2266,6 +2743,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2266
2743
|
async integrateOriginSessionBranch(s, cwd, branch = s.activeBranch) {
|
|
2267
2744
|
const originRef = await originTrackingRef(branch, cwd);
|
|
2268
2745
|
if (originRef === null || await isAncestor(originRef, "HEAD", cwd)) return;
|
|
2746
|
+
if (await this.skipOverUnfinishedOperation(cwd, "merge of origin session branch")) return;
|
|
2269
2747
|
try {
|
|
2270
2748
|
await gitCommitting(["merge", "--no-edit", originRef], cwd, 6e4);
|
|
2271
2749
|
this.log.info("merged origin session branch", { branch });
|
|
@@ -2307,12 +2785,12 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2307
2785
|
*/
|
|
2308
2786
|
async pushActiveBranch(s, cwd) {
|
|
2309
2787
|
if (s.activeBranch === s.branch) {
|
|
2310
|
-
await integrateOriginBaseBranch(s.branch, cwd);
|
|
2311
|
-
await
|
|
2788
|
+
await integrateOriginBaseBranch(s.branch, cwd, s);
|
|
2789
|
+
await gitRemote(["push", "origin", s.activeBranch], cwd, s, 6e4);
|
|
2312
2790
|
return;
|
|
2313
2791
|
}
|
|
2314
2792
|
await this.integrateOriginSessionBranch(s, cwd);
|
|
2315
|
-
await
|
|
2793
|
+
await gitRemote(["push", "-u", "origin", s.activeBranch], cwd, s, 6e4);
|
|
2316
2794
|
await this.recordPushedSessionBranch(s.activeBranch, cwd);
|
|
2317
2795
|
}
|
|
2318
2796
|
/**
|
|
@@ -2320,7 +2798,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2320
2798
|
* true when the branch was created from the anchor; false when no valid
|
|
2321
2799
|
* `fromRef` was supplied or the fetch failed (caller falls back to base).
|
|
2322
2800
|
*/
|
|
2323
|
-
async branchFromRef(session, sessionBranch, targetDir) {
|
|
2801
|
+
async branchFromRef(auth, session, sessionBranch, targetDir) {
|
|
2324
2802
|
if (!session.fromRef) return false;
|
|
2325
2803
|
if (!isValidGitRefOrSha(session.fromRef)) {
|
|
2326
2804
|
this.log.warn("fromRef rejected by refname validation, falling back to base branch", {
|
|
@@ -2330,7 +2808,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2330
2808
|
return false;
|
|
2331
2809
|
}
|
|
2332
2810
|
try {
|
|
2333
|
-
await
|
|
2811
|
+
await gitRemote(["fetch", "origin", session.fromRef], targetDir, auth);
|
|
2334
2812
|
await git(["checkout", "-b", sessionBranch, "FETCH_HEAD"], targetDir);
|
|
2335
2813
|
this.log.info("session branch created from fromRef", {
|
|
2336
2814
|
sessionBranch,
|
|
@@ -2462,16 +2940,18 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2462
2940
|
this.log.debug("sync skipped \u2014 fleet-managed");
|
|
2463
2941
|
return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
|
|
2464
2942
|
}
|
|
2465
|
-
this.
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2943
|
+
return this.withMountLock(s, async () => {
|
|
2944
|
+
this.log.debug("sync (pull) start", { branch: s.activeBranch });
|
|
2945
|
+
try {
|
|
2946
|
+
await pullByMerge(s.activeBranch, handle.mountPath, s, 6e4);
|
|
2947
|
+
await syncSubmodules(handle.mountPath, this.log, s);
|
|
2948
|
+
this.log.debug("sync ok");
|
|
2949
|
+
} catch (err) {
|
|
2950
|
+
this.log.error("sync failed", err, { branch: s.activeBranch });
|
|
2951
|
+
throw err;
|
|
2952
|
+
}
|
|
2953
|
+
return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
|
|
2954
|
+
});
|
|
2475
2955
|
}
|
|
2476
2956
|
// ── Watch ──────────────────────────────────────────────────────────────────
|
|
2477
2957
|
watch(handle, callback, options) {
|
|
@@ -2516,19 +2996,21 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2516
2996
|
}
|
|
2517
2997
|
const lc = s.lifecycle;
|
|
2518
2998
|
if (!lc.commitOnHibernate) return;
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2999
|
+
return this.withMountLock(s, async () => {
|
|
3000
|
+
const result = await commitAll(handle.mountPath, `skaile: hibernate snapshot`, this.log);
|
|
3001
|
+
if (!result) return this.pushUnpushedOnHibernate(s, handle.mountPath);
|
|
3002
|
+
const sha = await git(["rev-parse", "--short", "HEAD"], handle.mountPath);
|
|
3003
|
+
if (lc.pushOnHibernate) {
|
|
3004
|
+
try {
|
|
3005
|
+
await this.pushActiveBranch(s, handle.mountPath);
|
|
3006
|
+
this.log.info("push ok", { branch: s.activeBranch });
|
|
3007
|
+
} catch (err) {
|
|
3008
|
+
this.log.error("push failed", err, { branch: s.activeBranch });
|
|
3009
|
+
throw err;
|
|
3010
|
+
}
|
|
2529
3011
|
}
|
|
2530
|
-
|
|
2531
|
-
|
|
3012
|
+
return `Committed ${sha} on ${s.activeBranch}${lc.pushOnHibernate ? " (pushed)" : ""}`;
|
|
3013
|
+
});
|
|
2532
3014
|
}
|
|
2533
3015
|
/**
|
|
2534
3016
|
* Hibernate with nothing new to commit: push commits that are still only
|
|
@@ -2560,46 +3042,54 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2560
3042
|
}
|
|
2561
3043
|
const lc = s.lifecycle;
|
|
2562
3044
|
const details = [];
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
try {
|
|
2579
|
-
await gitCommitting(["merge", s.activeBranch], handle.mountPath);
|
|
2580
|
-
} catch (err) {
|
|
2581
|
-
this.log.warn("merge failed", {
|
|
2582
|
-
branch: s.activeBranch,
|
|
2583
|
-
error: redactCredentials(err instanceof Error ? err.message : String(err))
|
|
3045
|
+
return this.withMountLock(s, async () => {
|
|
3046
|
+
try {
|
|
3047
|
+
const commitResult = await commitAll(handle.mountPath, `skaile: session close`, this.log);
|
|
3048
|
+
if (commitResult) {
|
|
3049
|
+
const sha = await git(["rev-parse", "--short", "HEAD"], handle.mountPath);
|
|
3050
|
+
details.push(`Committed ${sha}`);
|
|
3051
|
+
}
|
|
3052
|
+
if (lc.pushOnClose || lc.mergeOnClose) {
|
|
3053
|
+
await this.pushActiveBranch(s, handle.mountPath);
|
|
3054
|
+
details.push(`Pushed ${s.activeBranch}`);
|
|
3055
|
+
}
|
|
3056
|
+
const closePending = lc.mergeOnClose && s.activeBranch !== s.branch ? await unfinishedGitOperation(handle.mountPath) : null;
|
|
3057
|
+
if (closePending !== null) {
|
|
3058
|
+
this.log.warn("merge on close skipped: the checkout has an unfinished merge or rebase", {
|
|
3059
|
+
reason: closePending
|
|
2584
3060
|
});
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
3061
|
+
details.push(`Merge of ${s.activeBranch} into ${s.branch} skipped: ${closePending}`);
|
|
3062
|
+
} else if (lc.mergeOnClose && s.activeBranch !== s.branch) {
|
|
3063
|
+
await git(["checkout", s.branch], handle.mountPath);
|
|
3064
|
+
await pullByMerge(s.branch, handle.mountPath, s, 6e4);
|
|
3065
|
+
await syncSubmodules(handle.mountPath, this.log, s);
|
|
3066
|
+
let merged = true;
|
|
3067
|
+
try {
|
|
3068
|
+
await gitCommitting(["merge", s.activeBranch], handle.mountPath);
|
|
3069
|
+
} catch (err) {
|
|
3070
|
+
this.log.warn("merge failed", {
|
|
3071
|
+
branch: s.activeBranch,
|
|
3072
|
+
error: redactCredentials(err instanceof Error ? err.message : String(err))
|
|
3073
|
+
});
|
|
3074
|
+
const resolved = await this.resolveConflictsOrAbort(s, handle.mountPath, "merge");
|
|
3075
|
+
if (resolved) {
|
|
3076
|
+
details.push("Resolved merge conflicts (created .conflict files)");
|
|
3077
|
+
} else {
|
|
3078
|
+
merged = false;
|
|
3079
|
+
}
|
|
2590
3080
|
}
|
|
3081
|
+
if (lc.pushOnClose) {
|
|
3082
|
+
await gitRemote(["push", "origin", s.branch], handle.mountPath, s, 6e4);
|
|
3083
|
+
}
|
|
3084
|
+
details.push(
|
|
3085
|
+
merged ? `Merged ${s.activeBranch} into ${s.branch}` : `Merge of ${s.activeBranch} into ${s.branch} FAILED`
|
|
3086
|
+
);
|
|
2591
3087
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
details.push(
|
|
2596
|
-
merged ? `Merged ${s.activeBranch} into ${s.branch}` : `Merge of ${s.activeBranch} into ${s.branch} FAILED`
|
|
2597
|
-
);
|
|
3088
|
+
return details.join("; ") || void 0;
|
|
3089
|
+
} finally {
|
|
3090
|
+
await this.teardownManagedCredential(handle.id, s);
|
|
2598
3091
|
}
|
|
2599
|
-
|
|
2600
|
-
} finally {
|
|
2601
|
-
await this.teardownManagedCredential(handle.id, s);
|
|
2602
|
-
}
|
|
3092
|
+
});
|
|
2603
3093
|
}
|
|
2604
3094
|
// ── Private: managed gitconfig (Tier-2 credential exposure) ──────────────
|
|
2605
3095
|
/**
|
|
@@ -2926,15 +3416,6 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2926
3416
|
token: result.token,
|
|
2927
3417
|
credentialsPath: state.managedGitconfig.credentialsPath
|
|
2928
3418
|
});
|
|
2929
|
-
} else if (state.targetDir) {
|
|
2930
|
-
const rebake = this.rebakeRemoteAuth(state.targetDir, state);
|
|
2931
|
-
state.rebakeInFlight = rebake;
|
|
2932
|
-
try {
|
|
2933
|
-
await rebake;
|
|
2934
|
-
} finally {
|
|
2935
|
-
if (state.rebakeInFlight === rebake) state.rebakeInFlight = void 0;
|
|
2936
|
-
}
|
|
2937
|
-
if (state.tokenMediator !== mediator) return;
|
|
2938
3419
|
}
|
|
2939
3420
|
this.log.info("refresh", {
|
|
2940
3421
|
event: "refresh",
|
|
@@ -2954,11 +3435,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2954
3435
|
state.refreshTimer = void 0;
|
|
2955
3436
|
}
|
|
2956
3437
|
state.tokenMediator = void 0;
|
|
2957
|
-
if (state.
|
|
2958
|
-
if (!state.managedGitconfig) {
|
|
2959
|
-
await this.stripRemoteAuth(state);
|
|
2960
|
-
return;
|
|
2961
|
-
}
|
|
3438
|
+
if (!state.managedGitconfig) return;
|
|
2962
3439
|
try {
|
|
2963
3440
|
removeMountBlock({
|
|
2964
3441
|
mountId: connectorId,
|
|
@@ -2991,7 +3468,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
2991
3468
|
* The intent is to preserve whatever the agent last had on disk. We
|
|
2992
3469
|
* therefore:
|
|
2993
3470
|
* - `git init` — creates `.git/`; does not modify any existing file.
|
|
2994
|
-
* - `git remote add origin <
|
|
3471
|
+
* - `git remote add origin <remoteUrl>` — wires up the remote so the
|
|
2995
3472
|
* normal lifecycle hooks (`pushOnHibernate`, `pushAfterCommit`)
|
|
2996
3473
|
* have a target.
|
|
2997
3474
|
*
|
|
@@ -3003,32 +3480,32 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3003
3480
|
* user's last on-disk state. The agent or autocommit reconciles
|
|
3004
3481
|
* with the remote later.
|
|
3005
3482
|
*
|
|
3006
|
-
*
|
|
3007
|
-
*
|
|
3008
|
-
* handling is external to this method (managed-gitconfig credential
|
|
3009
|
-
* helper refresh in Tier-2 connectors).
|
|
3483
|
+
* `remoteUrl` carries no credential; network commands authenticate per
|
|
3484
|
+
* command (see `runRemoteGit`).
|
|
3010
3485
|
*/
|
|
3011
|
-
async initInPlace(targetDir,
|
|
3486
|
+
async initInPlace(targetDir, remoteUrl) {
|
|
3012
3487
|
await git(["init"], targetDir, 1e4);
|
|
3013
3488
|
try {
|
|
3014
3489
|
await git(["remote", "remove", "origin"], targetDir, 5e3);
|
|
3015
3490
|
} catch {
|
|
3016
3491
|
}
|
|
3017
|
-
await git(["remote", "add", "origin",
|
|
3492
|
+
await git(["remote", "add", "origin", remoteUrl], targetDir, 5e3);
|
|
3018
3493
|
}
|
|
3019
3494
|
/**
|
|
3020
|
-
* Count `refs/heads/*` on
|
|
3495
|
+
* Count `refs/heads/*` on the remote, optionally narrowed to one `branch`.
|
|
3021
3496
|
*
|
|
3022
3497
|
* Returns `null` when the probe itself fails (network, auth, bad URL).
|
|
3023
3498
|
* "Cannot tell" must never collapse into "no refs": the empty-remote
|
|
3024
3499
|
* fallback WRITES to origin, and every caller therefore treats `null` as
|
|
3025
3500
|
* "assume populated" and falls through to the path that fails loudly.
|
|
3026
3501
|
*/
|
|
3027
|
-
async countRemoteHeads(targetDir,
|
|
3502
|
+
async countRemoteHeads(targetDir, auth, branch) {
|
|
3503
|
+
const remoteUrl = plainRemoteUrl(auth);
|
|
3028
3504
|
try {
|
|
3029
|
-
const out = await
|
|
3030
|
-
branch === void 0 ? [
|
|
3031
|
-
|
|
3505
|
+
const out = await runRemoteGit(
|
|
3506
|
+
branch === void 0 ? ["ls-remote", "--heads", remoteUrl] : ["ls-remote", "--heads", remoteUrl, `refs/heads/${branch}`],
|
|
3507
|
+
auth,
|
|
3508
|
+
{ safeDir: targetDir, timeout: 3e4 }
|
|
3032
3509
|
);
|
|
3033
3510
|
return out.length === 0 ? 0 : out.split("\n").length;
|
|
3034
3511
|
} catch (err) {
|
|
@@ -3062,8 +3539,8 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3062
3539
|
* come up with a usable checkout even when origin is unreachable — the
|
|
3063
3540
|
* lifecycle hooks (`pushAfterCommit`, hibernate/close push) publish later.
|
|
3064
3541
|
*/
|
|
3065
|
-
async bootstrapEmptyRemote(targetDir, branch,
|
|
3066
|
-
await this.initInPlace(targetDir,
|
|
3542
|
+
async bootstrapEmptyRemote(targetDir, branch, auth, access) {
|
|
3543
|
+
await this.initInPlace(targetDir, plainRemoteUrl(auth));
|
|
3067
3544
|
await git(["symbolic-ref", "HEAD", `refs/heads/${branch}`], targetDir, 5e3);
|
|
3068
3545
|
await git(
|
|
3069
3546
|
[
|
|
@@ -3086,7 +3563,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3086
3563
|
return;
|
|
3087
3564
|
}
|
|
3088
3565
|
try {
|
|
3089
|
-
await
|
|
3566
|
+
await gitRemote(["push", "-u", "origin", branch], targetDir, auth, 6e4);
|
|
3090
3567
|
this.log.info("empty remote initialised on origin", { branch });
|
|
3091
3568
|
return;
|
|
3092
3569
|
} catch (err) {
|
|
@@ -3095,11 +3572,11 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3095
3572
|
error: redactCredentials(err instanceof Error ? err.message : String(err))
|
|
3096
3573
|
});
|
|
3097
3574
|
}
|
|
3098
|
-
const publishedElsewhere = await this.countRemoteHeads(targetDir,
|
|
3575
|
+
const publishedElsewhere = await this.countRemoteHeads(targetDir, auth, branch);
|
|
3099
3576
|
if (publishedElsewhere === null || publishedElsewhere === 0) return;
|
|
3100
3577
|
try {
|
|
3101
3578
|
this.log.info("origin gained the branch concurrently \u2014 adopting it", { branch });
|
|
3102
|
-
await this.bootstrapFromOrigin(targetDir, branch,
|
|
3579
|
+
await this.bootstrapFromOrigin(targetDir, branch, auth);
|
|
3103
3580
|
} catch (err) {
|
|
3104
3581
|
this.log.warn("adopting the concurrently-created branch failed; keeping local branch", {
|
|
3105
3582
|
branch,
|
|
@@ -3138,22 +3615,19 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3138
3615
|
* `reset --hard`. Files that do not conflict with the remote stay
|
|
3139
3616
|
* exactly where they are.
|
|
3140
3617
|
*
|
|
3141
|
-
*
|
|
3142
|
-
*
|
|
3143
|
-
* after token rotation. This persists the token in `.git/config`
|
|
3144
|
-
* the same way `git clone <authUrl>` does — token-rotation handling
|
|
3145
|
-
* is external to this connector method.
|
|
3618
|
+
* A pre-existing `origin` is replaced with the declared URL, which also
|
|
3619
|
+
* drops a token an older driver wrote into it.
|
|
3146
3620
|
*/
|
|
3147
|
-
async bootstrapFromOrigin(targetDir, branch,
|
|
3621
|
+
async bootstrapFromOrigin(targetDir, branch, auth) {
|
|
3148
3622
|
try {
|
|
3149
3623
|
await git(["remote", "remove", "origin"], targetDir, 5e3);
|
|
3150
3624
|
} catch {
|
|
3151
3625
|
}
|
|
3152
|
-
await git(["remote", "add", "origin",
|
|
3153
|
-
await
|
|
3626
|
+
await git(["remote", "add", "origin", plainRemoteUrl(auth)], targetDir, 5e3);
|
|
3627
|
+
await gitRemote(["fetch", "--depth=1", "origin", branch], targetDir, auth, 12e4);
|
|
3154
3628
|
await this.preserveConflictingUntracked(targetDir, branch);
|
|
3155
3629
|
await git(["checkout", "-B", branch, `origin/${branch}`], targetDir, 3e4);
|
|
3156
|
-
await syncSubmodules(targetDir, this.log);
|
|
3630
|
+
await syncSubmodules(targetDir, this.log, auth);
|
|
3157
3631
|
try {
|
|
3158
3632
|
await git(["branch", `--set-upstream-to=origin/${branch}`, branch], targetDir, 5e3);
|
|
3159
3633
|
} catch {
|
|
@@ -3203,16 +3677,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3203
3677
|
}
|
|
3204
3678
|
}
|
|
3205
3679
|
}
|
|
3206
|
-
// ── Private:
|
|
3207
|
-
authUrl(s) {
|
|
3208
|
-
if (s.token && s.url.startsWith("https://")) {
|
|
3209
|
-
const urlObj = new URL(s.url);
|
|
3210
|
-
urlObj.username = "x-access-token";
|
|
3211
|
-
urlObj.password = s.token;
|
|
3212
|
-
return urlObj.toString();
|
|
3213
|
-
}
|
|
3214
|
-
return s.url;
|
|
3215
|
-
}
|
|
3680
|
+
// ── Private: origin URL ───────────────────────────────────────────────────
|
|
3216
3681
|
/**
|
|
3217
3682
|
* Align `origin` with the exact sanitized URL used by the Tier-2 helper.
|
|
3218
3683
|
* Git does not normalize query/fragment data, path case, or `.git` aliases
|
|
@@ -3230,43 +3695,37 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3230
3695
|
return credentialScopeUrl;
|
|
3231
3696
|
}
|
|
3232
3697
|
/**
|
|
3233
|
-
*
|
|
3234
|
-
*
|
|
3235
|
-
*
|
|
3236
|
-
*
|
|
3237
|
-
*
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
try {
|
|
3242
|
-
await git(["remote", "set-url", "origin", this.authUrl(state)], targetDir, 5e3);
|
|
3243
|
-
} catch (err) {
|
|
3244
|
-
this.log.warn("failed to refresh inline token on origin", {
|
|
3245
|
-
error: err instanceof Error ? err.message : String(err)
|
|
3246
|
-
});
|
|
3247
|
-
}
|
|
3248
|
-
}
|
|
3249
|
-
/**
|
|
3250
|
-
* Tier-1: remove the userinfo from `origin`, so the token baked by
|
|
3251
|
-
* connect/refresh does not stay in `.git/config` after teardown. No-op when
|
|
3252
|
-
* `origin` carries none, or nothing was baked (no token, non-https remote).
|
|
3698
|
+
* Point `origin` at the declared URL, without a credential in it.
|
|
3699
|
+
*
|
|
3700
|
+
* Clones made before the driver authenticated per command hold
|
|
3701
|
+
* `https://x-access-token:<token>@…` in `.git/config`, where the agent can
|
|
3702
|
+
* read it; this is what removes it, on the first connect after an upgrade.
|
|
3703
|
+
* With a token in hand `origin` is set to the declared URL, as the old
|
|
3704
|
+
* re-bake did. Without one only a token-bearing origin is rewritten: a URL
|
|
3705
|
+
* the user declared with their own userinfo is theirs to keep.
|
|
3253
3706
|
*/
|
|
3254
|
-
async
|
|
3255
|
-
if (!state.token || !state.targetDir || !state.url.startsWith("https://")) return;
|
|
3707
|
+
async reconcileRemoteUrl(targetDir, state) {
|
|
3256
3708
|
try {
|
|
3257
|
-
const current = await
|
|
3258
|
-
|
|
3259
|
-
if (
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3709
|
+
const current = await gitOrNull(["remote", "get-url", "origin"], targetDir);
|
|
3710
|
+
let wanted = null;
|
|
3711
|
+
if (state.token && state.url.startsWith("https://")) {
|
|
3712
|
+
wanted = plainRemoteUrl(state);
|
|
3713
|
+
} else if (current?.startsWith("https://")) {
|
|
3714
|
+
const u = new URL(current);
|
|
3715
|
+
if (u.username === TOKEN_USERNAME) {
|
|
3716
|
+
u.username = "";
|
|
3717
|
+
u.password = "";
|
|
3718
|
+
wanted = u.toString();
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
if (wanted === null || wanted === current) return;
|
|
3722
|
+
await git(
|
|
3723
|
+
current === null ? ["remote", "add", "origin", wanted] : ["remote", "set-url", "origin", wanted],
|
|
3724
|
+
targetDir,
|
|
3725
|
+
5e3
|
|
3726
|
+
);
|
|
3268
3727
|
} catch (err) {
|
|
3269
|
-
this.log.warn("failed to
|
|
3728
|
+
this.log.warn("failed to reconcile origin URL", {
|
|
3270
3729
|
error: redactCredentials(err instanceof Error ? err.message : String(err))
|
|
3271
3730
|
});
|
|
3272
3731
|
}
|
|
@@ -3285,7 +3744,9 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3285
3744
|
}
|
|
3286
3745
|
if (s.disposed) return;
|
|
3287
3746
|
s.fetchInFlight = true;
|
|
3288
|
-
void this.
|
|
3747
|
+
void this.withMountLock(s, async () => {
|
|
3748
|
+
if (!s.disposed) await this.runPeriodicFetch(s, mountPath);
|
|
3749
|
+
}).catch((err) => {
|
|
3289
3750
|
this.log.debug("periodic fetch failed", {
|
|
3290
3751
|
error: redactCredentials(err instanceof Error ? err.message : String(err))
|
|
3291
3752
|
});
|
|
@@ -3299,14 +3760,15 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3299
3760
|
await this.fetchOrigin(s, mountPath);
|
|
3300
3761
|
if (s.disposed) return;
|
|
3301
3762
|
if (s.sync.autoRebase && s.activeBranch !== s.branch) {
|
|
3302
|
-
const onOrigin = await this.trackSessionBranch(s.activeBranch, mountPath);
|
|
3763
|
+
const onOrigin = await this.trackSessionBranch(s.activeBranch, mountPath, s);
|
|
3303
3764
|
if (s.disposed) return;
|
|
3304
3765
|
if (onOrigin !== "absent") {
|
|
3305
3766
|
await this.mergeBaseIntoSessionBranch(s, mountPath);
|
|
3306
3767
|
if (s.disposed) return;
|
|
3307
|
-
await syncSubmodules(mountPath, this.log);
|
|
3768
|
+
await syncSubmodules(mountPath, this.log, s);
|
|
3308
3769
|
return;
|
|
3309
3770
|
}
|
|
3771
|
+
if (await this.skipOverUnfinishedOperation(mountPath, "rebase onto base")) return;
|
|
3310
3772
|
try {
|
|
3311
3773
|
await gitCommitting(["rebase", `origin/${s.branch}`], mountPath, 6e4);
|
|
3312
3774
|
} catch (err) {
|
|
@@ -3317,11 +3779,11 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3317
3779
|
await this.resolveConflictsOrAbort(s, mountPath, "rebase");
|
|
3318
3780
|
}
|
|
3319
3781
|
if (s.disposed) return;
|
|
3320
|
-
await syncSubmodules(mountPath, this.log);
|
|
3782
|
+
await syncSubmodules(mountPath, this.log, s);
|
|
3321
3783
|
} else if (s.sync.autoPull) {
|
|
3322
|
-
await
|
|
3784
|
+
await pullByMerge(s.activeBranch, mountPath, s, 6e4);
|
|
3323
3785
|
if (s.disposed) return;
|
|
3324
|
-
await syncSubmodules(mountPath, this.log);
|
|
3786
|
+
await syncSubmodules(mountPath, this.log, s);
|
|
3325
3787
|
}
|
|
3326
3788
|
}
|
|
3327
3789
|
/**
|
|
@@ -3331,11 +3793,12 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3331
3793
|
* refspec fails the whole fetch. Drop it and fetch again. The base branch
|
|
3332
3794
|
* refspec is never dropped.
|
|
3333
3795
|
*/
|
|
3334
|
-
async fetchOrigin(s, mountPath) {
|
|
3796
|
+
async fetchOrigin(s, mountPath, auth = s) {
|
|
3335
3797
|
for (let attempt = 0; ; attempt++) {
|
|
3336
3798
|
try {
|
|
3337
|
-
await
|
|
3799
|
+
await runRemoteGit(["fetch", "origin"], auth, {
|
|
3338
3800
|
cwd: mountPath,
|
|
3801
|
+
safeDir: mountPath,
|
|
3339
3802
|
timeout: 6e4,
|
|
3340
3803
|
env: cLocaleEnv()
|
|
3341
3804
|
});
|
|
@@ -3362,6 +3825,8 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3362
3825
|
async mergeBaseIntoSessionBranch(s, mountPath) {
|
|
3363
3826
|
const baseRef = `origin/${s.branch}`;
|
|
3364
3827
|
if (await isAncestor(baseRef, "HEAD", mountPath)) return;
|
|
3828
|
+
if (await this.skipOverUnfinishedOperation(mountPath, "merge of base into session branch"))
|
|
3829
|
+
return;
|
|
3365
3830
|
try {
|
|
3366
3831
|
await gitCommitting(["merge", "--no-edit", baseRef], mountPath, 6e4);
|
|
3367
3832
|
this.log.info("merged base into published session branch", {
|
|
@@ -3378,6 +3843,17 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3378
3843
|
}
|
|
3379
3844
|
}
|
|
3380
3845
|
// ── Conflict resolution ──────────────────────────────────────────────────
|
|
3846
|
+
/**
|
|
3847
|
+
* True (and logged) when a merge or rebase is already open in the checkout,
|
|
3848
|
+
* so a driver merge must not start: its failure path would abort or resolve
|
|
3849
|
+
* the agent's own operation.
|
|
3850
|
+
*/
|
|
3851
|
+
async skipOverUnfinishedOperation(cwd, what) {
|
|
3852
|
+
const reason = await unfinishedGitOperation(cwd);
|
|
3853
|
+
if (reason === null) return false;
|
|
3854
|
+
this.log.warn(`${what} skipped: the checkout has an unfinished merge or rebase`, { reason });
|
|
3855
|
+
return true;
|
|
3856
|
+
}
|
|
3381
3857
|
/**
|
|
3382
3858
|
* Attempt to resolve merge/rebase conflicts by keeping "ours" and copying
|
|
3383
3859
|
* "theirs" to a .conflict file. If resolution fails entirely, abort.
|
|
@@ -3386,7 +3862,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3386
3862
|
async resolveConflictsOrAbort(s, mountPath, mode) {
|
|
3387
3863
|
try {
|
|
3388
3864
|
const conflicted = (await git(["diff", "--name-only", "--diff-filter=U"], mountPath)).split("\n").filter(Boolean);
|
|
3389
|
-
if (conflicted.length === 0)
|
|
3865
|
+
if (conflicted.length === 0) throw new Error(`${mode} failed without conflicts`);
|
|
3390
3866
|
for (const file of conflicted) {
|
|
3391
3867
|
await this.resolveFileConflict(mountPath, file);
|
|
3392
3868
|
}
|
|
@@ -3482,7 +3958,9 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3482
3958
|
return;
|
|
3483
3959
|
}
|
|
3484
3960
|
s.autoCommitInFlight = true;
|
|
3485
|
-
void this.
|
|
3961
|
+
void this.withMountLock(s, async () => {
|
|
3962
|
+
if (!s.disposed) await this.performAutoCommit(s, mountPath);
|
|
3963
|
+
}).catch((err) => {
|
|
3486
3964
|
this.log.debug("auto-commit failed", {
|
|
3487
3965
|
error: err instanceof Error ? err.message : String(err)
|
|
3488
3966
|
});
|
|
@@ -3493,9 +3971,10 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3493
3971
|
}
|
|
3494
3972
|
async performAutoCommit(s, mountPath) {
|
|
3495
3973
|
try {
|
|
3974
|
+
if (await refuseUnfinishedOperation(mountPath, this.log)) return;
|
|
3496
3975
|
const message = await this.generateCommitMessage(mountPath);
|
|
3497
3976
|
if (s.disposed) return;
|
|
3498
|
-
const result = await commitAll(mountPath, message);
|
|
3977
|
+
const result = await commitAll(mountPath, message, this.log);
|
|
3499
3978
|
if (!result || s.disposed) return;
|
|
3500
3979
|
if (s.lifecycle.pushAfterCommit) {
|
|
3501
3980
|
try {
|
|
@@ -3508,7 +3987,7 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3508
3987
|
}
|
|
3509
3988
|
async generateCommitMessage(mountPath) {
|
|
3510
3989
|
try {
|
|
3511
|
-
await
|
|
3990
|
+
await stageAll(mountPath, this.log);
|
|
3512
3991
|
const stat5 = await git(["diff", "--cached", "--stat"], mountPath);
|
|
3513
3992
|
const lines = stat5.split("\n").filter((l) => l.includes("|"));
|
|
3514
3993
|
if (lines.length === 0) return "skaile: auto-save";
|
|
@@ -3523,7 +4002,13 @@ var GitConnector = class extends AbstractConnector {
|
|
|
3523
4002
|
function createConnector5() {
|
|
3524
4003
|
return new GitConnector();
|
|
3525
4004
|
}
|
|
3526
|
-
var __test = {
|
|
4005
|
+
var __test = {
|
|
4006
|
+
resolveGitConfig,
|
|
4007
|
+
networkGitEnv,
|
|
4008
|
+
plainRemoteUrl,
|
|
4009
|
+
runRemoteGit,
|
|
4010
|
+
comparableRemoteUrl
|
|
4011
|
+
};
|
|
3527
4012
|
|
|
3528
4013
|
// factory-assets/connectors/gmail/adapter.ts
|
|
3529
4014
|
var GmailConnector = class extends AbstractConnector {
|
|
@@ -8264,6 +8749,20 @@ async function buildSdkConnectorTools(manager, options) {
|
|
|
8264
8749
|
tools: withActivityLabelDefs(buildConnectorToolDefs(manager, options), options?.z ?? zStatic)
|
|
8265
8750
|
});
|
|
8266
8751
|
}
|
|
8752
|
+
var GIT_REMOTE_TOOLS = [
|
|
8753
|
+
[
|
|
8754
|
+
"push",
|
|
8755
|
+
"Push a git mount's branch to origin with the mount's credential. Commit with the git CLI first; only commits are pushed. Never forces: a refused push returns status 'rejected' \u2014 run git_pull, then push again. Returns JSON {status, message, sync}."
|
|
8756
|
+
],
|
|
8757
|
+
[
|
|
8758
|
+
"pull",
|
|
8759
|
+
"Fetch origin and merge it into a git mount's branch with the mount's credential (a merge, never a rebase). On a conflict the merge is aborted, the checkout is left as it was, and status 'conflict' lists the files; merge origin/<branch> locally to resolve. Submodules are not fetched: a moved submodule updates at the next platform sync, and the message names it. Returns JSON {status, message, conflicts?, sync}."
|
|
8760
|
+
],
|
|
8761
|
+
[
|
|
8762
|
+
"fetch",
|
|
8763
|
+
"Fetch origin for a git mount with the mount's credential. Updates origin/<branch> refs only; the working tree is not touched. Returns JSON {status, sync}."
|
|
8764
|
+
]
|
|
8765
|
+
];
|
|
8267
8766
|
function buildConnectorToolDefs(manager, options) {
|
|
8268
8767
|
const shellAccess = options?.shellAccess ?? true;
|
|
8269
8768
|
const z5 = options?.z ?? zStatic;
|
|
@@ -8412,6 +8911,31 @@ function buildConnectorToolDefs(manager, options) {
|
|
|
8412
8911
|
}
|
|
8413
8912
|
}
|
|
8414
8913
|
});
|
|
8914
|
+
if (manager.listGitConnectors().length > 0) {
|
|
8915
|
+
for (const [op, what] of GIT_REMOTE_TOOLS) {
|
|
8916
|
+
tools.push({
|
|
8917
|
+
name: `git_${op}`,
|
|
8918
|
+
description: what,
|
|
8919
|
+
inputSchema: {
|
|
8920
|
+
connector_id: z5.string().describe("ID of the git mount (see the Mounts table)")
|
|
8921
|
+
},
|
|
8922
|
+
handler: async ({ connector_id }) => {
|
|
8923
|
+
try {
|
|
8924
|
+
if (manager.get(connector_id).connector.name !== "git") {
|
|
8925
|
+
throw new Error(`connector "${connector_id}" is not a git mount`);
|
|
8926
|
+
}
|
|
8927
|
+
const result = await manager.executeOp(connector_id, op, {});
|
|
8928
|
+
return { content: [{ type: "text", text: result }] };
|
|
8929
|
+
} catch (err) {
|
|
8930
|
+
return {
|
|
8931
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
8932
|
+
isError: true
|
|
8933
|
+
};
|
|
8934
|
+
}
|
|
8935
|
+
}
|
|
8936
|
+
});
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
8415
8939
|
if (!shellAccess) {
|
|
8416
8940
|
tools.push({
|
|
8417
8941
|
name: "connector_exec",
|
|
@@ -8656,13 +9180,13 @@ function appendGitAuthNotes(lines, manager, fsConnectors) {
|
|
|
8656
9180
|
const hosts = [...new Set(tier2.map((m) => safeHost(m.source)))].filter(Boolean);
|
|
8657
9181
|
lines.push("");
|
|
8658
9182
|
lines.push(
|
|
8659
|
-
`> **Git auth (CLI):** the git CLI is authenticated for the duration of this session for ${hosts.map((h) => `\`${h}\``).join(", ")}. Use \`git push\` / \`git pull\` directly.`
|
|
9183
|
+
`> **Git auth (CLI):** the git CLI is authenticated for the duration of this session for ${hosts.map((h) => `\`${h}\``).join(", ")} (mounts ${idList(tier2)}). Use \`git push\` / \`git pull\` directly, or the \`git_push\` / \`git_pull\` / \`git_fetch\` tools.`
|
|
8660
9184
|
);
|
|
8661
9185
|
}
|
|
8662
9186
|
if (tier1.length > 0) {
|
|
8663
9187
|
lines.push("");
|
|
8664
9188
|
lines.push(
|
|
8665
|
-
|
|
9189
|
+
`> **Git auth (mount-managed):** on mounts ${idList(tier1)} the git CLI has no credential, so a network git command (\`git push\`, \`git pull\`, \`git fetch\`) fails with "could not read Username" \u2014 do not retry it. Commit with the git CLI as usual, then push and pull with the \`git_push\`, \`git_pull\` and \`git_fetch\` tools (\`connector_id\` = the mount id; where only \`connector_exec\` is offered, its \`push\` / \`pull\` / \`fetch\` operations), which run with the mount's credential. They act on the mount's own branch only and never force; a rejected push means \`git_pull\` first. \`git_pull\` does not fetch submodules; they update at the next platform sync. \`gh\` and pull requests are not available on these mounts: they need the user to enable "Allow agent to use git CLI directly" on the mount.`
|
|
8666
9190
|
);
|
|
8667
9191
|
}
|
|
8668
9192
|
}
|
|
@@ -8711,6 +9235,9 @@ function buildToolSection(manager, toolConnectors, shellAccess) {
|
|
|
8711
9235
|
}
|
|
8712
9236
|
return lines.join("\n");
|
|
8713
9237
|
}
|
|
9238
|
+
function idList(mounts) {
|
|
9239
|
+
return mounts.map((m) => `\`${m.id}\``).join(", ");
|
|
9240
|
+
}
|
|
8714
9241
|
function safeHost(source) {
|
|
8715
9242
|
try {
|
|
8716
9243
|
return new URL(source).host;
|
|
@@ -12043,5 +12570,5 @@ function describeFlowOperations() {
|
|
|
12043
12570
|
}
|
|
12044
12571
|
|
|
12045
12572
|
export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, BoxConnector, ClassifierSubpromptRunner, ConnectorManager, DeployConnector, DevServerConnector, FlowAdapter, GitConnector, GmailConnector, GoogleDriveConnector, LocalConnector, LogBuffer, MAX_REARM_MS, MIN_REFRESH_DELAY_MS, ManagedGitconfigCollisionError, MattermostConnector, MemoryConnector, MinIOConnector, NO_ACTIVE_FLOW_ERROR, PortPool, PostgresConnector, REFRESH_LEAD_MS, S3Connector, S3FetchStoppedError, S3PresignedRequestError, S3_PRESIGN_BATCH, SQLiteConnector, SessionInlineSubFlowRunner, SessionNodeExecutor, SessionSubpromptRunner, SharePointConnector, StaticServerConnector, TunnelConnector, WebDAVConnector, XStateConnector, XStateStoreConnector, __test, atomicReplaceCredential, buildConnectorPromptSection, buildConnectorToolDefs, buildFlowPromptEvent, buildFlowToolDefs, buildLazyFlowToolDefs, buildLazySdkFlowTools, buildSdkConnectorTools, buildSdkFlowTools, checkFleetMountLiveness, classifierQuestion, classifyConnectError, createConnector, createConnector10, createConnector11, createConnector12, createConnector13, createConnector14, createConnector15, createConnector16, createConnector17, createConnector18, createConnector19, createConnector2, createConnector20, createConnector3, createConnector4, createConnector5, createConnector6, createConnector7, createConnector8, createConnector9, createFsWatcher, createWorktree, defaultSpawn, describeFlowOperations, driveOrchestratorTurn, ensureDirMode, ensureFleetMounted, ensureRefreshAttemptStamp, findGitRoot, findMissingPackages, getConnector, installNpmPackages, isFleetManaged, isPackageResolvable, listConnectors, listWorktrees, parseListObjectsV2, presignScopePrefix, presignableKey, readGitSyncStatus, registerBuiltinConnectors, removeMountBlock, removeUnavailableMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock, writeUnavailableMountBlock };
|
|
12046
|
-
//# sourceMappingURL=chunk-
|
|
12047
|
-
//# sourceMappingURL=chunk-
|
|
12573
|
+
//# sourceMappingURL=chunk-EZOFQW3G.js.map
|
|
12574
|
+
//# sourceMappingURL=chunk-EZOFQW3G.js.map
|