@@ -1461,6 +1461,15 @@ def _apply_pack(
14611461
14621462 for oldsha , sha , ref in refs :
14631463 ref_status = b"ok"
1464+
1465+ # Run update hook for this ref
1466+ hook_error = self ._on_update (ref , oldsha , sha )
1467+ if hook_error :
1468+ # Update hook declined this ref
1469+ ref_status = hook_error
1470+ yield (ref , ref_status )
1471+ continue
1472+
14641473 try :
14651474 if sha == zero_sha :
14661475 if CAPABILITY_DELETE_REFS not in self .capabilities ():
@@ -1512,6 +1521,68 @@ def flush() -> None:
15121521 write (None ) # type: ignore
15131522 flush ()
15141523
1524+ def _on_pre_receive (
1525+ self , client_refs : list [tuple [ObjectID , ObjectID , Ref ]]
1526+ ) -> None :
1527+ """Run pre-receive hook.
1528+
1529+ Args:
1530+ client_refs: List of (old_sha, new_sha, ref_name) tuples
1531+
1532+ Raises:
1533+ HookError: If hook fails, preventing the push
1534+ """
1535+ hook = self .repo .hooks .get ("pre-receive" , None ) # type: ignore[attr-defined]
1536+ if not hook :
1537+ return
1538+ try :
1539+ stdout , stderr = hook .execute (client_refs )
1540+ # Only send output via sideband if capabilities are set
1541+ if self ._client_capabilities is not None :
1542+ if stdout and self .has_capability (CAPABILITY_SIDE_BAND_64K ):
1543+ self .proto .write_sideband (SIDE_BAND_CHANNEL_PROGRESS , stdout )
1544+ if stderr and self .has_capability (CAPABILITY_SIDE_BAND_64K ):
1545+ self .proto .write_sideband (SIDE_BAND_CHANNEL_PROGRESS , stderr )
1546+ except HookError as err :
1547+ # Send error to client via sideband if available
1548+ if self ._client_capabilities is not None and self .has_capability (
1549+ CAPABILITY_SIDE_BAND_64K
1550+ ):
1551+ self .proto .write_sideband (
1552+ SIDE_BAND_CHANNEL_FATAL , str (err ).encode ("utf-8" )
1553+ )
1554+ # Re-raise to abort the push
1555+ raise
1556+
1557+ def _on_update (
1558+ self , ref_name : Ref , old_sha : ObjectID , new_sha : ObjectID
1559+ ) -> bytes | None :
1560+ """Run update hook for a single ref.
1561+
1562+ Args:
1563+ ref_name: Name of the reference
1564+ old_sha: Old SHA of the reference
1565+ new_sha: New SHA of the reference
1566+
1567+ Returns:
1568+ Error message if hook fails, None otherwise
1569+ """
1570+ hook = self .repo .hooks .get ("update" , None ) # type: ignore[attr-defined]
1571+ if not hook :
1572+ return None
1573+ try :
1574+ stdout , stderr = hook .execute (ref_name , old_sha , new_sha )
1575+ # Only send output via sideband if capabilities are set
1576+ if self ._client_capabilities is not None :
1577+ if stdout and self .has_capability (CAPABILITY_SIDE_BAND_64K ):
1578+ self .proto .write_sideband (SIDE_BAND_CHANNEL_PROGRESS , stdout )
1579+ if stderr and self .has_capability (CAPABILITY_SIDE_BAND_64K ):
1580+ self .proto .write_sideband (SIDE_BAND_CHANNEL_PROGRESS , stderr )
1581+ return None
1582+ except HookError as err :
1583+ # Return error message to mark this ref as failed
1584+ return str (err ).encode ("utf-8" )
1585+
15151586 def _on_post_receive (self , client_refs : dict [bytes , tuple [bytes , bytes ]]) -> None :
15161587 """Run post-receive hook.
15171588
@@ -1569,6 +1640,22 @@ def handle(self) -> None:
15691640 client_refs .append ((ObjectID (oldsha ), ObjectID (newsha ), Ref (ref_name )))
15701641 ref_line = self .proto .read_pkt_line ()
15711642
1643+ # Run pre-receive hook before processing the pack
1644+ # If it fails, we abort the entire push
1645+ try :
1646+ self ._on_pre_receive (client_refs )
1647+ except HookError :
1648+ # Hook failed, report error to client and abort
1649+ # We still need to consume the pack data to avoid protocol errors
1650+ if self .has_capability (CAPABILITY_REPORT_STATUS ):
1651+ # Send unpack error status
1652+ status = [(b"unpack" , b"pre-receive hook declined" )]
1653+ # Add 'ng' (not good) status for all refs
1654+ for _ , _ , ref_name in client_refs :
1655+ status .append ((ref_name , b"pre-receive hook declined" ))
1656+ self ._report_status (status )
1657+ return
1658+
15721659 # backend can now deal with this refs and read a pack using self.read
15731660 status = list (self ._apply_pack (client_refs ))
15741661
0 commit comments