Skip to content

Instantly share code, notes, and snippets.

@GuyPaddock
Last active June 3, 2026 23:28
Show Gist options
  • Select an option

  • Save GuyPaddock/86b4390c0da555e4472c3b6acb9c91ec to your computer and use it in GitHub Desktop.

Select an option

Save GuyPaddock/86b4390c0da555e4472c3b6acb9c91ec to your computer and use it in GitHub Desktop.
Two-part Script for Rewriting Issue Tags in Commit Summary Lines of a Branch from One Issue (e.g., Jira issue `[ABC-123]`) to Another (e.g., Jira issue `[DEF-456]`)
#!/usr/bin/env bash
set -euo pipefail
# Change the issue tag(s) cited in commits through a rebase starting at the given start commit.
# - Replaces specific issue tags in the subject line (e.g., ABC-176 -> PROJ-96).
# - Optionally prepends a default tag if no issue tag is detected in the subject.
# - Scope: only the commits in <start>..[branch]. No git-filter-repo used.
#
# Usage:
# git remap-issue-tags <start-commit> [branch]
# --map SRC=DST # may be repeated (SRC/DST with or without brackets)
# [--default TAG] # if no tag detected, prepend [TAG]
# [--dry-run] # preview OLD -> NEW subjects; no history rewrite
# [--bulk-edit] # open a SHA-keyed plan file to batch-edit NEW subjects
# [--keep-plan] # do NOT delete the plan file after run
# [--no-verify-hooks] # bypass commit-msg hooks during amend (default)
# [--verify-hooks] # run commit-msg hooks during amend
# [--help]
#
# Examples:
# git remap-issue-tags ace0fba5e \
# --map ABC-176=PROJ-96 \
# --map ABC-180=PROJ-58 \
# --default PROJ-96
#
# git remap-issue-tags ace0fba5e \
# feature/x \
# --map [ABC-180]=[PROJ-58]
#
# Notes:
# - Recognized issue tags look like [PROJ-123] where PROJ is A-Z then A-Z0-9+.
# - Only the *subject line* is edited; commit message bodies are unchanged.
# - Preserves fixup!/squash! prefixes.
# - Bulk-edit plan (block format only):
# <sha>
# OLD: <original subject>
# NEW: <editable subject>
print_usage() {
awk '
NR==1 && /^#!/ { next } # skip shebang
{ sub(/\r$/, "") } # normalize CRLF
in_header == 0 && /^#/ { in_header = 1 }
in_header == 1 {
if (/^#/) { line = $0; sub(/^# ?/, "", line); print line; next }
if (/^[[:space:]]*$/) { next }
exit 0
}
' "$0"
}
error() {
echo "ERROR: $*" >&2
}
error_and_usage() {
local msg="$1"
local code="${2:-2}"
error "$msg"
echo >&2
print_usage
exit "$code"
}
# --- Parse args ---
start=""
branch=""
maps=()
default_tag=""
no_verify=1
dry_run=0
bulk_edit=0
keep_plan=0
while [[ $# -gt 0 ]]; do
case "$1" in
--map)
shift
if [[ $# -lt 1 ]]; then
error "--map requires an argument"
exit 2
fi
maps+=("$1")
;;
--default)
shift
if [[ $# -lt 1 ]]; then
error "--default requires an argument"
exit 2
fi
default_tag="$1"
;;
--dry-run)
dry_run=1
;;
--bulk-edit)
bulk_edit=1
;;
--keep-plan)
keep_plan=1
;;
--no-verify-hooks)
no_verify=1
;;
--verify-hooks)
no_verify=0
;;
--help|-h)
print_usage
exit 0
;;
-*)
error_and_usage "Unknown option: $1" 2
;;
*)
if [[ -z "$start" ]]; then
start="$1"
elif [[ -z "$branch" ]]; then
branch="$1"
else
error_and_usage "Unexpected positional arg: $1" 2
fi
;;
esac
shift
done
if [[ -z "$start" ]]; then
error_and_usage "<start-commit> is required." 2
fi
if [[ -z "$branch" ]]; then
branch="$(git rev-parse --abbrev-ref HEAD)"
fi
# --- Preconditions ---
if ! command -v git >/dev/null; then
error "git not found"
exit 1
fi
declare -a pycmd=()
pick_python() {
if command -v python3 >/dev/null 2>&1; then
pycmd=(python3)
return 0
fi
if command -v py >/dev/null 2>&1; then
if py -3 -c "import sys; sys.exit(0)" >/dev/null 2>&1; then
pycmd=(py -3)
return 0
fi
fi
if command -v wsl >/dev/null 2>&1 && wsl which python3 >/dev/null 2>&1; then
pycmd=(wsl python3)
return 0
fi
return 1
}
if ! pick_python; then
error "Python 3 not found. Install python3, or ensure 'py -3' or 'wsl python3' is available in PATH."
exit 1
fi
pycmd_str=$(printf '%q ' "${pycmd[@]}")
pycmd_str=${pycmd_str% }
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
error "Not inside a Git repo"
exit 1
fi
if ! git rev-parse --verify "$start" >/dev/null 2>&1; then
error "Start commit '$start' not found"
exit 1
fi
if ! git rev-parse --verify "$branch" >/dev/null 2>&1; then
error "Branch '$branch' not found"
exit 1
fi
if ! git merge-base --is-ancestor "$start" "$branch"; then
error "$start is not an ancestor of $branch"
exit 1
fi
if ! git diff-index --quiet HEAD --; then
error "Working tree not clean. Commit or stash changes first."
exit 1
fi
# --- Backup ---
backup_ref="refs/backup/${branch}-rewrite-$(date +%Y%m%d%H%M%S)"
git update-ref "$backup_ref" "$(git rev-parse "$branch")"
echo "Backup created at $backup_ref"
# --- Paths/state under .git (persist across rebase --continue) ---
gitdir="$(git rev-parse --git-dir)"
stamp="$(date +%s)"
step_script="$gitdir/rewrite-subject-during-rebase.$stamp.py"
mappings_file="$gitdir/remap-mappings.$stamp.list"
env_file="$gitdir/remap-env.$stamp.vars"
queue_file="$gitdir/remap-queue.$stamp.list"
plan_file=""
# --- Gentle housekeeping: prune artifacts older than 14 days ---
if command -v find >/dev/null 2>&1; then
find "$gitdir" -maxdepth 1 -type f \
\( -name 'subject-remap-plan.*.txt' \
-o -name 'rewrite-subject-during-rebase.*.py' \
-o -name 'remap-*.vars' \
-o -name 'remap-*.list' \) \
-mtime +14 -print -delete >/dev/null 2>&1 || true
fi
# --- Write mappings file ---
if ((${#maps[@]})); then
printf '%s\n' "${maps[@]}" >"$mappings_file"
else
: >"$mappings_file"
fi
# --- Write env file (UPPERCASE keys by design) ---
{
echo "DEFAULT_TAG=${default_tag}"
if [[ $no_verify -eq 1 ]]; then
echo "AMEND_VERIFY_FLAG=--no-verify"
else
echo "AMEND_VERIFY_FLAG="
fi
} >"$env_file"
# --- Build queue of original non-merge SHAs in pick order (first-parent only) ---
"
# --- Create helper script (plan generation & rebase step) ---
cat >"$step_script" <<'PY'
#!/usr/bin/env python3
import argparse
import os
import re
import subprocess
import sys
import tempfile
def sh(args):
return subprocess.run(
args, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
).stdout
def read_kv(path):
d = {}
if not path or not os.path.exists(path):
return d
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.rstrip("\r\n")
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
d[k.strip()] = v
return d
def read_lines(path):
if not path or not os.path.exists(path):
return []
with open(path, "r", encoding="utf-8") as f:
return [ln.rstrip("\n") for ln in f]
def transform_subject(core, mappings_lines, default_tag):
for line in mappings_lines:
s = line.strip()
if not s or "=" not in s:
continue
src, dst = s.split("=", 1)
src = src.strip()
dst = dst.strip()
if src.startswith("[") and src.endswith("]"):
src = src[1:-1]
if dst.startswith("[") and dst.endswith("]"):
dst = dst[1:-1]
core = core.replace(f"[{src}]", f"[{dst}]")
if default_tag:
dt = default_tag
if dt.startswith("[") and dt.endswith("]"):
dt = dt[1:-1]
if not re.search(r"\[[A-Z][A-Z0-9]+-\d+\]", core):
core = f"[{dt}] {core}".strip()
return core
def current_subject_and_body():
msg = sh(["git", "log", "-1", "--pretty=%B"])
lines = msg.splitlines()
subject = lines[0] if lines else ""
body = "\n".join(lines[1:]) if len(lines) > 1 else ""
return subject, body
def emit_plan(rev_range, mappings_lines, default_tag):
out = sh(
[
"git", "log", "-z", "--reverse",
"--first-parent", "--no-merges",
"--pretty=format:%H%x00%s",
rev_range,
]
)
toks = out.split("\x00")
for i in range(0, len(toks) - 1, 2):
sha = toks[i]
if not sha:
continue
subject = toks[i + 1]
m = re.match(r"^(?:fixup|squash)!\s+", subject)
prefix = m.group(0) if m else ""
core = subject[len(prefix):] if m else subject
new = prefix + transform_subject(core, mappings_lines, default_tag)
subject = subject.replace("\r", " ")
new = new.replace("\r", " ")
print(sha)
print(f"OLD: {subject}")
print(f"NEW: {new}\n")
def plan_lookup(plan_path, sha):
if not plan_path or not os.path.exists(plan_path):
return None
new_map = {}
cur_sha = None
with open(plan_path, "r", encoding="utf-8") as f:
for raw in f:
line = raw.rstrip("\r\n")
if not line or line.lstrip().startswith("#"):
continue
m_sha = re.match(r"^([0-9a-f]{7,40})\s*$", line)
if m_sha:
cur_sha = m_sha.group(1)
continue
if cur_sha:
m_new = re.match(r"^NEW:\s*(.*)$", line)
if m_new:
new_map[cur_sha] = m_new.group(1).strip()
continue
v = new_map.get(sha)
if v:
return v
return None
def find_gitdir():
try:
out = subprocess.run(
["git", "rev-parse", "--git-dir"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
).stdout.strip()
return out or ".git"
except Exception:
return ".git"
def find_rebase_dir(gitdir):
for d in ("rebase-merge", "rebase-apply"):
path = os.path.join(gitdir, d)
if os.path.isdir(path):
return path
return None
_PICK_CMDS = ("pick", "reword", "edit", "fixup", "squash")
def last_pick_sha(rebase_dir):
if not rebase_dir:
return None
done = os.path.join(rebase_dir, "done")
try:
with open(done, "r", encoding="utf-8") as f:
lines = [ln.rstrip("\r\n") for ln in f]
except FileNotFoundError:
return None
i = len(lines) - 1
while i >= 0 and lines[i].startswith("exec "):
i -= 1
if i < 0:
return None
m = re.match(r"^([a-z]+)\s+([0-9a-f]{7,40})\b", lines[i])
if not m:
return None
cmd, sha = m.group(1), m.group(2)
if cmd in _PICK_CMDS:
return sha
return None
def main():
ap = argparse.ArgumentParser(add_help=False)
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--emit-plan", action="store_true")
ap.add_argument("--range", required=False)
ap.add_argument("--env", required=False)
ap.add_argument("--mappings", required=False)
ap.add_argument("--plan", required=False)
ap.add_argument("--queue", required=False) # accepted; not used
ap.add_argument("--sha", required=False)
ap.add_argument("--subject", required=False)
args = ap.parse_args()
env = read_kv(args.env) if args.env else {}
mappings_lines = read_lines(args.mappings)
default_tag = env.get("DEFAULT_TAG", "")
if args.emit_plan:
if not args.range:
print("ERROR: --emit-plan requires --range", file=sys.stderr)
sys.exit(2)
emit_plan(args.range, mappings_lines, default_tag)
return
if args.dry_run:
subject = args.subject or ""
sha = args.sha or ""
override = plan_lookup(args.plan, sha)
if override is not None:
print(override)
return
m = re.match(r"^(?:fixup|squash)!\s+", subject)
prefix = m.group(0) if m else ""
core = subject[len(prefix):] if m else subject
new_subject = prefix + transform_subject(core, mappings_lines, default_tag)
print(new_subject)
return
# Mutating mode during rebase: act ONLY after pick-like steps and bind to that SHA.
subject, body = current_subject_and_body()
gitdir = find_gitdir()
rebase_dir = find_rebase_dir(gitdir)
sha = last_pick_sha(rebase_dir)
if not sha:
return
new_subject = plan_lookup(args.plan, sha)
if new_subject is None:
m = re.match(r"^(?:fixup|squash)!\s+", subject)
prefix = m.group(0) if m else ""
core = subject[len(prefix):] if m else subject
new_subject = prefix + transform_subject(core, mappings_lines, default_tag)
if new_subject == subject:
return
new_msg = new_subject + ("\n" + body if body else "")
with tempfile.NamedTemporaryFile("w", delete=False, encoding="utf-8") as tf:
tf.write(new_msg)
path = tf.name
try:
amend_flag = env.get("AMEND_VERIFY_FLAG", "")
cmd = ["git", "commit", "--amend", "-F", path]
if amend_flag:
cmd.append(amend_flag)
subprocess.run(cmd, check=True)
finally:
try:
os.remove(path)
except OSError:
pass
if __name__ == "__main__":
main()
PY
chmod +x "$step_script"
# --- Optional bulk-edit plan (create & open before rebase) ---
if [[ $bulk_edit -eq 1 ]]; then
plan_file="$gitdir/subject-remap-plan.$stamp.txt"
{
echo "# Edit ONLY the 'NEW:' line in each block."
echo "# Blank NEW means: use automatic mapping for that commit."
echo "# Lines starting with '#' are ignored."
echo
} > "$plan_file"
"${pycmd[@]}" "$step_script" --emit-plan --range "$start..$branch" \
--env "$env_file" --mappings "$mappings_file" >> "$plan_file"
editor_cmd="${VISUAL:-${GIT_EDITOR:-${EDITOR:-vi}}}"
sh -c "$editor_cmd \"$plan_file\""
# Normalize CRLF to LF for parsing
if grep -q $'\r' "$plan_file"; then
tmp_norm="$plan_file.norm.$$"
tr -d '\r' < "$plan_file" > "$tmp_norm"
mv "$tmp_norm" "$plan_file"
fi
# Abort if plan is empty (no SHA headers)
if ! LC_ALL=C grep -Eq '^[0-9a-f]{7,40}[[:space:]]*$' "$plan_file"; then
error "Bulk-edit plan is empty; aborting."
if [[ $keep_plan -ne 1 ]]; then
rm -f "$plan_file"
fi
exit 1
fi
# Sanity check: plan SHAs match first-parent pick list exactly (count & order)
mapfile -t fp_arr < <(git rev-list --first-parent --reverse --no-merges "$start..$branch")
mapfile -t plan_arr < <(LC_ALL=C grep -E '^[0-9a-f]{7,40}[[:space:]]*$' "$plan_file")
fp_count="${#fp_arr[@]}"
plan_count="${#plan_arr[@]}"
if [[ "$plan_count" -ne "$fp_count" ]]; then
error "Plan has $plan_count commit blocks but first-parent pick list has $fp_count. Aborting to avoid misapplied edits."
if [[ $keep_plan -ne 1 ]]; then
rm -f "$plan_file"
fi
exit 1
fi
mismatch_index=-1
for ((i = 0; i < fp_count; i++)); do
if [[ "${fp_arr[$i]}" != "${plan_arr[$i]}" ]]; then
mismatch_index="$i"
break
fi
done
if [[ "$mismatch_index" -ge 0 ]]; then
error "Plan SHA at position $((mismatch_index + 1)) does not match first-parent pick list."
echo " Expected: ${fp_arr[$mismatch_index]}" >&2
echo " Found: ${plan_arr[$mismatch_index]}" >&2
if [[ $keep_plan -ne 1 ]]; then
rm -f "$plan_file"
fi
exit 1
fi
fi
# --- DRY RUN PATH (preview only) ---
if [[ $dry_run -eq 1 ]]; then
echo "Dry run: showing subject rewrites for commits in $start..$branch"
while IFS= read -r -d '' sha && IFS= read -r -d '' subject; do
new_subject="$("${pycmd[@]}" "$step_script" --dry-run \
--env "$env_file" \
--mappings "$mappings_file" \
--plan "${plan_file:-}" \
--sha "$sha" \
--subject "$subject" || true)"
if [[ "$new_subject" != "$subject" ]]; then
printf '%s\n OLD: %s\n NEW: %s\n\n' "$sha" "$subject" "$new_subject"
fi
done < <(git log -z --reverse --first-parent --no-merges --pretty=format:%H%x00%s "$start..$branch")
if [[ -n "${plan_file}" && $keep_plan -ne 1 ]]; then
rm -f "$plan_file"
fi
exit 0
fi
# --- Execute the rebase (mutating) ---
current_branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ "$current_branch" != "$branch" ]]; then
git checkout -q "$branch"
fi
set +e
git rebase --rebase-merges=no-rebase-cousins -x \
"$pycmd_str '$step_script' --env '$env_file' --mappings '$mappings_file' --plan '${plan_file:-}'" \
"$start"
status=$?
set -e
if [[ $status -ne 0 ]]; then
echo
error "Rebase paused or failed."
echo "Options:" >&2
echo " - Resolve conflicts, then: git rebase --continue" >&2
echo " - Abort and restore: git rebase --abort && git reset --hard $backup_ref" >&2
echo
echo "State files kept for resume/debug:" >&2
echo " $step_script" >&2
echo " $env_file" >&2
echo " $mappings_file" >&2
echo " $queue_file" >&2
if [[ -n "${plan_file}" ]]; then
echo " $plan_file" >&2
fi
exit $status
fi
# Cleanup on success
rm -f "$step_script" "$env_file" "$mappings_file" "$queue_file"
if [[ -n "${plan_file}" && $keep_plan -ne 1 ]]; then
rm -f "$plan_file"
fi
cat <<EOF
Done. Rebased '$branch' from $start and updated commit subjects.
Rollback backup ref:
git reset --hard "$backup_ref"
Push (history changed):
git push --force-with-lease origin "$branch"
Scope:
Only commits in the range $start..$branch were rewritten. Commit bodies were untouched.
EOF
if [[ -n "${plan_file}" && $keep_plan -eq 1 ]]; then
echo "(Bulk-edit plan kept at: $plan_file)"
fi
#!/usr/bin/env python3
import argparse
import os
import re
import subprocess
import sys
import tempfile
def sh(args):
return subprocess.run(
args, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
).stdout
def read_kv(path):
d = {}
if not path or not os.path.exists(path):
return d
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.rstrip("\r\n")
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
d[k.strip()] = v
return d
def read_lines(path):
if not path or not os.path.exists(path):
return []
with open(path, "r", encoding="utf-8") as f:
return [ln.rstrip("\n") for ln in f]
def transform_subject(core, mappings_lines, default_tag):
for line in mappings_lines:
s = line.strip()
if not s or "=" not in s:
continue
src, dst = s.split("=", 1)
src = src.strip()
dst = dst.strip()
if src.startswith("[") and src.endswith("]"):
src = src[1:-1]
if dst.startswith("[") and dst.endswith("]"):
dst = dst[1:-1]
core = core.replace(f"[{src}]", f"[{dst}]")
if default_tag:
dt = default_tag
if dt.startswith("[") and dt.endswith("]"):
dt = dt[1:-1]
if not re.search(r"\[[A-Z][A-Z0-9]+-\d+\]", core):
core = f"[{dt}] {core}".strip()
return core
def current_subject_and_body():
msg = sh(["git", "log", "-1", "--pretty=%B"])
lines = msg.splitlines()
subject = lines[0] if lines else ""
body = "\n".join(lines[1:]) if len(lines) > 1 else ""
return subject, body
def emit_plan(rev_range, mappings_lines, default_tag):
out = sh(
[
"git", "log", "-z", "--reverse",
"--first-parent", "--no-merges",
"--pretty=format:%H%x00%s",
rev_range,
]
)
toks = out.split("\x00")
for i in range(0, len(toks) - 1, 2):
sha = toks[i]
if not sha:
continue
subject = toks[i + 1]
m = re.match(r"^(?:fixup|squash)!\s+", subject)
prefix = m.group(0) if m else ""
core = subject[len(prefix):] if m else subject
new = prefix + transform_subject(core, mappings_lines, default_tag)
subject = subject.replace("\r", " ")
new = new.replace("\r", " ")
print(sha)
print(f"OLD: {subject}")
print(f"NEW: {new}\n")
def plan_lookup(plan_path, sha):
if not plan_path or not os.path.exists(plan_path):
return None
new_map = {}
cur_sha = None
with open(plan_path, "r", encoding="utf-8") as f:
for raw in f:
line = raw.rstrip("\r\n")
if not line or line.lstrip().startswith("#"):
continue
m_sha = re.match(r"^([0-9a-f]{7,40})\s*$", line)
if m_sha:
cur_sha = m_sha.group(1)
continue
if cur_sha:
m_new = re.match(r"^NEW:\s*(.*)$", line)
if m_new:
new_map[cur_sha] = m_new.group(1).strip()
continue
v = new_map.get(sha)
if v:
return v
return None
def find_gitdir():
try:
out = subprocess.run(
["git", "rev-parse", "--git-dir"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
).stdout.strip()
return out or ".git"
except Exception:
return ".git"
def find_rebase_dir(gitdir):
for d in ("rebase-merge", "rebase-apply"):
path = os.path.join(gitdir, d)
if os.path.isdir(path):
return path
return None
_PICK_CMDS = ("pick", "reword", "edit", "fixup", "squash")
def last_pick_sha(rebase_dir):
if not rebase_dir:
return None
done = os.path.join(rebase_dir, "done")
try:
with open(done, "r", encoding="utf-8") as f:
lines = [ln.rstrip("\r\n") for ln in f]
except FileNotFoundError:
return None
i = len(lines) - 1
while i >= 0 and lines[i].startswith("exec "):
i -= 1
if i < 0:
return None
m = re.match(r"^([a-z]+)\s+([0-9a-f]{7,40})\b", lines[i])
if not m:
return None
cmd, sha = m.group(1), m.group(2)
if cmd in _PICK_CMDS:
return sha
return None
def main():
ap = argparse.ArgumentParser(add_help=False)
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--emit-plan", action="store_true")
ap.add_argument("--range", required=False)
ap.add_argument("--env", required=False)
ap.add_argument("--mappings", required=False)
ap.add_argument("--plan", required=False)
ap.add_argument("--queue", required=False) # accepted; not used
ap.add_argument("--sha", required=False)
ap.add_argument("--subject", required=False)
args = ap.parse_args()
env = read_kv(args.env) if args.env else {}
mappings_lines = read_lines(args.mappings)
default_tag = env.get("DEFAULT_TAG", "")
if args.emit_plan:
if not args.range:
print("ERROR: --emit-plan requires --range", file=sys.stderr)
sys.exit(2)
emit_plan(args.range, mappings_lines, default_tag)
return
if args.dry_run:
subject = args.subject or ""
sha = args.sha or ""
override = plan_lookup(args.plan, sha)
if override is not None:
print(override)
return
m = re.match(r"^(?:fixup|squash)!\s+", subject)
prefix = m.group(0) if m else ""
core = subject[len(prefix):] if m else subject
new_subject = prefix + transform_subject(core, mappings_lines, default_tag)
print(new_subject)
return
subject, body = current_subject_and_body()
gitdir = find_gitdir()
rebase_dir = find_rebase_dir(gitdir)
sha = last_pick_sha(rebase_dir)
if not sha:
return
new_subject = plan_lookup(args.plan, sha)
if new_subject is None:
m = re.match(r"^(?:fixup|squash)!\s+", subject)
prefix = m.group(0) if m else ""
core = subject[len(prefix):] if m else subject
new_subject = prefix + transform_subject(core, mappings_lines, default_tag)
if new_subject == subject:
return
new_msg = new_subject + ("\n" + body if body else "")
with tempfile.NamedTemporaryFile("w", delete=False, encoding="utf-8") as tf:
tf.write(new_msg)
path = tf.name
try:
amend_flag = env.get("AMEND_VERIFY_FLAG", "")
cmd = ["git", "commit", "--amend", "-F", path]
if amend_flag:
cmd.append(amend_flag)
subprocess.run(cmd, check=True)
finally:
try:
os.remove(path)
except OSError:
pass
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment