From d35655ceebb04078f28ba187132618c6254ac972 Mon Sep 17 00:00:00 2001 From: Roberto Guagliardo Date: Mon, 2 Feb 2026 20:25:27 +0000 Subject: [PATCH] fixed with codex --- .../__pycache__/cli.cpython-311.pyc | Bin 0 -> 6175 bytes .../__pycache__/diff.cpython-311.pyc | Bin 8464 -> 9350 bytes .../__pycache__/ollama.cpython-311.pyc | Bin 0 -> 1781 bytes .../__pycache__/render.cpython-311.pyc | Bin 0 -> 3638 bytes src/ai_reviewer/diff.py | 60 ++++++++++++------ .../test_diff.cpython-311-pytest-9.0.2.pyc | Bin 0 -> 7916 bytes tests/test_diff.py | 57 +++++++++++++++++ 7 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 src/ai_reviewer/__pycache__/cli.cpython-311.pyc create mode 100644 src/ai_reviewer/__pycache__/ollama.cpython-311.pyc create mode 100644 src/ai_reviewer/__pycache__/render.cpython-311.pyc create mode 100644 tests/__pycache__/test_diff.cpython-311-pytest-9.0.2.pyc create mode 100644 tests/test_diff.py diff --git a/src/ai_reviewer/__pycache__/cli.cpython-311.pyc b/src/ai_reviewer/__pycache__/cli.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68d21ad3adee64de82461ea86beb1ed242a6fb4a GIT binary patch literal 6175 zcmd5=TWk|o8b0H>cznMkCI*r)kkH^15=trUBA_LtfpQDwQWbBz-oY~g!}!wUF_6ev zc`KxiP%E+$5^0yJe(18PnukjJQtiX4tL{pDoT=u)8YxnwRUi6hR4P?yAND_I?8G)u zkoIMdkAMH?od4Ya%Xj>jK){P2efXbB#vefF-=xq?eurUx{F@7*J4itaqoFL5WEjG` zG*{9^f7v7pzpTb(`6S7n(!=FR$&zN|m#&jykKS|@11Y$zF` zyju%rBgqKmMJ<}`N_J(tlik@^GL~JJT*n|6QiaPs%>^o+CGR~3p)cU;q-5NM4kN{P z11Wx0yvITtd>d`+mF-I82AABR#;Y5fwXMJRpzy_N1)Zz6SyZA*m*P>nmb&k8AO&A1 zbxO6b=a*ZjSY?N@;RfG6Z?fCF1_1yzq_yWF<-Mvfcf2zc$r%Fgqk=U$k z>6Dlfl@aCnM^3U|ah*Yl(I3cS6O8SXbGf`O>zRD6coNFoDOtaiaM_+Qxu~AVE2?Jm z6B$)g60Ge@Un=FUq^2^OTC}|d85h-*l9`&a{kW7%UCijT1cUe|HBHXShqR2E(@FV4 zDWfT=0?ua(dV;gv6PcWn$z8NVimH?fDhx@>McaqfoTB2?fjISv9Yq<#+ zDkv*TDh=akDp_;7ap?4?q1Mcg*P(a^sVK=PDCs&!OoUaKC0Dy5COL&wd4<~t(o}-K zftG|08G*{US7pRUMsX`17{yB@6dxR|-xklPx(?eb&V=8)rsj5yytE??CyDpId;xag zR@5oEr0F(W)UnNH$r-n8gV+&lh52zi-G;tu$p~FuviRews29^)!fz3(T^|>dU#;;Qa-DW zD(clyxTwo&S}%^movQL^TFdN^OL`uwS2OCh(IQTd%9)f?QZZ>BDNH{U_raZCP~-zM zk>>tKnpH8Fpx6bZg8uCK%(J+|_|==HblQ?mR}dzC2hIg_fu2ihN>0OE)3z^9wJ1dv z{=;uz=Qf+k>9!{;Ur%Y7oC^9X+F~k|le20nWqVU8ILneocz-JOUP;!R7A(L7m@L5# zrBW^Wo=V|Oq-hHw8wepw!NkAeXp<18_q_P#ZMdY({H$ z=<&K$M(sv0%j8A}kPe-j>GG<#8$`Ho)}_0fEuf=Be`!*fvg=RilMb^hyWWQwIqUkJ z>l$+nU1!guYfPD`^37hYzl!T3YS;2v_9B|)%Ir9L_ph_O2bH-hx!y;T9$Kr?BWrw_ zSJ?Hd#rA!6$Z^jo$3LT-@QiZqC&?{|_u!PiSUsus)idaWn{Z1+KN{&5@5b}Ycf)~a z@t&ONqtQTev`kf`Ozx_zWfbY?naPuTC0kte;T#DNHb-KG?Jnjrg@UTv?yOoY$`@6e z*W?STmf$h*Zg>YFBZQ0+vJ=Qme{&*7#ieYfSOjk>O_4CPS4wzoj)n(b%gaj94v__n ztHi^;j&UAbsyfzee^-No{6GRvW^4`vp2 zn4xV}Xxkh&$NjwQ3t7HRb;LkxD_stDSwYFzJn?lG5XffmuoXP~?HNNn^)sY8OL`DS zK)%qeZ8x{g?_I#AcZ=oSQaM!XmnuhU;jTLJ^KZepgwAnwm(TMSQ}cJYg?p*FDZs=Q3sxRNonj5xT8@AVdLL_=0 z0eL{keO3$vA0Qz0Rr2#sQ525D9$^iwbUE5%MF)+o$G%Pjfow*PThZgj>2rqvTw|-y z1-KPoS7owDF5&&|9&{&kkY(@153qmdf%(V4m~d#=_06z%Ji>poJ3j8`|LNy}A2|zP zMNqGUA1vA)qK&Ei6?xhgl?-+`4y%PcCXUMHE&$L0ZB=E(=1%5wYPtavLP-bP)>Nqg&~v7N9VLp3 zWq7Xm=O9z+qUPdBfRcWdK(BlK#*~fUH!O$d%II z|IiC~t68xY_cz0UX%R>>5i_S({v8dHts(LrG_5^`X2sUFGG7qN;u=6J3$(l%D9i38 zrb8t>tzlCqH`hqdpiW-h*OW%6-0|d__30$kH&WSG_FP^K(12gtlx^1E7WO=Ap}jr4 z%myIv2Frd3yuY0d=>*I*#}8HMgSlC5{ttU*h$C8xuCxF$*r@|GKbd#oM}1P7U`%WEVCRkhu8f!`CK~KOb)s#U zD39&d^VEyk;zbqAT-H_M_u*Be&|r*-6;MZrUm_gAgg8h#daq%K=?;260u_m_4i||} z5ZRN2tgUDZ=^7vbBUNlX@ocfl;WtPt@4Su$`c#HD7CesN4eY=Oo>tm|^A2|Kh5)__ z93XuhQEbsp9Ip(}p1izp3T7Z6qnM`v8G-#~ zV80dEUzzwmuzq2iwPC~zj9P)w%7lYDB0Y7)@~==b$1g|v7SvC(A7?+x-_4tm5i2q> zC)DEopN2jTEs9IQuLLvxsuh2AE>IK0w?a2VpM(|zW_*to-(!j|TjI+G&EJRm7p^<7 zsd8k+9WY`E!%cD%CyklGLssyRAs$*8-udOx?}iT;!v{W0Exc_8p0fha8G+~ib<8;R zwmE*@8b4o|SaEyF0=L#g->u-y;QZA^_gv5vcUt03qnW=K10Q(j_bjM}IB1H4mN*EE zlbvTOM{msuu-o7!Z~{m|bd-mLQYXj~JEW1eMRJ@-MxP={wT||tsy9i-q55}C-77W9)a)##t_09))@!Rc9Y<$WiC+A<6r}^Fs;ZrJ(I=+nb23V z9d3Epk5DW?&5jx%u}3@FD&RcOF|o(?hK7by8*S_1{G;OlaB7^y8mV2{{8X$xis!;SU?%-}w8!8+|L0zWLbg-i7qy z;8$C}9W)}7W@OTeOxD7^^Zaf9!WlEX*$Qv&DAwI>X0VRh$bA;MLO1&AE>he5KTlDM z_Rnvx7 zaW1v~;RW@swz%8abJ*xVV)h@g`j6CNo9EBney2KMY(Heg#?9Ec6&tTdhM28&)J9f! K(pF1dz<&Y7{fwLd literal 0 HcmV?d00001 diff --git a/src/ai_reviewer/__pycache__/diff.cpython-311.pyc b/src/ai_reviewer/__pycache__/diff.cpython-311.pyc index d4c7db85bb2bd97a70d11592e4452af449e2766d..02caa6b8b08011b986fa59b2f7aed0144fa5797d 100644 GIT binary patch delta 2035 zcmb7F&2JM&6rb7kde?URL7cVYBqnadmqTd6M|*04e2_v=LkdJyNQp^2>#$&KYjzD3 z)^ez#HU|!&i<%%>$yFst1qY;DDo)`)u#qdQRgt*#)T;6Ysp8Z(Yb7DARH>`o-+S}s z?c14o@Ar0b;Hwk9hhDE6!SzzSrT!Ye<7>sCJG=kr-9eh?9p~I+Cy_yV$Sx8A*+h1e z4v=25hr~(Oyl~D(xHBl;y?Owr-O_s)p(Uha^G~s@Sm1RSu3rZC07j2CECq+oug@W5 zAcHp?55V$h!}>XgP(dg-4dE7N2xpMtEQl^dFyRbDc;Yay!Oe;VNf$QbT-tcsC`v>i zPU3tlK5#%-cp4W*nLroPrS&mFI}qhDnm`%HHFTYyK-cj9U7GIMjAl4D@^&!)MP-`2ZE$Kj$>w#7Nrh6&lTxXsCqT_o z%VlpVk-S1Q7sQL1Q@)*3zkoJ}UWWFr3{^t=tD*gKr?w1!leC#^ppT5Bn{44bX~BNGyW=>HYt zwG(;5i&x9x{!%+Yxv#(C9;mtp%8mhT8?5i?JN+H#S%v$%i_ME4n}Wd0Cnk)af~CRl4Vu({YCDiZL!kG}s$#bab(pb9s7Lu}-{H_lmZ zO`oQ^#*#?ihY!U$Iu62eZzfs;9-vr^wt%3=*WqJOv>|{wRBM(O<++iX=&mCy>|LBZ zxODiN!=-p7*i#Mm*tVL~{6dO6mmG@n*nbdR|Gh(CJjM5VW{d|%)$J$yxu)t*rwqM{4JcQ0C3l4(=IaLq{M|fbT!*JY3 zL@)$61s+H!0#BXhnkVdX5{bBo>#^G`H%0e(t|IC+#7))`oZ^xkM(hE`isdZW0d8O9AL3rn&|!89I9-EvYz%cpgXb(C1F7_+ZN zwob4wFkm6IX8@3`Z9&LYOIIBQgo8FLNVV48b>w%!5Mbf>tAJb!wAgp37HlsEy5TNK z4Ot6BYk`isUkdsb246R$Q0(qhX>cXKcD6irwk#LNEAn_%9tRHj<*Gkc8ZP_0;jV?F zcMGMNa(GWUu;qT`mv0xKNW2GY(XQ{#zliodkM>of2ddEni|)Vboorb#)D&23D|_0@ zj&{3LESKGTbmhAK#+Oz#eR3F@)e!Wf)mQ#50jF0Tas=bc=JwEWteLYRb7T^B4{7m& zptDRDq4;xYRZpuK=w{BeodfzAOhG=_1ZC8;SwW}R3r&S!zYg1DoOwnC3t Hi5>YHhk_0y delta 1167 zcma)5O=ufO6rS1D{&=O`m0kTOvK`q?EgM59lu*((shv6rSR%!ZDbA1VdRERR@=DCE zoMPlyEukk1k>(UEDVsk)0KNtYBCZAyUxT*+3e>pU;9DLENL-ONc!y^6#&=j|2CzgUd@nKw=A z)Bjlt0xLWMoDi`>>5B)#0QeswrJD4LNa^1q9la+Q0P(7MJ$pZ`v|lAb+T@Z;sKk)WNT^>dB$5X6mY>Ue#o!wG^K^P;K7={uwMrH6@KwEOm6f`2?;i(Gjw@3E{%0Oi^!r#x3Xbuq z^HM9BcJsG^Nde9`!-*t>m&kbXEWAwYB>C_Ix}Ek{r4E10xJ0{GDVa{mv<%x9OWp3! zn6rc(`-bUUr43{2xSwr#i$RX<3*iflFOl=9L(v6BFES`FSY&XC)KkZV>y+l3Kc);0 zE;bLP?{e@8c|0^Bc2g|U8Cn$9Xv$9W!tgin)I6nGh)c|g`>$E099s-D23_H0wyye0 zp-`;Sdu9|0nEj*pEU_}DL{<{7kw=-dvQ827!p2@)EAk4!Kcm<~*IWv0lAIeB8 Ar~m)} diff --git a/src/ai_reviewer/__pycache__/ollama.cpython-311.pyc b/src/ai_reviewer/__pycache__/ollama.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..416f42d70b9071a54eb3061fe8979e71dbdf7144 GIT binary patch literal 1781 zcmZ`3U27XhaQD7+mL-40k)5v=CqgZ%9UrB22_=-8LR$LZKIEZ9DIAA(ZJ#;aUG`RS z981Ixc~FZ>gDI$x7WCvgB!43RKq`WYgMy(@$eV&wFs2Wky|ZMic4u#AXJ=;TV|V6$ zO{bFx;QN0z?WYn#e~L{Hl$gN#p8`=w1QG0@5-wmYXvvXEazT!0*-;9Lgg!?^d5nm< z1D2k{jG>?s`8LXHuf#Tw!(q#HJ#KN^a|77Q3-0=2@klpXj!xqJw_#gH6csQ*1&Jhy z`dBH*WSHo%Dr5+(roz-$j$@Uq&m5b&JXBV^fQNFxS*S6}%ghaxMbD+hemG!Ljwjy# zIC4B9wM z#Vb*R(EEnZyj5D{fw63JY8gexzHXJd$LM{Vt{DL<8kTLwJIV|%%5KhI4|UVDU7MTc z!_3K)=X&fR;qD@!P1MO;tl!Vz7^QSy0FTt*Kx zAYhO8Z$YAUR6(mf33>{z|BFl`Pg+5p}RSl{P#vm6XcQ}jQM}e{?J&^C9HK@ir2*apojiZV^wkxO|n9uvH zNKfr=PY{E8)dWu;3oD6+c+FElhDdTSzQLTKNQM_5rVY!tjb-XmW^sBe9Oks-3%blG z6E~Ju*aR%>JfKj6Vxm?lOn83OBd=}>4IC!8U7}u@1F5VAo*U|n20kHh8bEz|Vr89ZC zb0OEb*PP0ChtzDcivZOUf2YvM#MU>BYt3}7mCm)(xh}#(AMH+drrxS=Y;QC^*!}v+ zMr&%mJvCqZqAP^*(#cm3*b`qh@6ZR^>lp6!fI{ZRd` z`t75wM>XY-(Q{8etl!wa@za;PQfp$SJu%Z7oo$cKHuc$H98!IDVIeKQOe+iHiI?Xw z;6-?7QAg3;Gtsb2{C@LVeD~s3qZ&9N7reYGn%}ib)HK7SX+i^+9YLo|^Iq9PoCKGKP zYJAY~=b!K--4!NW#%VsP`uFA-25Lyx$*bZtthLyCkQyVf5{)@#m6qP$!( zyGy4gC}0ivAPFkO0s(BG3BZHlI&cm?6h)3X^am^h0$D6zz(7yF(RB}n&_mxW$rULk zg*=>n^JeDF*ZbyQW3dQ=@~8h=`TqnE`X}pj3-npRZ`J`mL=;i1q5@vPnBkntEpX8C zDqj#51aC}G1BKv15F-vLK^pij_yi;L48C^Z&}WE-9w17da4-&EyF7vBS+|20Li7NQ zJrEWM9iee(!_W>x8=>4alsfo|QKqmf%9>U*WiwyYj4brYB~#Jm+o}S1n98P{Q)R=r z4!z)#UaTlu&OZVWzTLrZE(7uqDQE#xw7}hfM=Ct!&cUdM@jfQ_Sir}E9wyQ-Tu-18 zxL(u^UCe7VuPwX0Vd}21lvfp3SSf3F+>oK=OC`m0Lj}b!>X=Il3rXpu@YJNs8 zn?+r@n^(Tf7iNACV%8<=()~-$E;4M*Tl4ql9l=M2 ztjpHr`FD7nheQb%Qb(i$*~$kHS1tKt*$dWi9_j87vCa zt>6(G%o38Ucye^(RH5aPB6XRnzYp_F!iEgMLNCOjjgPiIvgVwl0z&YLn|9uYc@B4>=^dO*TpG;6`jKwjOiH&~|JyR-+9v)+A%L zR~_+yb)_xWFdWqdR*!|lTjUHE`sszAzFs>7Q<_}ig#VbpSdmYTKX6eFc&IGG@mmiDaE;5`a}IU zFl;cPcX+!KImZ++S1gpySAN|#W@*(ZYUy)oF(<3Wc|f`Z@3F=207%)? zulQY$SFhLs*Wtt str: - cmd = [ - "git", - "-C", - repo, - "diff", - f"{base}...{head}", - "--unified=3", - "--no-color", - ] - result = subprocess.run(cmd, check=False, capture_output=True, text=True) - if result.returncode in (0, 1): - return result.stdout + base_candidates = _build_ref_candidates(base) + head_candidates = _build_ref_candidates(head) + + stdout, stderr = _diff_with_candidates(repo, base_candidates, head_candidates) + if stdout is not None: + return stdout # If the diff failed because revisions are missing (common in CI when the # PR head/base aren't fetched), try fetching from origin and retry once. - stderr = (result.stderr or "").strip() + first_error = stderr try: - fetch_cmd = ["git", "-C", repo, "fetch", "origin", f"{base}", f"{head}"] + fetch_cmd = ["git", "-C", repo, "fetch", "origin", base, head] subprocess.run(fetch_cmd, check=False, capture_output=True, text=True) except Exception: # Ignore fetch errors; we'll raise the original error below. pass - # Retry diff once - result2 = subprocess.run(cmd, check=False, capture_output=True, text=True) - if result2.returncode in (0, 1): - return result2.stdout + stdout, stderr = _diff_with_candidates(repo, base_candidates, head_candidates) + if stdout is not None: + return stdout # If still failing, raise a helpful error including stderr from git - raise RuntimeError(result2.stderr.strip() or stderr or "git diff failed") + raise RuntimeError(stderr or first_error or "git diff failed") + + +def _build_ref_candidates(ref: str) -> list[str]: + candidates = [ref] + if ref and not ref.startswith("origin/"): + candidates.append(f"origin/{ref}") + return candidates + + +def _diff_with_candidates(repo: str, base_candidates: list[str], head_candidates: list[str]) -> tuple[str | None, str]: + last_error = "" + for base_ref in base_candidates: + for head_ref in head_candidates: + cmd = [ + "git", + "-C", + repo, + "diff", + f"{base_ref}...{head_ref}", + "--unified=3", + "--no-color", + ] + result = subprocess.run(cmd, check=False, capture_output=True, text=True) + if result.returncode in (0, 1): + return result.stdout, "" + + if result.stderr: + last_error = result.stderr.strip() + return None, last_error def parse_diff(diff_text: str) -> list[FileDiff]: diff --git a/tests/__pycache__/test_diff.cpython-311-pytest-9.0.2.pyc b/tests/__pycache__/test_diff.cpython-311-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f12f2870f5091ee2b3061b111562c2892c29542b GIT binary patch literal 7916 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 EUpdwgW&i*H literal 0 HcmV?d00001 diff --git a/tests/test_diff.py b/tests/test_diff.py new file mode 100644 index 0000000..2c6fbf6 --- /dev/null +++ b/tests/test_diff.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from typing import Any +from unittest.mock import patch + +import subprocess + +from ai_reviewer.diff import run_git_diff + + +def _completed(cmd: list[str], returncode: int, stdout: str = "", stderr: str = "") -> subprocess.CompletedProcess[Any]: + return subprocess.CompletedProcess(cmd, returncode, stdout, stderr) + + +def test_run_git_diff_prefers_direct_refs(): + calls: list[list[str]] = [] + + def fake_run(cmd, check=False, capture_output=False, text=False): # type: ignore[override] + calls.append(cmd) + if cmd[3] == "diff": + if cmd[4] == "base...head": + return _completed(cmd, 0, stdout="ok") + return _completed(cmd, 128, stderr="fatal") + raise AssertionError("fetch should not run when refs exist locally") + + with patch("ai_reviewer.diff.subprocess.run", side_effect=fake_run): + output = run_git_diff(".", "base", "head") + + assert output == "ok" + # Only one diff attempt should be needed when local refs exist. + assert len(calls) == 1 + + +def test_run_git_diff_falls_back_to_origin_refs_after_fetch(): + fetched = False + diff_attempts: list[str] = [] + + def fake_run(cmd, check=False, capture_output=False, text=False): # type: ignore[override] + nonlocal fetched + if cmd[3] == "diff": + diff_attempts.append(cmd[4]) + spec = cmd[4] + if spec == "origin/base...origin/head" and fetched: + return _completed(cmd, 0, stdout="remote-ok") + return _completed(cmd, 128, stderr="missing ref") + if cmd[3] == "fetch": + fetched = True + return _completed(cmd, 0) + raise AssertionError("unexpected git invocation") + + with patch("ai_reviewer.diff.subprocess.run", side_effect=fake_run): + output = run_git_diff(".", "base", "head") + + assert output == "remote-ok" + assert "origin/base...origin/head" in diff_attempts + # Ensure the fallback only succeeds after fetch. + assert diff_attempts.index("origin/base...origin/head") > diff_attempts.index("base...head")