diff --git a/packages/lib/src/core/templates-entrypoint.ts b/packages/lib/src/core/templates-entrypoint.ts index 8c13276..aeaea8b 100644 --- a/packages/lib/src/core/templates-entrypoint.ts +++ b/packages/lib/src/core/templates-entrypoint.ts @@ -11,7 +11,6 @@ import { renderEntrypointZshShell, renderEntrypointZshUserRc } from "./templates-entrypoint/base.js" -import { renderEntrypointDnsRepair } from "./templates-entrypoint/dns-repair.js" import { renderEntrypointClaudeConfig } from "./templates-entrypoint/claude.js" import { renderEntrypointAgentsNotice, @@ -20,6 +19,7 @@ import { renderEntrypointCodexSharedAuth, renderEntrypointMcpPlaywright } from "./templates-entrypoint/codex.js" +import { renderEntrypointDnsRepair } from "./templates-entrypoint/dns-repair.js" import { renderEntrypointGeminiConfig } from "./templates-entrypoint/gemini.js" import { renderEntrypointGitConfig, renderEntrypointGitHooks } from "./templates-entrypoint/git.js" import { renderEntrypointDockerGitBootstrap } from "./templates-entrypoint/nested-docker-git.js" diff --git a/packages/lib/src/core/templates-entrypoint/dns-repair.ts b/packages/lib/src/core/templates-entrypoint/dns-repair.ts index b4a44ea..d5e52b8 100644 --- a/packages/lib/src/core/templates-entrypoint/dns-repair.ts +++ b/packages/lib/src/core/templates-entrypoint/dns-repair.ts @@ -10,7 +10,7 @@ // INVARIANT: after execution, at least one nameserver in /etc/resolv.conf resolves external domains // COMPLEXITY: O(1) per probe attempt, O(max_attempts) worst case export const renderEntrypointDnsRepair = (): string => - `# 0) Ensure DNS resolution works; repair /etc/resolv.conf if Docker DNS is broken + String.raw`# 0) Ensure DNS resolution works; repair /etc/resolv.conf if Docker DNS is broken docker_git_repair_dns() { local test_domain="github.com" local resolv="/etc/resolv.conf" @@ -32,7 +32,7 @@ docker_git_repair_dns() { if [[ "$has_external" -eq 0 ]]; then for ns in $fallback_dns; do - printf "nameserver %s\\n" "$ns" >> "$resolv" + printf "nameserver %s\n" "$ns" >> "$resolv" done echo "[dns-repair] appended fallback nameservers to $resolv" fi diff --git a/packages/lib/src/shell/files.ts b/packages/lib/src/shell/files.ts index fbf34c2..b4a30c9 100644 --- a/packages/lib/src/shell/files.ts +++ b/packages/lib/src/shell/files.ts @@ -3,8 +3,8 @@ import type * as FileSystem from "@effect/platform/FileSystem" import type * as Path from "@effect/platform/Path" import { Effect, Match } from "effect" -import { type TemplateConfig } from "../core/domain.js" import { dockerGitScriptNames } from "../core/docker-git-scripts.js" +import { type TemplateConfig } from "../core/domain.js" import { resolveComposeResourceLimits, withDefaultResourceLimitIntent } from "../core/resource-limits.js" import { type FileSpec, planFiles } from "../core/templates.js" import { FileExistsError } from "./errors.js" diff --git a/packages/lib/src/usecases/projects-apply-all.ts b/packages/lib/src/usecases/projects-apply-all.ts index ed4628c..d62d2e0 100644 --- a/packages/lib/src/usecases/projects-apply-all.ts +++ b/packages/lib/src/usecases/projects-apply-all.ts @@ -37,7 +37,9 @@ export const applyAllDockerGitProjects: Effect.Effect< runDockerComposeUpWithPortCheck(status.projectDir).pipe( Effect.catchTag("DockerCommandError", (error: DockerCommandError) => Effect.logWarning( - `apply failed for ${status.projectDir}: ${renderError(error)}. Check the project docker-compose config (e.g. env files for merge conflicts, port conflicts in docker-compose.yml config) and retry.` + `apply failed for ${status.projectDir}: ${ + renderError(error) + }. Check the project docker-compose config (e.g. env files for merge conflicts, port conflicts in docker-compose.yml config) and retry.` )), Effect.catchTag("ConfigNotFoundError", (error) => Effect.logWarning( diff --git a/packages/lib/src/usecases/projects.ts b/packages/lib/src/usecases/projects.ts index 3f53ce4..9dd02de 100644 --- a/packages/lib/src/usecases/projects.ts +++ b/packages/lib/src/usecases/projects.ts @@ -1,3 +1,4 @@ +export { applyAllDockerGitProjects } from "./projects-apply-all.js" export { buildSshCommand, loadProjectItem, @@ -7,7 +8,6 @@ export { type ProjectLoadError, type ProjectStatus } from "./projects-core.js" -export { applyAllDockerGitProjects } from "./projects-apply-all.js" export { deleteDockerGitProject } from "./projects-delete.js" export { downAllDockerGitProjects } from "./projects-down.js" export { listProjectItems, listProjects, listProjectSummaries, listRunningProjectItems } from "./projects-list.js" diff --git a/packages/lib/src/usecases/state-repo.ts b/packages/lib/src/usecases/state-repo.ts index 6c0b202..5a9ad7b 100644 --- a/packages/lib/src/usecases/state-repo.ts +++ b/packages/lib/src/usecases/state-repo.ts @@ -161,7 +161,15 @@ export const autoPullState: Effect.Effect = Effect.ge if (!enabled) { return } - yield* _(statePullInternal(root)) + // CHANGE: abort any in-progress rebase if pull fails to prevent conflict markers + // WHY: if git pull --rebase fails (e.g. due to merge commits), git leaves the repo + // in a conflicted state with conflict markers; rebase --abort restores clean state + // PURITY: SHELL + yield* _( + statePullInternal(root).pipe( + Effect.tapError(() => git(root, ["rebase", "--abort"], gitBaseEnv).pipe(Effect.orElse(() => Effect.void))) + ) + ) }).pipe( Effect.matchEffect({ onFailure: (error) => Effect.logWarning(`State auto-pull failed: ${String(error)}`), @@ -187,9 +195,21 @@ const statePullInternal = ( ) const originUrl = yield* _(normalizeOriginUrlIfNeeded(root, rawOriginUrl)) const token = yield* _(resolveGithubToken(fs, path, root)) + // CHANGE: resolve current branch and pass origin explicitly + // WHY: bare `git pull --rebase` can fail or pull the wrong branch in some git configurations + // QUOTE(ТЗ): "Сделай что бы правильные параметры передавались" + // REF: issue-181 + // PURITY: SHELL + const branchRaw = yield* _( + gitCapture(root, ["rev-parse", "--abbrev-ref", "HEAD"], gitBaseEnv).pipe( + Effect.map((value) => value.trim()), + Effect.orElse(() => Effect.succeed("main")) + ) + ) + const branch = branchRaw === "HEAD" ? "main" : branchRaw const effect = token && token.length > 0 && isGithubHttpsRemote(originUrl) - ? withGithubAskpassEnv(token, (env) => git(root, ["pull", "--rebase"], env)) - : git(root, ["pull", "--rebase"], gitBaseEnv) + ? withGithubAskpassEnv(token, (env) => git(root, ["pull", "--rebase", "origin", branch], env)) + : git(root, ["pull", "--rebase", "origin", branch], gitBaseEnv) yield* _(effect) }).pipe(Effect.asVoid) diff --git a/packages/lib/src/usecases/state-repo/pull-push.ts b/packages/lib/src/usecases/state-repo/pull-push.ts index 062df8b..b8d5e5a 100644 --- a/packages/lib/src/usecases/state-repo/pull-push.ts +++ b/packages/lib/src/usecases/state-repo/pull-push.ts @@ -25,9 +25,22 @@ export const statePull: Effect.Effect< return } const auth = yield* _(resolveStateGithubContext(fs, path, root)) + // CHANGE: resolve current branch and pass origin explicitly + // WHY: bare `git pull --rebase` can fail or pull the wrong branch in some git configurations + // QUOTE(ТЗ): "Сделай что бы правильные параметры передавались" + // REF: issue-181 + // PURITY: SHELL + const branchRaw = yield* _( + pipe( + gitCapture(root, ["rev-parse", "--abbrev-ref", "HEAD"], gitBaseEnv), + Effect.map((value) => value.trim()), + Effect.orElse(() => Effect.succeed("main")) + ) + ) + const branch = branchRaw === "HEAD" ? "main" : branchRaw const effect = auth.token && auth.token.length > 0 && isGithubHttpsRemote(auth.originUrl) - ? withGithubAskpassEnv(auth.token, (env) => git(root, ["pull", "--rebase"], env)) - : git(root, ["pull", "--rebase"], gitBaseEnv) + ? withGithubAskpassEnv(auth.token, (env) => git(root, ["pull", "--rebase", "origin", branch], env)) + : git(root, ["pull", "--rebase", "origin", branch], gitBaseEnv) yield* _(withGithubAuthHintOnFailure(effect, auth.authHintNeeded)) }).pipe(Effect.asVoid)