
    `gj                        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
 ddlmZmZ dZdZd	Zdd
ZddZ G d d      Z e       ZddZddZddZddZefddZy)u  Git working-tree probing for the gateway: run git, resolve repo roots, fold
linked worktrees under their common root.

Probing runs where the gateway runs, so it resolves repos for both local and
remote backends (unlike the desktop's electron probe, which only sees the local
fs). Resolved roots are cached with a thread-safe, single-flight cache: the
gateway's long handlers run on worker threads, so concurrent identical probes
(e.g. two overlapping project-tree builds) share one `git` invocation instead of
racing an unguarded dict.

Positive results are cached for the process lifetime; negative results (a cwd
that isn't a git repo, or a deleted/nonexistent dir) are cached only for a short
TTL (`_NEG_TTL`). Caching negatives matters a lot for the desktop Projects tree:
``project_tree.build_tree`` resolves a cwd once *per session* (not per distinct
cwd), so a power user with hundreds of sessions in non-git/deleted dirs would
otherwise re-spawn ``git`` hundreds of times on *every* sidebar open — the cause
of the multi-second "Projects" load. The TTL keeps a not-yet-repo cwd
re-probable (we `git init` a new project's folder on its first worktree, and a
frozen "" would mislabel its main lane by the dir basename) — it just stops the
same "not a repo" answer from being re-derived dozens of times within one build
and across rapid re-opens. `invalidate()` drops everything after a known
mutation.
    )annotationsN)Iterable)ThreadPoolExecutor)
IS_WINDOWSwindows_hide_flagsg      ?   g      >@c                   | syt         rdt               ini }	 t        j                  dd| g|fddddt        dt        j
                  d	|}|j                  d
k(  r|j                  j                         S dS # t        $ r Y yw xY w)uF   ``git -C <cwd> <args>`` → stripped stdout, or ``""`` on any failure. creationflagsgitz-CTzutf-8replaceF)capture_outputtextencodingerrorstimeoutcheckstdinr   )
r   r   
subprocessrun_GIT_TIMEOUTDEVNULL
returncodestdoutstrip	Exception)cwdargs_popen_kwargsresults       H/root/.hermes/venv/lib/python3.12/site-packages/tui_gateway/git_probe.pyrun_gitr"   .   s    ?I_&8&:;rMD#%%

 $$

 

 )/(9(9Q(>v}}""$FBF s   AA9 7A9 9	BBc                <    t        | dd      xs t        | ddd      S )Nbranchz--show-current	rev-parsez--shortHEADr"   r   s    r!   r$   r$   D   s$    3"23cwsKQZ\b7cc    c                  (    e Zd ZdZddZddZddZy)
_RootCachea(  Thread-safe, single-flight cache of git-root probes. Positive results are
    cached for the process lifetime; negative ("not a repo") results are cached
    only for ``_NEG_TTL`` seconds so a not-yet-repo cwd stays re-probable.
    Followers wait on the leader's probe instead of duplicating it.c                `    t        j                         | _        i | _        i | _        i | _        y N)	threadingLock_lock_roots_neg	_inflightselfs    r!   __init__z_RootCache.__init__N   s%    ^^%
&(&(	57r)   c                    | j                   5  | j                  j                          | j                  j                          | j                  j                          d d d        y # 1 sw Y   y xY wr-   )r0   r1   clearr2   r3   r4   s    r!   
invalidatez_RootCache.invalidateT   sL    ZZ 	#KKIIOONN  "	# 	# 	#s   AA%%A.c                .   	 | j                   5  | j                  j                  |      }|r|cd d d        S | j                  j                  |      }|.|t	        j
                         kD  r
	 d d d        y| j                  |= | j                  j                  |      }|&t        j                         }|| j                  |<   d}nd}d d d        sj                  t        dz          d}	  |       }| j                   5  |r|| j                  |<   n(t	        j
                         t        z   | j                  |<   | j                  j                  |d        d d d        j                          |S # 1 sw Y   xY w# 1 sw Y   'xY w# | j                   5  |r|| j                  |<   n(t	        j
                         t        z   | j                  |<   | j                  j                  |d        d d d        n# 1 sw Y   nxY wj                          w xY w)NTr
   Fg      ?)r   )r0   r1   getr2   time	monotonicr3   r.   Eventwaitr   _NEG_TTLpopset)r5   keyprobehitexpirygateleadervalues           r!   resolvez_RootCache.resolveZ   s    #kkooc*# # s+% 00  "# # 		#~~))#.<$??,D*.DNN3'!F"F'#* 		,"4	5E	ZZ 2+0C()-)9H)D		#NN&&sD12 
