
    sj                        d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
mZ  ej        e          ZdZdZdd
ZddddZddZdS )u0  Stale git lock-file recovery for update/check paths.

A crashed or killed ``git fetch`` on a shallow clone can leave
``.git/shallow.lock`` behind. Every later fetch then fails with::

    fatal: Unable to create '/path/.git/shallow.lock': File exists.

This wedges ``hermes update --check`` (hard failure) and silently degrades the
passive banner check in :mod:`hermes_cli.banner` (the fetch is swallowed, the
stale refs are compared, and the user can be told an update is available when
the checkout already contains the remote tip). Git does not self-heal these
lock files — they persist until a human removes them.

This module provides two small, defensive helpers used by the update paths:

* :func:`clear_stale_git_locks` — remove abandoned ``.git`` lock files (with
  an age + git-process guard so a live fetch is never yanked).
* :func:`is_ancestor_of_head` — ask whether a remote tip is already contained
  in HEAD. Used by the shallow-clone update check to avoid reporting a false
  "update available" when local cherry-picks sit on top of the remote tip.
    )annotationsN)Path)ListOptionaliX  )zshallow.lockz
index.lockz	HEAD.lockzMERGE_HEAD.lockreturnboolc                 2   	 t           j        dk    r5t          j        g dddd          j                                        } d| v S t          j        g dddd          } | j        dk    S # t          $ r  t          	                    d	d
           Y dS w xY w)a  True when a ``git`` process is currently running.

    The conservative answer on any platform we can't probe: if we can't tell,
    treat a lock as possibly-live and don't remove it. This is the safety
    check that stops us from yanking a lock a real fetch is holding.
    nt)tasklistz/FIzIMAGENAME eq git.exez/FOCSVT
   )capture_outputtexttimeoutzgit.exe)pgrepz-xgitr   z1git process probe failed; assuming no git runningexc_infoF)
osname
subprocessrunstdoutlower
returncode	Exceptionloggerdebug)outs    6/home/agent/.hermes/hermes-agent/hermes_cli/gitlock.py_git_proc_runningr!   0   s    7d??.III#$   UUWW  ##n"""4dB
 
 
 ~""   HSWXXXuus   AA, $A, ,&BB)min_age_seconds	repo_rootr   r"   Optional[int]	List[str]c               j   t          |           dz  }|                                sg S t                      rt                              d           g S t          j                    ||nt          z
  }g }t          D ]}||z  }	 |                                rn|	                                j
        |k     rQ|                                 |                    t          |                     t                              d|           # t          $ r  t                              d|d           Y w xY w|S )u  Remove abandoned ``.git`` lock files under ``repo_root``.

    A lock is removed only when BOTH conditions hold:

    * it is older than :data:`STALE_LOCK_MIN_AGE_SECONDS` (default), and
    * no ``git`` process is currently running.

    Returns the list of removed lock file paths. Never raises: a lock we
    cannot stat or unlink is skipped (a concurrently-held lock may have just
    been created between our age check and the unlink — the process guard
    makes that window vanishingly small, and skipping is always safe).
    z.gitz.git process running; skipping stale-lock sweepNzRemoved stale git lock %szCould not clear %s (skipping)Tr   )r   is_dirr!   r   r   timeSTALE_LOCK_MIN_AGE_SECONDS
LOCK_NAMESis_filestatst_mtimeunlinkappendstrinfoOSError)r#   r"   git_dircutoffremovedr   	lock_paths          r    clear_stale_git_locksr7   G   s?    9oo&G>> 	 EFFF	Y[[/JOOPjkFG T TdN		T  "" Dy~~'7'7'@6'I'I  """s9~~...7CCC 	T 	T 	TLL8)dLSSSSS	TNs   BD'D0/D0revr0   c                    	 t          j        ddd|dgt          |           ddd          }|j        dk    S # t          $ r! t
                              d	|d
           Y dS w xY w)u  True when ``rev`` is an ancestor of (or equal to) HEAD.

    Wraps ``git merge-base --is-ancestor <rev> HEAD``. This is the correct
    question for update checks: a local cherry-pick on top of the remote tip
    makes HEAD *different* from ``origin/main`` but still *contains* it, so
    the answer to "is there an update?" is no.

    Returns False on any probe failure (missing rev, shallow boundary, git
    error) — callers treat that as "can't prove contained", which is the
    conservative direction for an update check.
    r   z
merge-basez--is-ancestorHEADTr   )cwdr   r   r   r   z,merge-base --is-ancestor probe failed for %sr   F)r   r   r0   r   r   r   r   )r#   r8   results      r    is_ancestor_of_headr=   j   s    	L/3?IdB
 
 

  A%%   CSSWXXXuus   58 'A#"A#)r   r   )r#   r   r"   r$   r   r%   )r#   r   r8   r0   r   r   )__doc__
__future__r   loggingr   r   r(   pathlibr   typingr   r   	getLogger__name__r   r)   r*   r!   r7   r=        r    <module>rG      s    , # " " " " "  				            ! ! ! ! ! ! ! !		8	$	$ %  L
   . PT            F     rF   