PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /usr/share/doc/git-1.8.3.1/contrib/examples
Viewing File: /usr/share/doc/git-1.8.3.1/contrib/examples/git-svnimport.perl
#!/usr/bin/perl # This tool is copyright (c) 2005, Matthias Urlichs. # It is released under the Gnu Public License, version 2. # # The basic idea is to pull and analyze SVN changes. # # Checking out the files is done by a single long-running SVN connection. # # The head revision is on branch "origin" by default. # You can change that with the '-o' option. use strict; use warnings; use Getopt::Std; use File::Copy; use File::Spec; use File::Temp qw(tempfile); use File::Path qw(mkpath); use File::Basename qw(basename dirname); use Time::Local; use IO::Pipe; use POSIX qw(strftime dup2); use IPC::Open2; use SVN::Core; use SVN::Ra; die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1"; $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F, $opt_P,$opt_R); sub usage() { print STDERR <<END; usage: ${\basename $0} # fetch/update GIT from SVN [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-R repack_each_revs] [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname] [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg] [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL] END exit(1); } getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:R:uv") or usage(); usage if $opt_h; my $tag_name = $opt_t || "tags"; my $trunk_name = defined $opt_T ? $opt_T : "trunk"; my $branch_name = $opt_b || "branches"; my $project_name = $opt_P || ""; $project_name = "/" . $project_name if ($project_name); my $repack_after = $opt_R || 1000; my $root_pool = SVN::Pool->new_default; @ARGV == 1 or @ARGV == 2 or usage(); $opt_o ||= "origin"; $opt_s ||= 1; my $git_tree = $opt_C; $git_tree ||= "."; my $svn_url = $ARGV[0]; my $svn_dir = $ARGV[1]; our @mergerx = (); if ($opt_m) { my $branch_esc = quotemeta ($branch_name); my $trunk_esc = quotemeta ($trunk_name); @mergerx = ( qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i, qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i, qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i ); } if ($opt_M) { unshift (@mergerx, qr/$opt_M/); } # Absolutize filename now, since we will have chdir'ed by the time we # get around to opening it. $opt_A = File::Spec->rel2abs($opt_A) if $opt_A; our %users = (); our $users_file = undef; sub read_users($) { $users_file = File::Spec->rel2abs(@_); die "Cannot open $users_file\n" unless -f $users_file; open(my $authors,$users_file); while(<$authors>) { chomp; next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; (my $user,my $name,my $email) = ($1,$2,$3); $users{$user} = [$name,$email]; } close($authors); } select(STDERR); $|=1; select(STDOUT); package SVNconn; # Basic SVN connection. # We're only interested in connecting and downloading, so ... use File::Spec; use File::Temp qw(tempfile); use POSIX qw(strftime dup2); use Fcntl qw(SEEK_SET); sub new { my($what,$repo) = @_; $what=ref($what) if ref($what); my $self = {}; $self->{'buffer'} = ""; bless($self,$what); $repo =~ s#/+$##; $self->{'fullrep'} = $repo; $self->conn(); return $self; } sub conn { my $self = shift; my $repo = $self->{'fullrep'}; my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider, SVN::Client::get_ssl_server_trust_file_provider, SVN::Client::get_username_provider]); my $s = SVN::Ra->new(url => $repo, auth => $auth, pool => $root_pool); die "SVN connection to $repo: $!\n" unless defined $s; $self->{'svn'} = $s; $self->{'repo'} = $repo; $self->{'maxrev'} = $s->get_latest_revnum(); } sub file { my($self,$path,$rev) = @_; my ($fh, $name) = tempfile('gitsvn.XXXXXX', DIR => File::Spec->tmpdir(), UNLINK => 1); print "... $rev $path ...\n" if $opt_v; my (undef, $properties); $path =~ s#^/*##; my $subpool = SVN::Pool::new_default_sub; eval { (undef, $properties) = $self->{'svn'}->get_file($path,$rev,$fh); }; if($@) { return undef if $@ =~ /Attempted to get checksum/; die $@; } my $mode; if (exists $properties->{'svn:executable'}) { $mode = '100755'; } elsif (exists $properties->{'svn:special'}) { my ($special_content, $filesize); $filesize = tell $fh; seek $fh, 0, SEEK_SET; read $fh, $special_content, $filesize; if ($special_content =~ s/^link //) { $mode = '120000'; seek $fh, 0, SEEK_SET; truncate $fh, 0; print $fh $special_content; } else { die "unexpected svn:special file encountered"; } } else { $mode = '100644'; } close ($fh); return ($name, $mode); } sub ignore { my($self,$path,$rev) = @_; print "... $rev $path ...\n" if $opt_v; $path =~ s#^/*##; my $subpool = SVN::Pool::new_default_sub; my (undef,undef,$properties) = $self->{'svn'}->get_dir($path,$rev,undef); if (exists $properties->{'svn:ignore'}) { my ($fh, $name) = tempfile('gitsvn.XXXXXX', DIR => File::Spec->tmpdir(), UNLINK => 1); print $fh $properties->{'svn:ignore'}; close($fh); return $name; } else { return undef; } } sub dir_list { my($self,$path,$rev) = @_; $path =~ s#^/*##; my $subpool = SVN::Pool::new_default_sub; my ($dirents,undef,$properties) = $self->{'svn'}->get_dir($path,$rev,undef); return $dirents; } package main; use URI; our $svn = $svn_url; $svn .= "/$svn_dir" if defined $svn_dir; my $svn2 = SVNconn->new($svn); $svn = SVNconn->new($svn); my $lwp_ua; if($opt_d or $opt_D) { $svn_url = URI->new($svn_url)->canonical; if($opt_D) { $svn_dir =~ s#/*$#/#; } else { $svn_dir = ""; } if ($svn_url->scheme eq "http") { use LWP::UserAgent; $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []); } else { print STDERR "Warning: not HTTP; turning off direct file access\n"; $opt_d=0; } } sub pdate($) { my($d) = @_; $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)# or die "Unparseable date: $d\n"; my $y=$1; $y-=1900 if $y>1900; return timegm($6||0,$5,$4,$3,$2-1,$y); } sub getwd() { my $pwd = `pwd`; chomp $pwd; return $pwd; } sub get_headref($$) { my $name = shift; my $git_dir = shift; my $sha; if (open(C,"$git_dir/refs/heads/$name")) { chomp($sha = <C>); close(C); length($sha) == 40 or die "Cannot get head id for $name ($sha): $!\n"; } return $sha; } -d $git_tree or mkdir($git_tree,0777) or die "Could not create $git_tree: $!"; chdir($git_tree); my $orig_branch = ""; my $forward_master = 0; my %branches; my $git_dir = $ENV{"GIT_DIR"} || ".git"; $git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#; $ENV{"GIT_DIR"} = $git_dir; my $orig_git_index; $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE}; my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx', DIR => File::Spec->tmpdir()); close ($git_ih); $ENV{GIT_INDEX_FILE} = $git_index; my $maxnum = 0; my $last_rev = ""; my $last_branch; my $current_rev = $opt_s || 1; unless(-d $git_dir) { system("git init"); die "Cannot init the GIT db at $git_tree: $?\n" if $?; system("git read-tree --empty"); die "Cannot init an empty tree: $?\n" if $?; $last_branch = $opt_o; $orig_branch = ""; } else { -f "$git_dir/refs/heads/$opt_o" or die "Branch '$opt_o' does not exist.\n". "Either use the correct '-o branch' option,\n". "or import to a new repository.\n"; -f "$git_dir/svn2git" or die "'$git_dir/svn2git' does not exist.\n". "You need that file for incremental imports.\n"; open(F, "git symbolic-ref HEAD |") or die "Cannot run git-symbolic-ref: $!\n"; chomp ($last_branch = <F>); $last_branch = basename($last_branch); close(F); unless($last_branch) { warn "Cannot read the last branch name: $! -- assuming 'master'\n"; $last_branch = "master"; } $orig_branch = $last_branch; $last_rev = get_headref($orig_branch, $git_dir); if (-f "$git_dir/SVN2GIT_HEAD") { die <<EOM; SVN2GIT_HEAD exists. Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD. You may need to run git-read-tree -m -u SVN2GIT_HEAD HEAD EOM } system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD"); $forward_master = $opt_o ne 'master' && -f "$git_dir/refs/heads/master" && system('cmp', '-s', "$git_dir/refs/heads/master", "$git_dir/refs/heads/$opt_o") == 0; # populate index system('git', 'read-tree', $last_rev); die "read-tree failed: $?\n" if $?; # Get the last import timestamps open my $B,"<", "$git_dir/svn2git"; while(<$B>) { chomp; my($num,$branch,$ref) = split; $branches{$branch}{$num} = $ref; $branches{$branch}{"LAST"} = $ref; $current_rev = $num+1 if $current_rev <= $num; } close($B); } -d $git_dir or die "Could not create git subdir ($git_dir).\n"; my $default_authors = "$git_dir/svn-authors"; if ($opt_A) { read_users($opt_A); copy($opt_A,$default_authors) or die "Copy failed: $!"; } else { read_users($default_authors) if -f $default_authors; } open BRANCHES,">>", "$git_dir/svn2git"; sub node_kind($$) { my ($svnpath, $revision) = @_; $svnpath =~ s#^/*##; my $subpool = SVN::Pool::new_default_sub; my $kind = $svn->{'svn'}->check_path($svnpath,$revision); return $kind; } sub get_file($$$) { my($svnpath,$rev,$path) = @_; # now get it my ($name,$mode); if($opt_d) { my($req,$res); # /svn/!svn/bc/2/django/trunk/django-docs/build.py my $url=$svn_url->clone(); $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath"); print "... $path...\n" if $opt_v; $req = HTTP::Request->new(GET => $url); $res = $lwp_ua->request($req); if ($res->is_success) { my $fh; ($fh, $name) = tempfile('gitsvn.XXXXXX', DIR => File::Spec->tmpdir(), UNLINK => 1); print $fh $res->content; close($fh) or die "Could not write $name: $!\n"; } else { return undef if $res->code == 301; # directory? die $res->status_line." at $url\n"; } $mode = '0644'; # can't obtain mode via direct http request? } else { ($name,$mode) = $svn->file("$svnpath",$rev); return undef unless defined $name; } my $pid = open(my $F, '-|'); die $! unless defined $pid; if (!$pid) { exec("git", "hash-object", "-w", $name) or die "Cannot create object: $!\n"; } my $sha = <$F>; chomp $sha; close $F; unlink $name; return [$mode, $sha, $path]; } sub get_ignore($$$$$) { my($new,$old,$rev,$path,$svnpath) = @_; return unless $opt_I; my $name = $svn->ignore("$svnpath",$rev); if ($path eq '/') { $path = $opt_I; } else { $path = File::Spec->catfile($path,$opt_I); } if (defined $name) { my $pid = open(my $F, '-|'); die $! unless defined $pid; if (!$pid) { exec("git", "hash-object", "-w", $name) or die "Cannot create object: $!\n"; } my $sha = <$F>; chomp $sha; close $F; unlink $name; push(@$new,['0644',$sha,$path]); } elsif (defined $old) { push(@$old,$path); } } sub project_path($$) { my ($path, $project) = @_; $path = "/".$path unless ($path =~ m#^\/#) ; return $1 if ($path =~ m#^$project\/(.*)$#); $path =~ s#\.#\\\.#g; $path =~ s#\+#\\\+#g; return "/" if ($project =~ m#^$path.*$#); return undef; } sub split_path($$) { my($rev,$path) = @_; my $branch; if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) { $branch = "/$1"; } elsif($path =~ s#^/\Q$trunk_name\E/?##) { $branch = "/"; } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) { $branch = $1; } else { my %no_error = ( "/" => 1, "/$tag_name" => 1, "/$branch_name" => 1 ); print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path}); return () } if ($path eq "") { $path = "/"; } elsif ($project_name) { $path = project_path($path, $project_name); } return ($branch,$path); } sub branch_rev($$) { my ($srcbranch,$uptorev) = @_; my $bbranches = $branches{$srcbranch}; my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches; my $therev; foreach my $arev(@revs) { next if ($arev eq 'LAST'); if ($arev <= $uptorev) { $therev = $arev; last; } } return $therev; } sub expand_svndir($$$); sub expand_svndir($$$) { my ($svnpath, $rev, $path) = @_; my @list; get_ignore(\@list, undef, $rev, $path, $svnpath); my $dirents = $svn->dir_list($svnpath, $rev); foreach my $p(keys %$dirents) { my $kind = node_kind($svnpath.'/'.$p, $rev); if ($kind eq $SVN::Node::file) { my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p); push(@list, $f) if $f; } elsif ($kind eq $SVN::Node::dir) { push(@list, expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p)); } } return @list; } sub copy_path($$$$$$$$) { # Somebody copied a whole subdirectory. # We need to find the index entries from the old version which the # SVN log entry points to, and add them to the new place. my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_; my($srcbranch,$srcpath) = split_path($rev,$oldpath); unless(defined $srcbranch && defined $srcpath) { print "Path not found when copying from $oldpath @ $rev.\n". "Will try to copy from original SVN location...\n" if $opt_v; push (@$new, expand_svndir($oldpath, $rev, $path)); return; } my $therev = branch_rev($srcbranch, $rev); my $gitrev = $branches{$srcbranch}{$therev}; unless($gitrev) { print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n"; return; } if ($srcbranch ne $newbranch) { push(@$parents, $branches{$srcbranch}{'LAST'}); } print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v; if ($node_kind eq $SVN::Node::dir) { $srcpath =~ s#/*$#/#; } my $pid = open my $f,'-|'; die $! unless defined $pid; if (!$pid) { exec("git","ls-tree","-r","-z",$gitrev,$srcpath) or die $!; } local $/ = "\0"; while(<$f>) { chomp; my($m,$p) = split(/\t/,$_,2); my($mode,$type,$sha1) = split(/ /,$m); next if $type ne "blob"; if ($node_kind eq $SVN::Node::dir) { $p = $path . substr($p,length($srcpath)-1); } else { $p = $path; } push(@$new,[$mode,$sha1,$p]); } close($f) or print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n"; } sub commit { my($branch, $changed_paths, $revision, $author, $date, $message) = @_; my($committer_name,$committer_email,$dest); my($author_name,$author_email); my(@old,@new,@parents); if (not defined $author or $author eq "") { $committer_name = $committer_email = "unknown"; } elsif (defined $users_file) { die "User $author is not listed in $users_file\n" unless exists $users{$author}; ($committer_name,$committer_email) = @{$users{$author}}; } elsif ($author =~ /^(.*?)\s+<(.*)>$/) { ($committer_name, $committer_email) = ($1, $2); } else { $author =~ s/^<(.*)>$/$1/; $committer_name = $committer_email = $author; } if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) { ($author_name, $author_email) = ($1, $2); print "Author from From: $1 <$2>\n" if ($opt_v);; } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { ($author_name, $author_email) = ($1, $2); print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);; } else { $author_name = $committer_name; $author_email = $committer_email; } $date = pdate($date); my $tag; my $parent; if($branch eq "/") { # trunk $parent = $opt_o; } elsif($branch =~ m#^/(.+)#) { # tag $tag = 1; $parent = $1; } else { # "normal" branch # nothing to do $parent = $branch; } $dest = $parent; my $prev = $changed_paths->{"/"}; if($prev and $prev->[0] eq "A") { delete $changed_paths->{"/"}; my $oldpath = $prev->[1]; my $rev; if(defined $oldpath) { my $p; ($parent,$p) = split_path($revision,$oldpath); if(defined $parent) { if($parent eq "/") { $parent = $opt_o; } else { $parent =~ s#^/##; # if it's a tag } } } else { $parent = undef; } } my $rev; if($revision > $opt_s and defined $parent) { open(H,'-|',"git","rev-parse","--verify",$parent); $rev = <H>; close(H) or do { print STDERR "$revision: cannot find commit '$parent'!\n"; return; }; chop $rev; if(length($rev) != 40) { print STDERR "$revision: cannot find commit '$parent'!\n"; return; } $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"}; if($revision != $opt_s and not $rev) { print STDERR "$revision: do not know ancestor for '$parent'!\n"; return; } } else { $rev = undef; } # if($prev and $prev->[0] eq "A") { # if(not $tag) { # unless(open(H,"> $git_dir/refs/heads/$branch")) { # print STDERR "$revision: Could not create branch $branch: $!\n"; # $state=11; # next; # } # print H "$rev\n" # or die "Could not write branch $branch: $!"; # close(H) # or die "Could not write branch $branch: $!"; # } # } if(not defined $rev) { unlink($git_index); } elsif ($rev ne $last_rev) { print "Switching from $last_rev to $rev ($branch)\n" if $opt_v; system("git", "read-tree", $rev); die "read-tree failed for $rev: $?\n" if $?; $last_rev = $rev; } push (@parents, $rev) if defined $rev; my $cid; if($tag and not %$changed_paths) { $cid = $rev; } else { my @paths = sort keys %$changed_paths; foreach my $path(@paths) { my $action = $changed_paths->{$path}; if ($action->[0] eq "R") { # refer to a file/tree in an earlier commit push(@old,$path); # remove any old stuff } if(($action->[0] eq "A") || ($action->[0] eq "R")) { my $node_kind = node_kind($action->[3], $revision); if ($node_kind eq $SVN::Node::file) { my $f = get_file($action->[3], $revision, $path); if ($f) { push(@new,$f) if $f; } else { my $opath = $action->[3]; print STDERR "$revision: $branch: could not fetch '$opath'\n"; } } elsif ($node_kind eq $SVN::Node::dir) { if($action->[1]) { copy_path($revision, $branch, $path, $action->[1], $action->[2], $node_kind, \@new, \@parents); } else { get_ignore(\@new, \@old, $revision, $path, $action->[3]); } } } elsif ($action->[0] eq "D") { push(@old,$path); } elsif ($action->[0] eq "M") { my $node_kind = node_kind($action->[3], $revision); if ($node_kind eq $SVN::Node::file) { my $f = get_file($action->[3], $revision, $path); push(@new,$f) if $f; } elsif ($node_kind eq $SVN::Node::dir) { get_ignore(\@new, \@old, $revision, $path, $action->[3]); } } else { die "$revision: unknown action '".$action->[0]."' for $path\n"; } } while(@old) { my @o1; if(@old > 55) { @o1 = splice(@old,0,50); } else { @o1 = @old; @old = (); } my $pid = open my $F, "-|"; die "$!" unless defined $pid; if (!$pid) { exec("git", "ls-files", "-z", @o1) or die $!; } @o1 = (); local $/ = "\0"; while(<$F>) { chomp; push(@o1,$_); } close($F); while(@o1) { my @o2; if(@o1 > 55) { @o2 = splice(@o1,0,50); } else { @o2 = @o1; @o1 = (); } system("git","update-index","--force-remove","--",@o2); die "Cannot remove files: $?\n" if $?; } } while(@new) { my @n2; if(@new > 12) { @n2 = splice(@new,0,10); } else { @n2 = @new; @new = (); } system("git","update-index","--add", (map { ('--cacheinfo', @$_) } @n2)); die "Cannot add files: $?\n" if $?; } my $pid = open(C,"-|"); die "Cannot fork: $!" unless defined $pid; unless($pid) { exec("git","write-tree"); die "Cannot exec git-write-tree: $!\n"; } chomp(my $tree = <C>); length($tree) == 40 or die "Cannot get tree id ($tree): $!\n"; close(C) or die "Error running git-write-tree: $?\n"; print "Tree ID $tree\n" if $opt_v; my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n"; my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n"; $pid = fork(); die "Fork: $!\n" unless defined $pid; unless($pid) { $pr->writer(); $pw->reader(); open(OUT,">&STDOUT"); dup2($pw->fileno(),0); dup2($pr->fileno(),1); $pr->close(); $pw->close(); my @par = (); # loose detection of merges # based on the commit msg foreach my $rx (@mergerx) { if ($message =~ $rx) { my $mparent = $1; if ($mparent eq 'HEAD') { $mparent = $opt_o }; if ( -e "$git_dir/refs/heads/$mparent") { $mparent = get_headref($mparent, $git_dir); push (@parents, $mparent); print OUT "Merge parent branch: $mparent\n" if $opt_v; } } } my %seen_parents = (); my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents; foreach my $bparent (@unique_parents) { push @par, '-p', $bparent; print OUT "Merge parent branch: $bparent\n" if $opt_v; } exec("env", "GIT_AUTHOR_NAME=$author_name", "GIT_AUTHOR_EMAIL=$author_email", "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), "GIT_COMMITTER_NAME=$committer_name", "GIT_COMMITTER_EMAIL=$committer_email", "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), "git", "commit-tree", $tree,@par); die "Cannot exec git-commit-tree: $!\n"; } $pw->writer(); $pr->reader(); $message =~ s/[\s\n]+\z//; $message = "r$revision: $message" if $opt_r; print $pw "$message\n" or die "Error writing to git-commit-tree: $!\n"; $pw->close(); print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v; chomp($cid = <$pr>); length($cid) == 40 or die "Cannot get commit id ($cid): $!\n"; print "Commit ID $cid\n" if $opt_v; $pr->close(); waitpid($pid,0); die "Error running git-commit-tree: $?\n" if $?; } if (not defined $cid) { $cid = $branches{"/"}{"LAST"}; } if(not defined $dest) { print "... no known parent\n" if $opt_v; } elsif(not $tag) { print "Writing to refs/heads/$dest\n" if $opt_v; open(C,">$git_dir/refs/heads/$dest") and print C ("$cid\n") and close(C) or die "Cannot write branch $dest for update: $!\n"; } if ($tag) { $last_rev = "-" if %$changed_paths; # the tag was 'complex', i.e. did not refer to a "real" revision $dest =~ tr/_/\./ if $opt_u; system('git', 'tag', '-f', $dest, $cid) == 0 or die "Cannot create tag $dest: $!\n"; print "Created tag '$dest' on '$branch'\n" if $opt_v; } $branches{$branch}{"LAST"} = $cid; $branches{$branch}{$revision} = $cid; $last_rev = $cid; print BRANCHES "$revision $branch $cid\n"; print "DONE: $revision $dest $cid\n" if $opt_v; } sub commit_all { # Recursive use of the SVN connection does not work local $svn = $svn2; my ($changed_paths, $revision, $author, $date, $message) = @_; my %p; while(my($path,$action) = each %$changed_paths) { $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ]; } $changed_paths = \%p; my %done; my @col; my $pref; my $branch; while(my($path,$action) = each %$changed_paths) { ($branch,$path) = split_path($revision,$path); next if not defined $branch; next if not defined $path; $done{$branch}{$path} = $action; } while(($branch,$changed_paths) = each %done) { commit($branch, $changed_paths, $revision, $author, $date, $message); } } $opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'}; if ($opt_l < $current_rev) { print "Up to date: no new revisions to fetch!\n" if $opt_v; unlink("$git_dir/SVN2GIT_HEAD"); exit; } print "Processing from $current_rev to $opt_l ...\n" if $opt_v; my $from_rev; my $to_rev = $current_rev - 1; my $subpool = SVN::Pool::new_default_sub; while ($to_rev < $opt_l) { $subpool->clear; $from_rev = $to_rev + 1; $to_rev = $from_rev + $repack_after; $to_rev = $opt_l if $opt_l < $to_rev; print "Fetching from $from_rev to $to_rev ...\n" if $opt_v; $svn->{'svn'}->get_log("",$from_rev,$to_rev,0,1,1,\&commit_all); my $pid = fork(); die "Fork: $!\n" unless defined $pid; unless($pid) { exec("git", "repack", "-d") or die "Cannot repack: $!\n"; } waitpid($pid, 0); } unlink($git_index); if (defined $orig_git_index) { $ENV{GIT_INDEX_FILE} = $orig_git_index; } else { delete $ENV{GIT_INDEX_FILE}; } # Now switch back to the branch we were in before all of this happened if($orig_branch) { print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0); system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") if $forward_master; unless ($opt_i) { system('git', 'read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD'); die "read-tree failed: $?\n" if $?; } } else { $orig_branch = "master"; print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0); system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") unless -f "$git_dir/refs/heads/master"; system('git', 'update-ref', 'HEAD', "$orig_branch"); unless ($opt_i) { system('git checkout'); die "checkout failed: $?\n" if $?; } } unlink("$git_dir/SVN2GIT_HEAD"); close(BRANCHES);