LK# #<2 2TZZ 2+0C()-)9H)D		#NN&&sD12 2 2 
sI   E25E25AE2.F
 AE>2E;>F
HAG7.	H7H <HNreturnNone)rC   strrL   rN   )__name__
__module____qualname____doc__r6   r9   rJ    r)   r!   r+   r+   H   s    G
8#'r)   r+   c                 ,    t         j                          y)zEDrop cached roots after a known mutation (e.g. a worktree was added).N)_cacher9   rS   r)   r!   r9   r9      s    
r)   c                <      syt         j                    fd      S )z=Top-level git repo root for ``cwd`` (``""`` when not a repo).r
   c                     t         dd      S )Nr%   z--show-toplevelr'   r(   s   r!   <lambda>zrepo_root.<locals>.<lambda>   s    wsKAR'S r)   rU   rJ   r(   s   `r!   	repo_rootrZ      s    >>#STTr)   c                H      syd fd}t         j                  d  |      S )a  The MAIN (common) repo root for ``cwd``, folding linked worktrees.

    ``--show-toplevel`` returns a linked worktree's OWN root, so grouping by it
    splits every worktree into a separate "repo". The common ``.git`` dir
    (``--git-common-dir``) is shared by a repo and all its worktrees, so its
    parent is the one true repo root; fall back to the toplevel root otherwise.
    r
   c                     t        ddd      } | r`t        j                  j                  |       } t        j                  j	                  |       dk(  rt        j                  j                  |       S t              S )Nr%   z--path-format=absolutez--git-common-dirz.git)r"   ospathrealpathbasenamedirnamerZ   )gitdirr   s    r!   _probez common_repo_root.<locals>._probe   s`    k+CEWXWW%%f-Fww'61wwv..~r)   zcommon:)rL   rN   rY   )r   rc   s   ` r!   common_repo_rootrd      s)      >>GC5/622r)   c                B    t        |       }|syt        |       xs ||dS )a  Inject-able resolver for ``project_tree.build_tree``.

    Returns ``{"repo_root": <common root>, "worktree_root": <this checkout>}``
    or ``None`` when ``cwd`` is not in a git repo. ``build_tree`` treats
    ``worktree_root == repo_root`` as the main checkout.
    N)rZ   worktree_root)rZ   rd   )r   rf   s     r!   rJ   rJ      s)     cNM)#.?-R_``r)   c                R   t        | D ch c]  }|xs dj                          c}dhz
        }|syt        |      dk(  rt        |d          yt	        t        |t        |                  5 }t        |j                  t        |             ddd       yc c}w # 1 sw Y   yxY w)zPre-resolve many cwds' roots in parallel (bounded) so a cold first paint
    doesn't serialize one git subprocess per session cwd. Single-flight dedupes
    overlap; results land in the shared cache for the sequential consumers.r
   N   r   )max_workers)sortedr   lenrJ   r   minlistmap)cwdsri   r   pendingpools        r!   
warm_rootsrr      s     T:csyb''):bTABG
7|q
	KW(F	G )4TXXgw'() ) ;) )s   B/ BB&)r   rN   r   rN   rL   rN   )r   rN   rL   rN   rK   )r   rN   rL   zdict | None)ro   zIterable[str]ri   intrL   rM   )rR   
__future__r   r]   r   r.   r<   collections.abcr   concurrent.futuresr   hermes_cli._subprocess_compatr   r   r   _WARM_WORKERSr@   r"   r$   r+   rU   r9   rZ   rd   rJ   rr   rS   r)   r!   <module>ry      su   0 # 	    $ 1 H ,d9 9x 

U3,
a 8E )r)   