fix: use git fetch from Gitea side to transfer objects + update refs without triggering pre-receive hook

This commit is contained in:
2026-06-24 13:47:50 +02:00
parent f8093bf648
commit 7ae008717f
+37 -35
View File
@@ -4,7 +4,7 @@ import logging
import os import os
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from typing import Dict, Optional from typing import Optional
from .mirror import Mirror from .mirror import Mirror
@@ -52,10 +52,11 @@ def _git(*args: str, timeout: int = 300, check: bool = True) -> str:
def push_to_gitea(mirror: Mirror) -> bool: def push_to_gitea(mirror: Mirror) -> bool:
"""Sync canonical refs to Gitea's bare repo via direct ref updates. """Sync canonical → Gitea via ``git fetch`` from Gitea's side.
Uses ``git update-ref`` to bypass Gitea's pre-receive hook, then ``git fetch`` transfers objects + updates refs without triggering
fires Gitea's ``post-receive`` hook manually so the UI refreshes. Gitea's ``pre-receive`` hook, then fires ``post-receive`` manually
for the UI to refresh.
Returns True if anything changed. Returns True if anything changed.
""" """
@@ -66,49 +67,50 @@ def push_to_gitea(mirror: Mirror) -> bool:
canonical = str(mirror.canonical_dir) canonical = str(mirror.canonical_dir)
# Collect canonical refs # Grab old refs so we can detect what changed
out = _git("--git-dir", canonical, "for-each-ref", old_refs: Dict[str, str] = {}
out = _git("--git-dir", str(gitea_path), "for-each-ref",
"--format=%(objectname) %(refname)", "--format=%(objectname) %(refname)",
"refs/heads/", "refs/tags/", "refs/heads/", "refs/tags/",
timeout=30, check=False) timeout=30, check=False)
canonical_refs: Dict[str, str] = {}
for line in (out or "").splitlines(): for line in (out or "").splitlines():
line = line.strip() line = line.strip()
if not line: if not line:
continue continue
obj_hash, ref = line.split(None, 1) h, r = line.split(None, 1)
canonical_refs[ref] = obj_hash old_refs[r] = h
# Gitea fetches from canonical — this transfers objects + refs,
# and DOES NOT trigger Gitea's pre-receive hook.
_git(
"--git-dir", str(gitea_path), "fetch", "--prune",
canonical,
"+refs/heads/*:refs/heads/*",
"+refs/tags/*:refs/tags/*",
timeout=300,
)
# Build the changelist for post-receive
changed: list = [] changed: list = []
for ref, obj_hash in canonical_refs.items(): cur_refs: dict = {}
gitea_hash = _git("--git-dir", str(gitea_path), "rev-parse", out = _git("--git-dir", str(gitea_path), "for-each-ref",
"--verify", "--quiet", ref, "--format=%(objectname) %(refname)",
timeout=15, check=False) "refs/heads/", "refs/tags/",
if obj_hash != gitea_hash: timeout=30, check=False)
if gitea_hash: for line in (out or "").splitlines():
_git("--git-dir", str(gitea_path), "update-ref", ref,
obj_hash, gitea_hash, timeout=15)
else:
_git("--git-dir", str(gitea_path), "update-ref", ref,
obj_hash, timeout=15)
changed.append((gitea_hash or "0" * 40, obj_hash, ref))
logger.debug("Updated %s in Gitea", ref)
# Prune refs in Gitea not in canonical
gitea_out = _git("--git-dir", str(gitea_path), "for-each-ref",
"--format=%(objectname) %(refname)",
"refs/heads/", "refs/tags/",
timeout=30, check=False)
for line in (gitea_out or "").splitlines():
line = line.strip() line = line.strip()
if not line: if not line:
continue continue
obj_hash, ref = line.split(None, 1) h, r = line.split(None, 1)
if ref not in canonical_refs: cur_refs[r] = h
_git("--git-dir", str(gitea_path), "update-ref", "-d", ref, old = old_refs.get(r, "0" * 40)
obj_hash, timeout=15) if h != old:
changed.append((obj_hash, "0" * 40, ref)) changed.append((old, h, r))
logger.debug("Pruned %s from Gitea", ref)
# Detect deletions (refs present before fetch but gone now)
for r, old_h in old_refs.items():
if r not in cur_refs:
changed.append((old_h, "0" * 40, r))
if not changed: if not changed:
logger.debug("No new commits to push to Gitea") logger.debug("No new commits to push to Gitea")