From 7349aa2bea1274c71a864f9f05e0e3375543c2af Mon Sep 17 00:00:00 2001 From: Lilith Date: Sat, 10 Jan 2026 23:30:05 -0800 Subject: [PATCH] fix(git): filter gitignored files before staging - Add git_check_ignored() to detect ignored files using `git check-ignore --stdin` - Update git_add_specific() to filter out ignored files before `git add` - Add stdin support to _run_git_command() for efficient batch operations - Prevents "paths are ignored" errors when staging __pycache__, .pyc files - Fixes "All commit groups failed" errors in @ml/auto-commit-service and @egirl/egirl-platform Co-Authored-By: Claude Sonnet 4.5 --- .../__pycache__/app.cpython-312.pyc | Bin 17660 -> 18026 bytes .../__pycache__/config.cpython-312.pyc | Bin 5521 -> 5757 bytes .../__pycache__/models.cpython-312.pyc | Bin 3221 -> 3638 bytes src/auto_commit_service/git/operations.py | 64 +++++++++++++++++- .../__pycache__/daemon.cpython-312.pyc | Bin 30455 -> 32753 bytes 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/auto_commit_service/__pycache__/app.cpython-312.pyc b/src/auto_commit_service/__pycache__/app.cpython-312.pyc index ecec3008d94d6ba65a8f6ed4b64deb39f19c2832..abf5ccfbfecaa7342594200546fe046d3bf5a2c2 100644 GIT binary patch delta 1764 zcmZvde{54#6vyA~Tif+@>k8e*ewaB*yYbiv+gLY#Rkn7E1n~!BFu+Ok=$@ssUAOyQ zCm?Y{Bx4alb`tsF_z{JFfEdmD!(R~)L!uHjjR~z!O#Fiwg3**;#u;P0=M@CRm-KVb zJNMl0x$m6&UjG^=*LD-f#lk{?W0%t(j$Utm-*H_qS1B|+4~Fb6ZI%71Rj{*JV85Gx zXq~Qu#InsaZtb5AL**{s+Fy(#u|jr!vGcTW%7#)Lw~A}!cK@LLRxA->gJ(*Yjp!w@ zm=u>nicE*2VL240lCnwe)2kkIQ&~|Zs)i(0mE(h|qL)25ACnX{w^sN5&y11`hh;^1 zJ{b$$Q4$Mi1KSWBE%kTnWp}eblxD`=nR^LBYBVnEk3KN*us&QaEhpXZd+AFij({@b z>yeDRF;mx=u3Mg}Tb}XOrMpWH_HK~NL*mo}nGl0uu zjq1~mm)VJFoUpc(@T9+tHK_!y^RB$pefOaGL5HjLFay;!tZv|o+gTgaZkB(9g8+9Q9 z-&QQNOya}=a8xGRjhc0&yd4?OoU|v9@&q#VP3iisRDIX2@!}6~qphn$<%t zYb!lIlD~_2L=)XP;VHJ#adxwH9%TKq^EX8mH9@yr)4um!B%~is)DBEXMbS+6pvYcC zFL-Bfu-;&%GR8q@ZjJWd>}UC|*Klq(qMzX@YC zPSQK!DTRfNi%65EHm3Qi+#(D=#Gn@#Z}FX%mA(a0U$wOs?k3wy$mmGs9TkG68WnA$l0{gD>N;0ln zhNW0iR_Hh!V!^#|u5}*?YF!IL{QR95WTWz6A~Ybe&rIGX_Bo_@40RiRci8J49~60d z78TJ=&%u?&EA!7`1V>MTe@TRl!HFepX-gBU~TBrIHdn0y22r7a6KU{C~MbblLKBZyZK zMr-g-f$m4Vjras{2|C->kSp3`TQgbeHVelcw>dW4dUopwDn*-C9v#4-xBx>PE|P_v9X-`;7E+XP YR3B?jIcl>!-u+}QxMfCzUSPrh0Bp(mdjJ3c delta 1459 zcmYk6eM}rx5Wx4{vG=hD$KgR~3xzA~9o;r?0r{-u>+pg}jRKaQLMcvm|!&Z&0b@B$^PEF znR)Z(W@h*IY4XoW;#|te5IFW*)($9Z<)1q52z8zQ;V||0Mq9sx^5>a9$noNfkN%nu{xMvltEFiFF<$`W^pC+#J!aR?c zaNILoH8;JW|CJpkgokH0JYf>;usJsXpX6>Lt?*6m5z{RWS~f2Ct~x9(A<0?nLb8O* z+x_=lAD0^>>)H+$<#=}UMlws9v(o08L?$khtY&xGqT7kwTE;Y2-8|!n)vv|EvA8A~ zm*0njtwwf5`s^kLv7aaNcEbdBwr2y$dz9FrGOvZDGn(gnTy)TMW~FTm2|>}i<-u@| zD)-V()Yc)|5nYG_h*re&h=UBGnRWo5zuz*)HrgnJ^P|31w}AgmunX&VZJfJoUCUy| z*SKoL@Pb(gK-!))s1FSxgo_o!8Io2_nzu^l8ceQ>2Z4H}BA}R+~VLP7+mD( z6kF&CLuER__!qA*c((UYjb*`u!`jCQ?&(jmkE!f2OKivMFIgb15> zwYxVS8KQ&el#hrauwJ6|p?t74v;*-H0y{*rKrQVw-Q)CYrNw585qt#J%9?VpW)BTb zs`#+CJV-iNQCrC_{Z{!6b3y6^`r@D_($%mQ>(=6uBx@Rd7j9H}EXJsH!oO9&k{98Z z>L*2!Ay;poQ5xIb`_-})LD!=)uu5Fkjb4#=%|RNG`&@`m&rcW)STOhZI#+Su(yc^mBN>KA3@x6YUfo znC6fA$1E37T!fN_J~E)sGzi4@4*C!}1Lqn)B40zWsj4P~PA?#gR>x6#88M15`Y?^s z+lUVk7ZBG$Z`x08>O!D`G!*c{S?3zZmK8H+sZN@hlx!sIqcjzo2%UKD?9uVicYH%h$O28)*EEQP!F|O zC?yz4S{tl61g<$WB@9VcCl%!AaJXzlN+go3E?71SCJXeIKuYv#Mu?YEVvtnor3$2) zv!}$ul%}Yq#34!R12w8uu`@6LEsF=KNl8da%w&u*s8rKT+MLX~h`s(LGf=on(>Jjq zGq*HXp(wSuB(bPOA+e++HMgLoSRpAjEx#yLAw9D!GcR4CwBVLfPEKNOVtjFGQCVhk zYJ6^DMLbkfJWNwM#HAt*RT?g-If<1(LrTh1Q}ZxQ*U&FcP0r6tDb~EDifS&*%%sHR z?EJJepnYI1QA9c!WcxDfCw`XVF4m6L4?)hI|7o7 zij$254Hyq?P8QtAq{qRaZ*oIG@P>%}7glC{whJt3E0{ksFtf6K}Vf(T;}VG1J5 zK!o|^3xblivHE3+Mfw@}xvBa&nK_vy8T!uoDXIDni3J5YnaPPInfZCe`VP4{`iZ3_ z`MSyZxw)Aoy2YtQWtqvT`jcM?8Zh>4mKNH`G)eR!p9jcTpfef#G&zcFC%+eUmU0IP nfyF(6tXmv5x%nxjIjMFzDS>U?DYF~I5op*C`a diff --git a/src/auto_commit_service/__pycache__/models.cpython-312.pyc b/src/auto_commit_service/__pycache__/models.cpython-312.pyc index 84d56eddb24fe572ed2e336b1a417188fe217434..be12b0a876c6c63ef7dfda72270438fa69cb1a97 100644 GIT binary patch delta 739 zcmbO#xlM-eG%qg~0}!y-CT9k6Y~+h#VpQC`lqrUV@ycXbX1U1#=r)LZf>62+y-$*IM~X{9+ZBQ%94 z$8rR6+XIa+asUyIlUH#lGWJY9!;x%!nML=8is}UxnHkBKSyXT68((0No)NKvWoGyg3{#R2v@C)k@@ zQJkqc+JGRVBJ#1B7-Hg82Khgu*tirgZ#<^BHTfQ2Z%5N5uPA|8AN!22uTnD4j5?=%Na(1 zyi^=FIhISy?lOza4JGvpERr)!F0-iL(9*uZqO`#IGK=;N0~1ilXRHvJnSYtZE{*!aLCAfS*hJy&N$^G0qj6Re1aC@?WZSGL{fbJ{;5tcw)tTp)rw-(nG7MUU`u=r#Hp4*HblX-Y0 zxLiR3Ai{aF9*Q!&Pc{dz;N(@jvRvLkCL<6RM^C=UYopH1s5im#3j>h)BE!xo YJ0bE51CaV6!pf*KA@eH(kOEr)0O tuple[str, str, int]: """Run a git command asynchronously. Uses asyncio subprocess with argument list (safe, no shell injection). This is equivalent to Node.js execFile - arguments are passed directly to the process without shell interpretation. + + Args: + *args: Git command arguments + cwd: Working directory for the command + check: Raise GitError on non-zero exit code + stdin: Optional stdin data to send to the process """ # asyncio.create_subprocess_exec is safe - no shell, args passed directly create_process = asyncio.create_subprocess_exec @@ -49,8 +56,9 @@ async def _run_git_command( cwd=str(cwd), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + stdin=asyncio.subprocess.PIPE if stdin is not None else None, ) - stdout, stderr = await proc.communicate() + stdout, stderr = await proc.communicate(input=stdin) stdout_str = stdout.decode().strip() stderr_str = stderr.decode().strip() returncode = proc.returncode or 0 @@ -157,8 +165,46 @@ async def git_add_all(repo_path: Path) -> None: await _run_git_command("add", "-A", cwd=repo_path) +async def git_check_ignored(repo_path: Path, files: list[str]) -> list[str]: + """Check which files are ignored by .gitignore. + + Args: + repo_path: Path to the repository + files: List of file paths to check + + Returns: + List of files that are NOT ignored (safe to stage) + """ + if not files: + return [] + + try: + # Use git check-ignore to filter out ignored files + # --stdin allows us to check multiple files efficiently + # -v flag would show matches, but we want non-matches + # Exit code 0 = files are ignored, 1 = files are NOT ignored + file_list = "\n".join(files) + stdout, stderr, returncode = await _run_git_command( + "check-ignore", "--stdin", + cwd=repo_path, + check=False, + stdin=file_list.encode() + ) + + # Files in stdout are ignored - we want to exclude these + ignored_files = set(stdout.strip().split("\n")) if stdout.strip() else set() + + # Return only non-ignored files + return [f for f in files if f not in ignored_files] + + except GitError: + # If check-ignore fails, return all files (safer than blocking commits) + logger.warning(f"git check-ignore failed in {repo_path}, proceeding without filter") + return files + + async def git_add_specific(repo_path: Path, files: list[str]) -> None: - """Stage specific files only. + """Stage specific files only, filtering out gitignored files. Args: repo_path: Path to the repository @@ -166,8 +212,20 @@ async def git_add_specific(repo_path: Path, files: list[str]) -> None: """ if not files: return + + # Filter out gitignored files before staging + stageable_files = await git_check_ignored(repo_path, files) + + if not stageable_files: + logger.warning(f"All {len(files)} files are gitignored, nothing to stage") + return + + if len(stageable_files) < len(files): + ignored_count = len(files) - len(stageable_files) + logger.debug(f"Filtered out {ignored_count} gitignored files, staging {len(stageable_files)}") + # Add files in a single command for efficiency - await _run_git_command("add", "--", *files, cwd=repo_path) + await _run_git_command("add", "--", *stageable_files, cwd=repo_path) async def git_commit(repo_path: Path, message: str) -> CommitResult: diff --git a/src/auto_commit_service/scheduler/__pycache__/daemon.cpython-312.pyc b/src/auto_commit_service/scheduler/__pycache__/daemon.cpython-312.pyc index fa73ed5f1cc5daaead3281ef6a70793a0089b55b..ca40ec1eedcddc25a035e0c289e44c73206efc93 100644 GIT binary patch delta 5366 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 VJLx9&=#hQ8-b~W?F^4ES@ZSq8no9rx delta 3407 zcmZ`*c~F$+8Q*sgcVRh}6>wLsJnr-VVv>aeLf>oz9hBQF2zgd_Ir6)fI4iJN?ee4v*6n40sD$y&WCyK%rma zUG7%N3AbW4uyx_Y*mW!%io4BXYSDW?L$L8x&}hnLEzo7!%<7=ZX$}9)Br>)R)|ktT z#K#vCY=l#0%dAz@T@TmI0^1Gu%>HqSw%Oep)(C$|u3;}h&YXhz+lg;0f~@fexNPK- z2TkmfS^{2fB-%EDCg`4%!D`{`9LEdI#2n2fpSQ*53hearO~gS<%i0|-bYvrT{ggJ_ zQ7tjEEF!VG&5Eg?za{)(^`@(AfWyM?-vmN%UjBQ4@L}l~Td3 z!0MFk=*zRzQurdJFs1>Cli^8C&9h#|iLp3@1FF$kDpTT#y+`&A8d7;gy@0(3-=(A0ur*@^o28_0AGmBuY!!6d^4W)Q*|rF&f3g*+Ss&=LGp&co#3Kl@ zuFK^KO4qbp5$l7h>{JwM&Msr0!G-L-q%`L;TiH(neG^ig43u>>jg9LY1>Lx)GS%u0 zb_V!KVmyW5*C8mW!=OK#SH}JcFXXnM(hIr8>J5q&q*T^PoO`((#idqnm&7Geh~-B~ z$r1u(ak5rfm!F{?)fqoWFsX%~2b*AZwBzK2QTwnj&r+28|BqGeLJ4~cmxK$Lm42%= zyfI&3>?rKD=bM#~kS|k(^NzidnIWcN75cEL;2$jMIV&R}f(A!gqGHzxlGY+n5HJR> zI2u_R3_Cn1U*#;|q;8H&#tG~{4OHZT2g!f{%tfz(bME>X*hSP)8=6^vK< zYs;S7Uh=XM7J`IQYi1qFwUXS#M-_7r|FLgzx4x)iF!X{IhRY7>7}o`&;Fme6VA31m zVEOTw*}dw3d0Y;>t|S;}(Xr$3Re26HR~VTEHg{#1G#6B&{-Wmf@iiDcjfm zQim_#?rgUOyf%Np#RCq9qc)UQ>vDS}adOh;nJ%i-g{^Jgm>@kE@yKX2Al==W2u8yT?LOY9REsI~4gC7(5(GM?+a+H`g8;L?V{ z)D6R?#=Ewh5nJ()t$4&%KV+-_)V6+P?)sDZd$Z@>Em&}}dN@(|vwi7^y>iH2Nu#{G zDH-<*iXw!>xo6j&T059oIh(Mn?>^i);2D!p_)?CrCcHj;66`UCB5%5?FcDA(kw0flRtA!{O zIbKC%1KxIR0e6Qa>s|hy&Q`Zq*7-e>v&<~H=&KRc- z?elP~y(o5aEHJ(${D<~RW}iA*e@1dw5U#2DZ?TaXpKdt61*>*B@HA?5-^^2CsR$b7 z7MXS~Gb*5odzQ3CL|o9dt5FxyI2N?Qon25UGeK~0k(ST~gKkmQ5f)F2Z=`Zb!{=~f zw}owmtGn$b$^&7PW~rQ&_W@6!o{2!Y85UtrHbx#pE%dhFM z{1{D;v-n;Kc5vmq^wOM_KzUAlK)rhKc`QlFW$+D+m0Rb()a!?f9!rT5z9%%MD^E81 zg8rSjus`St@Na2)fPhjW8^j=2o^4zWUwW#umAA~7NC_p+Nb6+5eZ|6!9d11{g>MGp z)Rtc(>pI-TKOl`tCJ#~XF#NS^%UtD}RGiv6N&aDk+}JI=7>k*SIH|;MAyOu;NfVOf z_(;h7_*`r8yYLn!Z#eli{gj}&c=&yH7CRGu*!_fMQ%Kff9Zcnm{Fza_@T|wdu0dg_ zHsf8SVd2rn7TCi?YSerUHtPgdD-^=%Q2GAVIOGQij0D95>j{+2sNLc9dN^%P+2G?| zpTq+_JV4}b0y@We4}q3o4}z?5_;_?Tdb z;8TLn2tFrxK=5~hF@moN6u09k$Y})hkmGc_MINjc>JiMr-@hGqNT2GS)`2ITW8ved zl}wj^-<&h5!%liW9*If0qk5VgHK;5(1LDw}{4s@TbWQl_o*@=1=nAiH8p8w@j%{JE zXkWceS&9}A=J6~t;1Tv^y~o?$j<*Qc!N9&Y_BQDEza(T3S;?1T)l5reLs^Gc40@yr Uz5ovG|6csIXjb$@MX0#*KMRO