Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .mise-tasks/git/worksync.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash

#MISE dir="{{ config_root }}"
#MISE alias="gws"
#MISE description="Sync an existing worktree with main: re-runs port remapping (preserving assigned ports, adding new dependents) and applies pending database migrations. Safe to run repeatedly."

#USAGE flag "--no-migrate" help="Skip applying database migrations."

set -e

main_worktree=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
current_worktree=$(git rev-parse --show-toplevel)

if [ -z "$main_worktree" ] || [ "$main_worktree" = "$current_worktree" ]; then
echo "Error: this task must be run from a git worktree, not the main working tree."
exit 1
fi

if [ ! -f "mise.local.toml" ]; then
echo "Error: mise.local.toml not found. Initialize this worktree first with 'mise gwi'."
exit 1
fi

echo "⏳ Syncing port mappings..."
added=0
remap=$(mise run zero:remap-ports --preserve --format flat --file -)
for line in $remap; do
if [ -z "$line" ]; then continue; fi
key="${line%%=*}"
mise set --file mise.local.toml "$line"
echo " + ${key}"
added=$((added + 1))
done

if [ "$added" -eq 0 ]; then
echo "✅ Port mappings already in sync."
else
echo "✅ Added ${added} env var declaration(s) to mise.local.toml."
fi

if [ "${usage_no_migrate:-false}" = "true" ]; then
echo
echo "ℹ️ Skipping database migrations (--no-migrate)."
exit 0
fi

echo
echo "⏳ Applying Postgres migrations..."
mise run db:migrate

echo
echo "⏳ Applying ClickHouse migrations..."
mise run clickhouse:migrate

echo
echo "✅ Worktree synced."
55 changes: 46 additions & 9 deletions .mise-tasks/zero/remap-ports.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

//USAGE flag "--format <format>" default="mise" { choices "mise" "flat" }
//USAGE flag "--file <file>" default="mise.worktree.local.toml" help="The file to write the environment variables to. If set to '-', the output will be written to stdout."
//USAGE flag "--preserve" help="Preserve existing port assignments and dependent declarations already present in mise.local.toml. Only emit newly-introduced ports (randomized) and newly-introduced dependent declarations."

/**
* This script is responsible for finding available ports for any environment
Expand All @@ -15,6 +16,13 @@
* environment variables that depend on the `_PORT` variables will also need to
* be picked up and redeclared since env var declarations are sensitive to
* config loading precedence and order dependent within each config file.
*
* When `--preserve` is set the script reads `mise.local.toml` and skips any
* `_PORT` or dependent declaration that already has a value there. This is
* what `mise git:worksync` (alias `gws`) uses to bring an existing worktree
* up to date with new ports / dependents added on `main` without
* re-randomizing ports that are already assigned and without clobbering
* manual edits the user may have made to dependent values.
*/

import { readFileSync, writeFileSync } from "node:fs";
Expand All @@ -26,23 +34,52 @@ async function main() {
env: Record<string, string>;
};

const preserve = process.env["usage_preserve"] === "true";

let existing: Record<string, string> = {};
if (preserve) {
try {
const localConfig = parseTOML(
await readFileSync("mise.local.toml", "utf-8"),
) as { env?: Record<string, string> };
existing = localConfig.env ?? {};
} catch {
// mise.local.toml is missing — treat as empty and emit everything.
}
}

const portEnvVars = Object.keys(config.env).filter((key) =>
key.endsWith("_PORT"),
);

const finalVars: [string, string][] = [];
const emitted = new Map<string, string>();
const emit = (key: string, value: string) => {
// delete-then-set moves the key to the end of insertion order, matching
// the unset+set semantics of `mise set` so dependents end up after the
// latest port they reference.
emitted.delete(key);
emitted.set(key, value);
};

for (const portEnvVar of portEnvVars) {
const port = await getPort({
name: portEnvVar,
random: true,
});
finalVars.push(
[portEnvVar, `${port}`],
...findDependentEnvVars(config.env, portEnvVar),
);
if (preserve && portEnvVar in existing) {
// Port is already assigned in mise.local.toml — keep it.
} else {
const port = await getPort({
name: portEnvVar,
random: true,
});
emit(portEnvVar, `${port}`);
}

for (const [key, value] of findDependentEnvVars(config.env, portEnvVar)) {
if (preserve && key in existing) continue;
emit(key, value);
}
}

const finalVars = Array.from(emitted.entries());

const format = process.env["usage_format"] ?? "mise";
let out = "";
switch (format) {
Expand Down
13 changes: 13 additions & 0 deletions zero
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ if [ -f "mise.worktree.local.toml" ]; then
export MISE_ENV=worktree
fi

# Detect git worktree (a working tree distinct from the main one). When in a
# worktree, sync port mappings against mise.toml so newly-added _PORT vars
# and dependents land in mise.local.toml. Migrations are skipped here because
# ./zero applies them later.
if command -v git &> /dev/null && git rev-parse --is-inside-work-tree &> /dev/null; then
git_main_worktree=$(cd "$(git rev-parse --git-common-dir)/.." && pwd 2>/dev/null || true)
git_current_worktree=$(git rev-parse --show-toplevel 2>/dev/null || true)
if [ -n "$git_main_worktree" ] && [ -n "$git_current_worktree" ] && [ "$git_main_worktree" != "$git_current_worktree" ]; then
echo -e "\n⏳ Syncing worktree port mappings"
mise run git:worksync --no-migrate
fi
fi

check_command() {
local cmd=$1
local instructions=$2
Expand Down
Loading