summaryrefslogtreecommitdiff
path: root/hooks/post-receive
blob: dc328195c3fd796950ae29dc818928dd28a6101b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env bash
# ==============================================================================
# post-receive hook — Mirror push to multiple forges
# ==============================================================================
#
# Place this file in your bare repository:
#   /path/to/project-tick.git/hooks/post-receive
#
# Make it executable:
#   chmod +x hooks/post-receive
#
# Configuration:
#   Set mirror remotes in the bare repo:
#
#   git remote add github  git@github.com:Project-Tick/Project-Tick.git
#   git remote add gitlab  git@gitlab.com:Project-Tick/Project-Tick.git
#   git remote add codeberg git@codeberg.org:Project-Tick/Project-Tick.git
#   git remote add sourceforge ssh://USERNAME@git.code.sf.net/p/project-tick/code
#
#   Or use HTTPS with token auth:
#
#   git remote add github  https://x-access-token:TOKEN@github.com/Project-Tick/Project-Tick.git
#   git remote add gitlab  https://oauth2:TOKEN@gitlab.com/Project-Tick/Project-Tick.git
#   git remote add codeberg https://TOKEN@codeberg.org/Project-Tick/Project-Tick.git
#
#   Environment variables (optional):
#     MIRROR_REMOTES — space-separated list of remote names to push to.
#                      Defaults to all configured mirror remotes.
#     MIRROR_LOG     — path to log file. Defaults to /var/log/git-mirror.log
#     MIRROR_NOTIFY  — email address for failure notifications (requires mail cmd)
#
# ==============================================================================

set -euo pipefail

# ---------------------
# Configuration
# ---------------------

# Where to find mirror remotes. Override with MIRROR_REMOTES env var.
# Falls back to auto-detecting all remotes that aren't "origin".
MIRROR_REMOTES="${MIRROR_REMOTES:-}"
MIRROR_LOG="${MIRROR_LOG:-/var/log/git-mirror.log}"

# Auto-detect remotes if not explicitly set
if [[ -z "$MIRROR_REMOTES" ]]; then
    MIRROR_REMOTES=$(git remote | grep -v '^origin$' || true)
fi

if [[ -z "$MIRROR_REMOTES" ]]; then
    echo "[mirror] No mirror remotes configured. Skipping." >&2
    exit 0
fi

# ---------------------
# Logging
# ---------------------
log() {
    local timestamp
    timestamp="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
    echo "[$timestamp] $*" | tee -a "$MIRROR_LOG" 2>/dev/null || echo "[$timestamp] $*"
}

# ---------------------
# Main
# ---------------------

log "=== Mirror push triggered ==="

# Read the stdin from post-receive (old-sha new-sha refname)
REFS=()
while read -r oldrev newrev refname; do
    REFS+=("$refname")
    log "  ref: $refname ($oldrev -> $newrev)"
done

FAILED_REMOTES=()
SUCCEEDED_REMOTES=()

for remote in $MIRROR_REMOTES; do
    log "Pushing to remote: $remote"

    # Use --mirror for full mirror, or --all --tags for selective
    # --mirror pushes ALL refs (branches, tags, notes, etc.)
    # --force ensures deleted branches/tags are also synced
    if git push --mirror --force "$remote" 2>&1 | tee -a "$MIRROR_LOG" 2>/dev/null; then
        SUCCEEDED_REMOTES+=("$remote")
        log "  ✓ Successfully pushed to $remote"
    else
        FAILED_REMOTES+=("$remote")
        log "  ✗ FAILED to push to $remote"
    fi
done

log "--- Summary ---"
log "  Succeeded: ${SUCCEEDED_REMOTES[*]:-none}"
log "  Failed:    ${FAILED_REMOTES[*]:-none}"

# Send notification on failure if configured
if [[ ${#FAILED_REMOTES[@]} -gt 0 && -n "${MIRROR_NOTIFY:-}" ]]; then
    if command -v mail &>/dev/null; then
        {
            echo "Mirror push failed for the following remotes:"
            printf '  - %s\n' "${FAILED_REMOTES[@]}"
            echo ""
            echo "Repository: $(pwd)"
            echo "Refs updated:"
            printf '  %s\n' "${REFS[@]}"
            echo ""
            echo "Check log: $MIRROR_LOG"
        } | mail -s "[git-mirror] Push failure in $(basename "$(pwd)")" "$MIRROR_NOTIFY"
    fi
fi

# Exit with error if any remote failed
if [[ ${#FAILED_REMOTES[@]} -gt 0 ]]; then
    log "=== Finished with errors ==="
    exit 1
fi

log "=== Finished successfully ==="
exit 0