@@ -129,7 +129,7 @@ const entrypointGitHooksTemplate = String
129129 . raw `# 3) Install global git hooks to protect main/master + managed AGENTS context
130130HOOKS_DIR="/opt/docker-git/hooks"
131131PRE_PUSH_HOOK="$HOOKS_DIR/pre-push"
132- POST_PUSH_HOOK ="$HOOKS_DIR/post-push"
132+ POST_PUSH_ACTION ="$HOOKS_DIR/post-push"
133133mkdir -p "$HOOKS_DIR"
134134
135135cat <<'EOF' > "$PRE_PUSH_HOOK"
@@ -257,16 +257,17 @@ done
257257EOF
258258chmod 0755 "$PRE_PUSH_HOOK"
259259
260- cat <<'EOF' > "$POST_PUSH_HOOK "
260+ cat <<'EOF' > "$POST_PUSH_ACTION "
261261#!/usr/bin/env bash
262262set -euo pipefail
263263
264264# 5) Run session backup after successful push
265265REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
266266cd "$REPO_ROOT"
267267
268- # CHANGE: run session backup in post-push so source commit has already landed in remote
269- # WHY: backups should mirror successfully pushed state and not block push validation
268+ # CHANGE: keep post-push backup logic in a reusable action script
269+ # WHY: git has no client-side post-push hook, so the global git wrapper
270+ # invokes this after a successful git push
270271# REF: issue-192
271272if [ "${ "${" } DOCKER_GIT_SKIP_SESSION_BACKUP:-}" != "1" ]; then
272273 if command -v gh >/dev/null 2>&1; then
@@ -277,7 +278,7 @@ if [ "${"${"}DOCKER_GIT_SKIP_SESSION_BACKUP:-}" != "1" ]; then
277278 BACKUP_SCRIPT="/opt/docker-git/scripts/session-backup-gist.js"
278279 fi
279280 if [ -n "$BACKUP_SCRIPT" ]; then
280- node "$BACKUP_SCRIPT" || echo "[session-backup] Warning: session backup failed (non-fatal)"
281+ DOCKER_GIT_SKIP_POST_PUSH_ACTION=1 node "$BACKUP_SCRIPT" || echo "[session-backup] Warning: session backup failed (non-fatal)"
281282 else
282283 echo "[session-backup] Warning: script not found (expected repo or global path)"
283284 fi
@@ -286,7 +287,129 @@ if [ "${"${"}DOCKER_GIT_SKIP_SESSION_BACKUP:-}" != "1" ]; then
286287 fi
287288fi
288289EOF
289- chmod 0755 "$POST_PUSH_HOOK"
290+ chmod 0755 "$POST_PUSH_ACTION"
291+
292+ # 5.5) Install git wrapper so post-push actions run for normal git push invocations.
293+ # Git has no client-side post-push hook, so core.hooksPath alone is insufficient.
294+ GIT_WRAPPER_BIN="/usr/local/bin/git"
295+ GIT_REAL_BIN="$(type -aP git | awk -v wrapper="$GIT_WRAPPER_BIN" '$0 != wrapper { print; exit }')"
296+ if [[ -n "$GIT_REAL_BIN" ]]; then
297+ cat <<'EOF' > "$GIT_WRAPPER_BIN"
298+ #!/usr/bin/env bash
299+ set -euo pipefail
300+
301+ # docker-git managed git wrapper
302+ DOCKER_GIT_REAL_GIT_BIN="__DOCKER_GIT_REAL_BIN__"
303+ DOCKER_GIT_POST_PUSH_ACTION="/opt/docker-git/hooks/post-push"
304+
305+ docker_git_git_subcommand() {
306+ local expect_value="0"
307+ local arg=""
308+ for arg in "$@"; do
309+ if [[ "$expect_value" == "1" ]]; then
310+ expect_value="0"
311+ continue
312+ fi
313+
314+ case "$arg" in
315+ --help|-h|--version|--html-path|--man-path|--info-path|--list-cmds|--list-cmds=*)
316+ return 1
317+ ;;
318+ -c|-C|--git-dir|--work-tree|--namespace|--exec-path|--super-prefix|--config-env)
319+ expect_value="1"
320+ continue
321+ ;;
322+ --git-dir=*|--work-tree=*|--namespace=*|--exec-path=*|--super-prefix=*|--config-env=*|--bare|--no-pager|--paginate|--literal-pathspecs|--no-literal-pathspecs|--glob-pathspecs|--noglob-pathspecs|--icase-pathspecs|--no-optional-locks|--no-lazy-fetch)
323+ continue
324+ ;;
325+ --)
326+ return 1
327+ ;;
328+ -*)
329+ continue
330+ ;;
331+ *)
332+ printf "%s" "$arg"
333+ return 0
334+ ;;
335+ esac
336+ done
337+
338+ return 1
339+ }
340+
341+ docker_git_git_push_is_dry_run() {
342+ local expect_value="0"
343+ local parsing_push_args="0"
344+ local arg=""
345+
346+ for arg in "$@"; do
347+ if [[ "$parsing_push_args" == "0" ]]; then
348+ if [[ "$expect_value" == "1" ]]; then
349+ expect_value="0"
350+ continue
351+ fi
352+
353+ case "$arg" in
354+ -c|-C|--git-dir|--work-tree|--namespace|--exec-path|--super-prefix|--config-env)
355+ expect_value="1"
356+ continue
357+ ;;
358+ --git-dir=*|--work-tree=*|--namespace=*|--exec-path=*|--super-prefix=*|--config-env=*|--bare|--no-pager|--paginate|--literal-pathspecs|--no-literal-pathspecs|--glob-pathspecs|--noglob-pathspecs|--icase-pathspecs|--no-optional-locks|--no-lazy-fetch)
359+ continue
360+ ;;
361+ push)
362+ parsing_push_args="1"
363+ continue
364+ ;;
365+ esac
366+
367+ continue
368+ fi
369+
370+ case "$arg" in
371+ --)
372+ break
373+ ;;
374+ --dry-run|-n)
375+ return 0
376+ ;;
377+ esac
378+ done
379+
380+ return 1
381+ }
382+
383+ docker_git_post_push_action() {
384+ if [[ "${ "${" } DOCKER_GIT_SKIP_POST_PUSH_ACTION:-}" == "1" ]]; then
385+ return 0
386+ fi
387+
388+ if [[ -x "$DOCKER_GIT_POST_PUSH_ACTION" ]]; then
389+ DOCKER_GIT_SKIP_POST_PUSH_ACTION=1 "$DOCKER_GIT_POST_PUSH_ACTION" || true
390+ fi
391+ }
392+
393+ subcommand=""
394+ if subcommand="$(docker_git_git_subcommand "$@")" && [[ "$subcommand" == "push" ]]; then
395+ if "$DOCKER_GIT_REAL_GIT_BIN" "$@"; then
396+ status=0
397+ else
398+ status=$?
399+ fi
400+
401+ if [[ "$status" -eq 0 ]] && ! docker_git_git_push_is_dry_run "$@"; then
402+ docker_git_post_push_action
403+ fi
404+
405+ exit "$status"
406+ fi
407+
408+ exec "$DOCKER_GIT_REAL_GIT_BIN" "$@"
409+ EOF
410+ sed -i "s#__DOCKER_GIT_REAL_BIN__#$GIT_REAL_BIN#g" "$GIT_WRAPPER_BIN" || true
411+ chmod 0755 "$GIT_WRAPPER_BIN" || true
412+ fi
290413
291414git config --system core.hooksPath "$HOOKS_DIR" || true
292415git config --global core.hooksPath "$HOOKS_DIR" || true`
0 commit comments