From 1ad3da6196fcbf95a52a5cfea90b721b8c4e6d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Fri, 5 Sep 2025 13:40:49 +0200 Subject: [PATCH 1/3] Fix typo, add missing slashes --- gix-blame/src/file/function.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gix-blame/src/file/function.rs b/gix-blame/src/file/function.rs index 98ed9790cd5..8d914d3b7ee 100644 --- a/gix-blame/src/file/function.rs +++ b/gix-blame/src/file/function.rs @@ -15,7 +15,7 @@ use crate::{types::BlamePathEntry, BlameEntry, Error, Options, Outcome, Statisti /// Produce a list of consecutive [`BlameEntry`] instances to indicate in which commits the ranges of the file /// at `suspect:` originated in. /// -/// ## Paramters +/// ## Parameters /// /// * `odb` /// - Access to database objects, also for used for diffing. @@ -55,13 +55,13 @@ use crate::{types::BlamePathEntry, BlameEntry, Error, Options, Outcome, Statisti /// /// The algorithm in `libgit2` works by going through parents and keeping a linked list of blame /// suspects. It can be visualized as follows: -// -// <----------------------------------------> -// <---------------><-----------------------> -// <---><----------><-----------------------> -// <---><----------><-------><-----><-------> -// <---><---><-----><-------><-----><-------> -// <---><---><-----><-------><-----><-><-><-> +/// +/// <----------------------------------------> +/// <---------------><-----------------------> +/// <---><----------><-----------------------> +/// <---><----------><-------><-----><-------> +/// <---><---><-----><-------><-----><-------> +/// <---><---><-----><-------><-----><-><-><-> pub fn file( odb: impl gix_object::Find + gix_object::FindHeader, suspect: ObjectId, From cdb1100d7ec85868298c5ebe30d2bc5711c4b7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Thu, 4 Sep 2025 10:10:52 +0200 Subject: [PATCH 2/3] feat: add `Repository::blame_file` --- gix/src/repository/blame.rs | 32 +++++++++ gix/src/repository/mod.rs | 18 +++++ .../generated-archives/make_blame_repo.tar | Bin 0 -> 76288 bytes gix/tests/fixtures/make_blame_repo.sh | 20 ++++++ gix/tests/gix/repository/blame.rs | 63 ++++++++++++++++++ gix/tests/gix/repository/mod.rs | 2 + 6 files changed, 135 insertions(+) create mode 100644 gix/src/repository/blame.rs create mode 100644 gix/tests/fixtures/generated-archives/make_blame_repo.tar create mode 100755 gix/tests/fixtures/make_blame_repo.sh create mode 100644 gix/tests/gix/repository/blame.rs diff --git a/gix/src/repository/blame.rs b/gix/src/repository/blame.rs new file mode 100644 index 00000000000..84c0902f7de --- /dev/null +++ b/gix/src/repository/blame.rs @@ -0,0 +1,32 @@ +use gix_hash::ObjectId; +use gix_ref::bstr::BStr; + +use crate::{repository::blame_file, Repository}; + +impl Repository { + /// Produce a list of consecutive [`gix_blame::BlameEntry`] instances. Each `BlameEntry` + /// corresponds to a hunk of consecutive lines of the file at `suspect:` that got + /// introduced by a specific commit. + /// + /// For details, see the documentation of [`gix_blame::file()`]. + pub fn blame_file( + &self, + file_path: &BStr, + suspect: impl Into, + options: gix_blame::Options, + ) -> Result { + let cache: Option = self.commit_graph_if_enabled()?; + let mut resource_cache = self.diff_resource_cache_for_tree_diff()?; + + let outcome = gix_blame::file( + &self.objects, + suspect.into(), + cache, + &mut resource_cache, + file_path, + options, + )?; + + Ok(outcome) + } +} diff --git a/gix/src/repository/mod.rs b/gix/src/repository/mod.rs index 92a9545e3a6..869616f7948 100644 --- a/gix/src/repository/mod.rs +++ b/gix/src/repository/mod.rs @@ -19,6 +19,8 @@ pub enum Kind { #[cfg(any(feature = "attributes", feature = "excludes"))] pub mod attributes; +#[cfg(feature = "blame")] +mod blame; mod cache; #[cfg(feature = "worktree-mutation")] mod checkout; @@ -61,6 +63,22 @@ mod submodule; mod thread_safe; mod worktree; +/// +#[cfg(feature = "blame")] +pub mod blame_file { + /// The error returned by [Repository::blame_file()](crate::Repository::blame_file()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + CommitGraphIfEnabled(#[from] super::commit_graph_if_enabled::Error), + #[error(transparent)] + DiffResourceCache(#[from] super::diff_resource_cache::Error), + #[error(transparent)] + Blame(#[from] gix_blame::Error), + } +} + /// #[cfg(feature = "blob-diff")] pub mod diff_tree_to_tree { diff --git a/gix/tests/fixtures/generated-archives/make_blame_repo.tar b/gix/tests/fixtures/generated-archives/make_blame_repo.tar new file mode 100644 index 0000000000000000000000000000000000000000..3aaf798017685dcb03769e95d787ff15d640c178 GIT binary patch literal 76288 zcmeHw3wUH_d7f;i1&tw?)L_-_FB7(7uLJmU2SZ-yOz()oRP*W z&5Y-=TCrXN0fO5Ag#dvz6bSI-!KEd|1`L$YrVl2V0wvr-NyzhXu?b020)$H`^nJhY zKWEO&NSYa~W-)m5c&*W#|MLCc|J}du|4%qF)c2n#e$tbZL;Q`O*5C47ESgNkqVZ@v zAU^=i$oSk^^PB}?-y?xF|0Gy{Ye3$=dFMAY=U|LMv2VE_BQF&Llw zAE`L`Y9Ht7eg9+8bSyU5|32=CJG8g|ktN5@^@j?!u>Y}idLaMZJ?iK0mi>>c*oESD z&&T~wC6lds-~Brpos11N>;G6h9ZL^Iw;Pc4#(RwG|9mzPpPr6RIr(&aI_Ja^Q|WXD z|BKnvvB^v-mWxeiqRGs3U_IOozWas8K!g8l_F{j;zwmi)_FpWPN~Q<;{}ND_?0?Rw zW-Enqtx(!_+0P|(lK(N-dad$5o`xTI3D*Bu_y0~5i}s3>vno!xR4vp>m9<@#lPlD$ z+ESrvHR>aeQL9$6*aOcUU#IXuKU@l8-Tpa*|4W%Wooua7(qH+%SNWfeCI<3< z`?tGoMF0Lr^8JapZOQ+O4g9~h&FaG!E&Csd+R@2OB9=-Z0+Y!m?djySW2a(HKAVll zqKRq8NyVo#y#dlm|1;*>qW{w|hEjW*W^1h<}+J3sfWtN+orUvunFzIO8Ho36b7;NyQ}edv8>pZVn*zx;~uTc_{4<5Tbb(tmr? zJDz{lTON7E3;*OflT)vM>nE2#zhm_SPkQEYyL9Hz;(fP`{L16@d}8I^r@!KJfBo*a z?7#eVZ~oeSXaC=Ozwx!vwQu~x`yYDI7tem~p_ktH%AxQ5`X7F7;^7CbJ^i!K-1p!O zsRPMpyz@)%NUzS*>tbnyK28R`JWu<|E{omagAE`KazD~ww=!< zQrXF=sr+QriB2IE<3!`J{8S!6yy5KRJ;9y^s$4Vzy97a`yGGz`Om)ozWPU<@7({-S683)&L8~G8@~Sj zClA%1^J@d~yfth;`g%3-e|owv0B%M8r;>yGZ(lZ}Wzd%WkL0qGiF|rGGnI(toKz|q zolHUY=kj(e?&R~6$Oj~1Q#~5yo#cNkooto=sdNhUA}z4?<%R1@x7=~hOP+AWKkzUA?ut);&8|K9Rr~(t zk50Y(8;_hG`#|m)@4fP#>#lmnO^Z96;OpLZ*O_O2__iB9^s(zd|M1-5ul)7X-}?O* zy!&^)Q2t!v&R2f-%*`)bIQfGgKm6M-%&$J>ZO^~rZ6o3&PbTqaG;8PV#AG(-MAI2NmrBHYgRGPN553Z6|HpAAXpsNu4bNL?&HArT z2L5~Ws+Y_@JYKmbcmI$6Jbd+cPW*1|#lQ9LXYKnx-+AKL_y6kDhwVGw@X(q6^uW8G z{KHTG{yky)?@wKI&qL36@oQiI(tqMybJHI@_+PHR>eVlQ)t57`dG;3{oconG|Ht8{ ze(ddk6WsNCPkZ@m-}q-QbYA_!H_qSv58wXs_pbTw%dS~_@PmKxjn{th`Cr)m8`q^T zdt}EG?tStrOQ-+cPd~8sz;B)ozWDsD&O86|DT%#r-O6GHeR{?~6aS}sEbQ!Zwk_HJ zu|fQ&OY~k;vzGmjWbCQzl!Fw|_CQ&v^1DwlvqE z)1waqP5htF_65ML$p3VDVE^}JGg=01+5bp)w}G?_(&5ie@_(K4f2njlF_8Zq@VXt3 z8UOL>fB*c-&%CDgg6KWBe(PJcqtQ=Y6*Tp~-+$5L9%T6EiEp1P1^?-xU;EcDox107 zuRi(_=X;kw_v;Vdc<@y#?^*ieFTdlVw|)C*pPqi-8{hVD^2twq+1oBZe%-gHu6)T$ z?|b0DpWVOk#XtP7so(rbF!CS%*XhF_`ojH(AGz?6$G_#@MBo0=sSno+KYsl;XMg(c zYY*iYSsVP63v*B1jlrsC;bG@sAg z(@riLjb-xbj59rzM^+%7ore-Qug4bNL??Z$uZyQ2~Rx$5Ij zyXoJSFB^LB>cH-;j5+A@i-UnC{x1~sr9ObZ1^YjqK#-xwGQ+Jw{un?1)wKWl9s@gF zVtfnxA4?DPf0x+3xMnT;ADNn*&c~za>GULPzOuR8bS9lmrc(KIZZerk#pAe{)9JBH zq@(_CkN>4ose%7@aXZ^x@6FO1tea}z+)kD`j>;7jx>wAB9#n7SW zJn8oC1|PI~)MG&S|K|OB_w^Cdj{J`U1ugmCXcG6o5A6RwZi*QiSHI8T9>;uD@Bfzj zp9gn2_8;$$mKlWqmrMP+`d5~@1^W-T&kgMVOF<*v{`c`E5L?*)SPJip8Tfzwr-0`D z@6Sshwy^*4^q~H`|C^5)*4zK>ehEYe`!5!Yww?b>qz3o@T$26g-gNV0rKh{Q8p_I+ zR#pnNnp3fMdmnFf&e<#FWhabp_E^(N9Pf(7qfzU6PCcy6tg%9|P%GHW<1#e89L zYXfxAi}Ga?`5%oX+V201#Ru}g3(Q`8latv}#W@`qZoK~|U#hIwwX;s8iq}C|vsN@P z%+~;|lyVOKs#WSvU^rt}_($GeuHr9*5AEe;1d;3IoLzIOZnKMrZZJZ>F(CXucjT5^ z4$d#^-*<5SmgCRf_9;8@KkglG&Hu#tPOw4#XWO@;ZNTind27STW9bXw|D{stOyA!A zwYB$u;f3LY`2Qs!#K!(d^3|0RB5 zAPwaI#Uj3o{yVOX)T@<9rcjKOoyu|`P_H^xwN@!)YrEw0s$D4-ii=hK>)_G3nVBPH zrx@QA2<))-7A+ImwJ1X@eT^vJ5m_kKoW%;PLF-mSOlUr~RI8P%Gm!{Rm^vABAoVTP zGvPuh;?i7X+*-w#7M^gNYT3ctDeGk`=hX0yLO_CXiq5KK7jqVD(n7v~=QL2O;lK_& zo5y=81s1%%vTRqYcxqd&H9MBAS1L}iw!CJ=$7P6RyIRBWv|BOV8K;OL)B>)tXBC}^ zjpCxTF4b%0daWve)pGa?j|q%_05-%Ru!t2byeZSJSxfd=$I3WPu>o|oP|WIkH2vMf z)=|5*RJB%6n2KRCe9PvtT`QnF&>?T#ER||jDKDQ_OO-POu2yjzfw1b7ZK6GAW!4Bg z9^w_Ahff?b*eO{~(azv2dA}zSHGv7t8$I1Oeqy5FU3I6j=1GE}8Jr-Ea%=m~IyPv5q zF6!==u^lz*`24>8$BtRSk*MVky3UxVia{A|Zpq1>5sWEqfI2HALYFcEg?s}zY$?g( zd|)_NaID~oVzpi_mnuZ6uBx$M8O^1xQ5TTWFy^(wfnf_j!2=)#?okG}D+}dh?IKw! z9A>j!feQhmW&~JW;C3#^Ud~#Jj{c373%NpNY#a+=yNXk*tN~Rq^Y@+AQgNYtW^q@* zIcqNy>WXt$y#NR8ImeG2o|(Dr_^!b4h}#MsgYNg3dl;i!u!!JNd(Wds*YA1sD51vm z%XYn(U0QHiS3s=RGgiy@9GQ@pFuB`yK`_Z4!$f_d+E}}e=j6slO5?H%!^(CG6{lKX zuHoy@p7Yt|QufS+3G2r5@}~%Z3l})t1tOGCH1#qtjEzXgLJmEpj}YAg)-t@q+JNLd zn9Yc6T7n3)#<-~b(u5V<6U6_7_}%q|boMZQ1ql^6xlj}zT6@gYYxyal5f7A}@imCq zKzFLB&RMp<*di1_-sa(ec1Axrp+Dv=JX0 zM|6aXpRq=+HPWEN(v%1f7DY@cV9q%VUzI>i##t_{TC2_|7~8Ij(5GOeqO=c++ATP`4p><*&5#ILzZcg@A+wJ_Cvu^p%e-Y}R`!3s+RCSR`rtO}JE zl)7C|RI6Bl`5;2dua+RTN$^2(52X~?1O3L@(K%s7C#=}G<=kZjvq2SF?X86Ig6-d* zk_m>J9+;``y6Yr}M{x{F@&hM2bK*2WyP%LO$;?{2cL#^}AKpixhfk^=r+8{+IDB)y zX*f=Z4#Vyy%vdLbB(C7;3HjQGQh8LWVThAKXeMc8G3SEXr~>deU1hWH$jXIOTNz|9jXV_h363%BV8>vA8rFHHo_t;WJeW(Xz|IZ4GL_`g^(O3SrKI>;aq zukLh0&LpEwvlNQ3JSf-2JO-V*BXlSDta=gVd$rNcntg#4x&736=#edp`1LFbBj{m4 z2%xU+mz{Si6$r0cSSj)hCWd^2-4@n2X4f7oDxzvcn-kaOBS2n>)QxRj;^vo@VY63g ztcQ0(Ge8nTJi-PJ?;N*aX;b$x;74#$N4{{1UWB?BU9rjMRkkS&i7aEr^n)=p7SQ6C|)TXss zDvs9RC$C9=&YUfw6Eq`-ryizT5BJ-#vrAOsg|mfR9cCmIF*K&oX_H+q7NOYnn{73E zwg{Dr+H2jLbsdDUU?ezB3%pssiuM$C_nZl#8ug)CJs4PX6a=oh3DXlww@ofKFQFSr z>rt!OdU|8(ytH_KMskf9f1eH^U=XME_9GORqbt?5>Zo=1-PVZtBD=P@S}2Yh(c0*LX6fNf?cpX5qu0%gV6m$=rY7N ziv?Br*pXID`!S%G{13Y+*IlcmI9MWrkg9n(PNRGxY*{z0 zAzqoc5v!tRUNcHI1AW;9OAelJ%>f!aKqBqL-zLV}!4UPoRUjmUzSbEC+d>;_#a?4X z+Kao}dDeMnD-HtGrDE8(a#<4yKn;uy)}E=@h#z*ld~JijdZ#N;DyV{~ zIY=JpiW$?D75HGnh)782>fP`{xm0CZ4bci8g0O2KJ^W|riNuR;pjm6qcyZ#AqpXHJ zG8-I;1q0xbigOm9~~{Xd#v5M9(>Tn>=}1ko&)K`0{KF~w+w@cpcN|Vy0mxWZyls< znf0o@=)fu6aMt!o-F;wLT%c8O%#Msvw3!S@C{$Z3JF}D_8wi?l9YNV$38t$d{2j&v zt)$v%h}QN^P=J>^^_Nw9BFgmkq(xV~z1HI-P@zgm<{&76kYBb^C{t<#%9YZc@QwKu zhGoJo}pHIq@o-dC|W@WY6bA6c3P53)P_H383S zlvzY_6Q~9{Ly?NFK+xg9^PNh`U=#B%g-RJB19G5R%fSuXjisNB6zgy(?}klojh>8# zrtMJv^r$r&MbufiJZR<)!UPYFa6}&yC|Ct|G<#6T?(D!7NGk2B<;t7>HDgsv4xhf`ZGhME*9zN{`GnFeP~1>4AZ^J&M9#ux}` z(z`i{<+QS2kiq{T>kxg59QN(+JiOS}S&?sSnibok5f~$V>G6sX^pTEAJpafiKz5S- zq%MSTwXr9ms)fB^D4vV=q#`#O^jWqSVJx}Q6Jx1h8%9YbRtS=6!kcc7;>}PA25muw zKEU@K5wVX31`P*Q-aV{%Qz$XDg4nvR{tfeZzX?j~y6Ysc0L2B9umW@<<|Slc?Xen^ z9^Z3a469aM<==pL;Cy7Hi-nRkXWeWgZ&K3m49u)xgJyzW)g2v5Nj+p7 zm_=x$fgloA5XE$M1yPn<2|Af}Gd|y0%1lTm12qA#nUM5Ac^BlJm(3K96r-|eijgG> z9)4t28=5M?inNl95D$3>L`LO~pXwQ?&6!c?j=(rY9J!&~OA zmyzjrY^{x`BD_T(ALW2p(HV?IkQmMxiMt()#>+SySJ^0MjlnE*R?6_n5mqYd*L*Ik z(^5neLKt_!hOF1BTLi;Ot5sliRc%Rwmr}WqZG;`9wvdN$j41?=R-6heKOpKyi_W>) zsA;UBI;0f@bq2r*I6GOJx30i?AeyifmBZtWt7EEJl&Zn-$Rjy}P6oPpWIgr>! zAYi_m<*WhjwxUEM7f`e~8HAkH$PSu4@c>YOKtzqpnGK5Jua*@<8|t6vvpYyV)wPvO zX}OSve9BuqsC)-99q{RBnULGGmVbf;q8Fv|y=B zUA=mcGoHS5T8xtH1yf8Rg{bYBVNNFvf9L8!|Jy8zZIo(sZ2HiE1WDpE$w zI*}YYZyaH$cq@1qEK^5X&ycQZX4V{ICTa&^&^hvOLDzi5rBL1w0L~W*#RdIyR(6Zl zJ*b0DEZ7;U9m>Vg5h~RZ0u&un=oOj)#le-TfIkZ&>I8{K_hPF#1T zIO2mtqXdmFz;Mcp2K}<%EwcqZAaw`tkmO_A&MBdRt%bSOIBnTFSArm?46>FAFkufJ z7yD%l&)zm{SVe|m*8tsX6Xa0xujozm(UboWUY|$1&SY7vwSkj zN-X()rMk#C%ucrj!a64U!(gv^tPLS8TkcBq87=taFk{a$+d}3*lBshI0a!xWMBpLtL~63W3ohL zLfIkX3Q~$0(wbB|i8-3O4@qNKb^lhMjlsKDF=2 z+=5iGNs`DYWhwZsBF?^8w<|eUII08{ne4CicU7V>7(AH|^;*n@1A%!+aHie!EbYWu z1?0t`P*obu1OnDh>j;ksv}g{RASUw`G@zsU&U_L%MtK#0P%u;`nJQ=!#G;_f#~heV zkY`$v1ep$!U||RAla|+Dhio*jCwH@OF##xxPy92uAWU#-Oo#UBQs=14G+|KWX%6sx zre1)+Z0I~R$G135Kq%PXu`A03N?qs?R579QR8Lwc(;1D088mw%2}j~ZY!n#)YqV+w zQK^n?Fk193)|iY64TR7@+hXOOC)5D1zEhHx~1 zP76*J?VZG#z*&<@j+M`9BDm8UoH0Tkz>rW>I;tu`-7jF{EN);TR8l*JZ#B(PugIYh zJPf$$q=-p?tUy8A2noO*4%`aT0@y&VOV*klQhSowBEca-6Cm#~6wD$@&e(Yig;Qb* z8uh@8XV5CZ*uv+5ffk7}MV^N_mKEz!n`*7ja&DJ=G%l-FEsobHd!QhlXrq^q(7$%janQ%@=^ z$iK?>po=n#>_FNg%wS^z29b3hhfmNd15FV)VL`9zTWaDVNffijRzzhuHwlo9!o2@rwM6izj9TDiZ@dzXk2<)A}5~#oC#82}p z6`muc2HXodf`b)sTJnW+<{SkGhcN)d_-er#nIwH~l0JD8AO*`DV1%>ctOocA`SQR`reR~^c`E1?oPTsIh;9kXIHfUjJLRzRF z0ft6Z2|0VhFd)(9TOjNfHn+Ux`GJYI1EYuyhgG+lNq|!PC-E3p2sCX9v1oP#2z1TA zOA?0rxlPg*|CM^J?F|5L9{-J^^snvxPsz!_`yVa|O*F@U_j*>OMspkIxy2l$pg@4C zT!;26N%01wtvrL}9Rn9@7@;U+V5y{0w%~8Arj*2hRkMz=ArHpR;)JkBpo+u#helV7 zBC%8IX2c2kFz6qXEDgBePD$9e>zYB0Tnn|MaIg`FD#9;h{nF7B$8TOZa&-RSk;4mf zM@}4`ccUbQS=@-Uy2OKMCZbtD2@>L@8}=K{vA$m_%Y!&5Q+H-hwM{WnVCcitSx6%- ztB@FqAV^Y!Cf_G9F|Sa|x0H~k|3=Qs5Ic6xUNBWGT5N!710s-#Llb5pV619^KaMJt zv4V9yf(Uq?gXUux?2-`SV3MC327L0r>LAdvfLs?w%z}hip7$H z`#&xLRW#&3f(S|W3Uz@3~JD5L*+CRWaGw<#z>?kI=8Tx5i@|z1+sE8 zT;_fb<2XvpO}wm@JRaOE78e%~7UM{8M(r{yswxFgCZ@o-XBKwS)f2lM73DNp{Ru>w zb_{TdsMJ6SAcLFSo33c*3{z{~sPqm+8(L`;CAcPJi8iPC-mI{j)*L&cEdohQomBd?kaYWeKOSEA)vt_)*4+odC z3~#~OWwZ}=^*Hr7CzSh7bQAFnN90bz3db|Xa5yFgb4#v0Z@|s5Hgc&Z9CX&k2H}Dc zgywXqsHEh+c;0f`ln}bB&XugXw~~kmaeo1=Q)0)^W@X2|89_YSI+tlbmkG13>s>ovFs0 z@M4&E<-p#OI$LlIBDuqITO;y#-gLUt>&&wsmz*HH(@e33Om&G( zL-(VQAGg1loSm{wYdN+nMwI9QAFto=q*&M`2W66RRAovX>PDlG5juzdw*g1j&aT zY$<7J5ev6i18_U)VDsEWV1=nK943J@Lj{IQ3kxb5Z3pLtEWA}2?thH? zI6nKIj(Vt8^0;lAtpASpKTakR>DKd~lauLz{eKC}|Di7Je^Wn6;5C+~bYxn20V(%t7^JjDmg5^BK1Z!K(UWTL8;zehj_8=}B!l41hjo)LC;wv~* z45y(RRwxE9#I|dF)A6CUa{WI|fPb}m zTxXZ=MFzRL4)bfI9)l1}gFv8z=bOZFz{P{mV!C*--zBmyt#RfEmONKX%f3)|(K`=C{6T1cNjJuf&8kFR~<-#L5**Eq`t%pje$9iAgOU3>x@U4UZWm5zV!)?5<=a zGYVymxh$4LL>4nvOX92Yeo4)+$PpQ7FVsa3k>FCLy7UK|m)CWuu`L#jR(0+4>g%*x z@Cf{M1ybV0Bt);U0wl*f$%G$U$5;Axk5elc#DrYDiN?IZOMpUA0}54ONb(H25^NW! z^^qc5g9trxm82KTNY%Bn+6sve039Z8t6s5eH}}DWx>@c_^M%>EkT7egZiQB)*i;QI zlsV(BnF^dmF2h-=<33ME`rx>(5C_s)6aW|rEeRs4pm{t>%W`;1l0G=di8FCqA@G#e zYO5uk6|U3@<-&Tf6u9vS0Dy>5-eG)c$!KE~cBo=-K!UaMFVEf*At)G7HK1RVGdOwL2-?X3aGyQv+c( zI1+~zaAUFDABAng$w?T6#VT)1mFgnYC(tQ2@^TLQo}*Y-ztfW<4M8AJfCHrNiDth& zq>K)^gGyE&|Hm){kIis_=|T=JMrjQ!lEuyIoO80svud5W%+gSGh9+>He8Qm8O9c4Q?Jt9HQ@|){P%6O(K@bz%W*Nq?%C-L181f!8#B|A21A5JbX41L=fIc7)R+qPkL3LcZV&!j3ov@pyaL< zT2tN(DbK@G355!aMeZst+k?EVGm=FAO=>QT&mva`ohWm2q<~cD)i_c?Dc<#lCr!zj zR*nJ(AO6$&Gx&$T)rdajK3>|zbTr%&9DT&57$Vo1NRl8$p~gz7z6e?GBfwJL4Nc4)s6r$wb@xACj>F|GVhx>+c5tZiNH^?2UNpfA zx^i{oR(KT4)M0za+-Y`6y|oS|(;Q@Y1m>2hO`_0nb8v0$FdPlJJ=3+T4Xnm>#~@;L z<9Dw|L=UqXxH$*|)1%?*b0XS=nsb(78uzg1iDQRorfl98*uy$G3e9PHew4RrOT+8O z?7ZcM84n) ztyh-yYORaClqaC>G3{CPi+S0N`Gl34os%zh{snv+V3ip3mdU6H#`wJ+*ONmwGP+_w^aQDHlPcKn@2}V1`T0L$V)rus$>5QOn9Kc znS@;9tZC8W;cZEHjoyw9JoqKR^Y$kXDAA7y}+QaYAAj zhA%llql^wv^Vp&A`Y+Di7|r^h=|^LzgmB(&n=FA&`ad0Sum6gs2k(FCtKv6_w&Js!X|LOyYsm@OTPIZALX^T@whz@s0qs`-~OBj*jZPEA#~kV=Ix{@p`b$_cRg?quGv;$T$^X z3lwrws3|gUf@Wfbut>={3gjm4X5|jE6cbu7XRQw)c7kR5`lFBr<&_WC9ZVw_0`UTe z&6IKndJfZ32ec7*#TKf_K*-l$Z_VQ6KI;K*;Z6?BhSw*^2lqC{<40~X#sHGAQXQj9 zo8NzI;o!dghv#{l0*DE!f3%9%O5k++o>SxDog%%D!b**A(fs}gv_3L2>!}VCt;?w`iG~1~6UH#QT|Ho3%w)kH%Iq?5o^!4?3xB6dG zER>B(CcS8XswUSz)f{T8Eg;;0$fB_Z+&PT?@M&}RxN&=^5JMOx)K#z$>;wtL#2B4g zbkzVMM$z^8=Y%H=r$}s(cKg9qa%w%O!s$As*kC!;_>Gqe5!0II#FVUr>_S~Z-Qi{+ zO~?XfI4-sq;IOR>oKfl~ey?xqTGpTor!wZ9&-TYyYI@+jE7Ea@1yJh|S3G7;z1l8! z_nw%)`N)=qu9hlRQLE^RR461AQECOHez#{2Bq?eaF}-MCSED0I1`5T66Lr+p;}2&Y z<<7LDI@jtBj~QRFMGH`_a$+28<{dmx9=Q7qdC9D+Mj)3Xx1Y3zPw$NJ|B>7%uC4YJ zd$KfQ-{x?*UgOXIO&C7;4;#4_(xlopIetcRb&&tjczUw6{x6vt$p5~|eRuF~<-hCy z@%#@90O4!6cWg_^1ZuS81w&xWTzw^@NpM~j;Tqvx%S0a;QYcD}gw~`f6hPT7a#66v z@}<5fM_eTR694w`znrp>D)4Q#0Xy-3x;_6hnTW>*`QJ-Gcir;;X7(R$KF5o*5vKw3 zH;72)H&R9fbOUpCVbbk<~T%u?77D3$wKQSh!q?> zLquKPBEk4IfQRGL=RB^u&D{48=I4WRxd1i>2Wp4-`b3sT$Qpmn9N6yEt z502azywFwl*vJ16@u9{7w$TLW#Q&+uHvexjF*yHw3AlgASO33x{I{F>5AYRB0C`wb z4--jY;-f?Y^yry2Gs8NZj0VbdU2rl7)5cAl>G0w(CycLA~~g*BaEx{d0}NE5Xpc>OVjDMYF)dY(ITVUViUiW}OfbbRnMGpv?X}pT@t%kQ6ws12VUS_^r z!dmz!FNX$L*NDF`j>O#Mwo9oVU{sMe*6VYevFAkqr~t<+NSIWU^CLy1lcdlDDWV+E z22%0fT;w98>Fp^j0%Z9`(+L1^q4TWAfP>8$QBIEa<8I7~rJ!Js@HSpv5Iccau;GjX z2f;}ZUeJ!(b{vdh+7y>^@QTJv0e1i+by~8HmzHrlt!l-hNQQdJhFWDDX9SSo!>gc7 zO%D=itO8O1pvz%&A|zWb^B_hEh(i7g!9yaA=WrH_h_fLOzycB62Ot)zl86-DyY=JE z=^wcrBNTGW@`1wHy?mS7h)e&ByIF_k7WN)GG&{$o?kvA5bKyxp zyZ3L0p7DbZzu||=6E9pE3YdGaYv*e2;Pbeb0-v8g6oah3{7vup@t=P4S0DcA=>vcD z{{Lb9=!sLwRQQ8m`O3*p9e&fhe*L#k{^?8J{0ATX>XlD%I}d(e5)2sr&zJf*8~J_X z_dkg=XyyNCIyyNt*+-bXp&#S^pSS-J=bV&!d5GD*58UC+4f5Zd>ti zaw;4fm`I!9>zD0Wd(>w8SJ;vN6K&`JVsY361O31Kd(k?goO+4{> simple.txt +git add simple.txt +git commit -q -m c1 + +echo "line 2" >> simple.txt +git add simple.txt +git commit -q -m c2 + +echo "line 3" >> simple.txt +git add simple.txt +git commit -q -m c3 + +echo "line 4" >> simple.txt +git add simple.txt +git commit -q -m c4 diff --git a/gix/tests/gix/repository/blame.rs b/gix/tests/gix/repository/blame.rs new file mode 100644 index 00000000000..ad298327143 --- /dev/null +++ b/gix/tests/gix/repository/blame.rs @@ -0,0 +1,63 @@ +use gix::bstr::BString; +use std::num::NonZero; + +#[test] +fn blame_simple() -> crate::Result { + let repo = crate::named_repo("make_blame_repo.sh")?; + + let suspect = repo.head_id()?; + let outcome = repo.blame_file("simple.txt".into(), suspect, Default::default())?; + + assert_eq!(outcome.entries.len(), 4); + + Ok(()) +} + +#[test] +fn blame_simple_with_options() -> crate::Result { + let repo = crate::named_repo("make_blame_repo.sh")?; + + let options = gix::blame::Options { + range: gix::blame::BlameRanges::from_range(1..=2), + ..Default::default() + }; + + let suspect = repo.head_id()?; + let outcome = repo.blame_file("simple.txt".into(), suspect, options)?; + + assert_eq!(outcome.entries.len(), 2); + + let entries_with_lines: Vec<_> = outcome.entries_with_lines().collect(); + + assert!(matches!( + entries_with_lines.as_slice(), + &[ + ( + gix::blame::BlameEntry { + start_in_blamed_file: 0, + start_in_source_file: 0, + source_file_name: None, + .. + }, + _, + ), + ( + gix::blame::BlameEntry { + start_in_blamed_file: 1, + start_in_source_file: 1, + source_file_name: None, + .. + }, + _, + ) + ] + )); + + assert_eq!(entries_with_lines[0].0.len, NonZero::new(1).unwrap()); + assert_eq!(entries_with_lines[1].0.len, NonZero::new(1).unwrap()); + + assert_eq!(entries_with_lines[0].1, vec![BString::new("line 1\n".into())]); + assert_eq!(entries_with_lines[1].1, vec![BString::new("line 2\n".into())]); + + Ok(()) +} diff --git a/gix/tests/gix/repository/mod.rs b/gix/tests/gix/repository/mod.rs index 4253ff4b121..1c10014dcf5 100644 --- a/gix/tests/gix/repository/mod.rs +++ b/gix/tests/gix/repository/mod.rs @@ -1,5 +1,7 @@ use gix::Repository; +#[cfg(feature = "blame")] +mod blame; mod config; #[cfg(feature = "excludes")] mod excludes; From 3c54e5b8d3d0a770c8dc00155b1a5b4c06446c9d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 7 Sep 2025 10:07:54 +0200 Subject: [PATCH 3/3] Add `blame` to extras, and asssure it's tested separately on CI --- gix/Cargo.toml | 1 + gix/tests/gix/repository/blame.rs | 4 ++-- justfile | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gix/Cargo.toml b/gix/Cargo.toml index 38585d20ccd..232f538f9b5 100644 --- a/gix/Cargo.toml +++ b/gix/Cargo.toml @@ -67,6 +67,7 @@ extras = [ "interrupt", "status", "dirwalk", + "blame" ] ## A collection of features that need a larger MSRV, and thus are disabled by default. diff --git a/gix/tests/gix/repository/blame.rs b/gix/tests/gix/repository/blame.rs index ad298327143..eb3f0d1ac0c 100644 --- a/gix/tests/gix/repository/blame.rs +++ b/gix/tests/gix/repository/blame.rs @@ -2,7 +2,7 @@ use gix::bstr::BString; use std::num::NonZero; #[test] -fn blame_simple() -> crate::Result { +fn simple() -> crate::Result { let repo = crate::named_repo("make_blame_repo.sh")?; let suspect = repo.head_id()?; @@ -14,7 +14,7 @@ fn blame_simple() -> crate::Result { } #[test] -fn blame_simple_with_options() -> crate::Result { +fn with_options() -> crate::Result { let repo = crate::named_repo("make_blame_repo.sh")?; let options = gix::blame::Options { diff --git a/justfile b/justfile index 8a4c4ffa95c..57f58d47c68 100755 --- a/justfile +++ b/justfile @@ -133,6 +133,7 @@ check: cargo check -p gix --no-default-features --features credentials --tests cargo check -p gix --no-default-features --features index --tests cargo check -p gix --no-default-features --features interrupt --tests + cargo check -p gix --no-default-features --features blame --tests cargo check -p gix --no-default-features cargo check -p gix-odb --features serde cargo check --no-default-features --features max-control