fugitive.vim 272 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127
  1. " Location: autoload/fugitive.vim
  2. " Maintainer: Tim Pope <http://tpo.pe/>
  3. " The functions contained within this file are for internal use only. For the
  4. " official API, see the commented functions in plugin/fugitive.vim.
  5. if exists('g:autoloaded_fugitive')
  6. finish
  7. endif
  8. let g:autoloaded_fugitive = 1
  9. " Section: Utility
  10. function! s:function(name) abort
  11. return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_'),''))
  12. endfunction
  13. function! s:sub(str,pat,rep) abort
  14. return substitute(a:str,'\v\C'.a:pat,a:rep,'')
  15. endfunction
  16. function! s:gsub(str,pat,rep) abort
  17. return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
  18. endfunction
  19. function! s:Uniq(list) abort
  20. let i = 0
  21. let seen = {}
  22. while i < len(a:list)
  23. let str = string(a:list[i])
  24. if has_key(seen, str)
  25. call remove(a:list, i)
  26. else
  27. let seen[str] = 1
  28. let i += 1
  29. endif
  30. endwhile
  31. return a:list
  32. endfunction
  33. function! s:JoinChomp(list) abort
  34. if empty(a:list[-1])
  35. return join(a:list[0:-2], "\n")
  36. else
  37. return join(a:list, "\n")
  38. endif
  39. endfunction
  40. function! s:winshell() abort
  41. return has('win32') && &shellcmdflag !~# '^-'
  42. endfunction
  43. function! s:WinShellEsc(arg) abort
  44. if type(a:arg) == type([])
  45. return join(map(copy(a:arg), 's:WinShellEsc(v:val)'))
  46. elseif a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
  47. return a:arg
  48. else
  49. return '"' . s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"') . '"'
  50. endif
  51. endfunction
  52. function! s:shellesc(arg) abort
  53. if type(a:arg) == type([])
  54. return join(map(copy(a:arg), 's:shellesc(v:val)'))
  55. elseif a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
  56. return a:arg
  57. elseif s:winshell()
  58. return '"' . s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"') . '"'
  59. else
  60. return shellescape(a:arg)
  61. endif
  62. endfunction
  63. function! s:fnameescape(file) abort
  64. if type(a:file) == type([])
  65. return join(map(copy(a:file), 's:fnameescape(v:val)'))
  66. else
  67. return fnameescape(a:file)
  68. endif
  69. endfunction
  70. function! fugitive#UrlDecode(str) abort
  71. return substitute(a:str, '%\(\x\x\)', '\=iconv(nr2char("0x".submatch(1)), "utf-8", "latin1")', 'g')
  72. endfunction
  73. function! s:UrlEncode(str) abort
  74. return substitute(a:str, '[%#?&;+=\<> [:cntrl:]]', '\=printf("%%%02X", char2nr(submatch(0)))', 'g')
  75. endfunction
  76. function! s:PathUrlEncode(str) abort
  77. return substitute(a:str, '[%#?[:cntrl:]]', '\=printf("%%%02X", char2nr(submatch(0)))', 'g')
  78. endfunction
  79. function! s:PathJoin(prefix, str) abort
  80. if a:prefix =~# '://'
  81. return a:prefix . s:PathUrlEncode(a:str)
  82. else
  83. return a:prefix . a:str
  84. endif
  85. endfunction
  86. function! s:throw(string) abort
  87. throw 'fugitive: '.a:string
  88. endfunction
  89. function! s:VersionCheck() abort
  90. if v:version < 704
  91. return 'return ' . string('echoerr "fugitive: Vim 7.4 or newer required"')
  92. elseif empty(fugitive#GitVersion())
  93. let exe = get(s:GitCmd(), 0, '')
  94. if len(exe) && !executable(exe)
  95. return 'return ' . string('echoerr "fugitive: cannot find ' . string(exe) . ' in PATH"')
  96. endif
  97. return 'return ' . string('echoerr "fugitive: cannot execute Git"')
  98. elseif !fugitive#GitVersion(1, 8, 5)
  99. return 'return ' . string('echoerr "fugitive: Git 1.8.5 or newer required"')
  100. else
  101. return ''
  102. endif
  103. endfunction
  104. let s:worktree_error = "core.worktree is required when using an external Git dir"
  105. function! s:DirCheck(...) abort
  106. let vcheck = s:VersionCheck()
  107. if !empty(vcheck)
  108. return vcheck
  109. endif
  110. let dir = call('FugitiveGitDir', a:000)
  111. if !empty(dir) && FugitiveWorkTree(dir, 1) is# 0
  112. return 'return ' . string('echoerr "fugitive: ' . s:worktree_error . '"')
  113. elseif !empty(dir)
  114. return ''
  115. elseif empty(bufname(''))
  116. return 'return ' . string('echoerr "fugitive: working directory does not belong to a Git repository"')
  117. else
  118. return 'return ' . string('echoerr "fugitive: file does not belong to a Git repository"')
  119. endif
  120. endfunction
  121. function! s:Mods(mods, ...) abort
  122. let mods = substitute(a:mods, '\C<mods>', '', '')
  123. let mods = mods =~# '\S$' ? mods . ' ' : mods
  124. if a:0 && mods !~# '\<\%(aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab\)\>'
  125. if a:1 ==# 'Edge'
  126. if mods =~# '\<vertical\>' ? &splitright : &splitbelow
  127. let mods = 'botright ' . mods
  128. else
  129. let mods = 'topleft ' . mods
  130. endif
  131. else
  132. let mods = a:1 . ' ' . mods
  133. endif
  134. endif
  135. return substitute(mods, '\s\+', ' ', 'g')
  136. endfunction
  137. if exists('+shellslash')
  138. let s:dir_commit_file = '\c^fugitive://\%(/[^/]\@=\)\=\(.\{-1,\}\)//\%(\(\x\{40,\}\|[0-3]\)\(/.*\)\=\)\=$'
  139. function! s:Slash(path) abort
  140. return tr(a:path, '\', '/')
  141. endfunction
  142. function! s:VimSlash(path) abort
  143. return tr(a:path, '\/', &shellslash ? '//' : '\\')
  144. endfunction
  145. else
  146. let s:dir_commit_file = '\c^fugitive://\(.\{-\}\)//\%(\(\x\{40,\}\|[0-3]\)\(/.*\)\=\)\=$'
  147. function! s:Slash(path) abort
  148. return a:path
  149. endfunction
  150. function! s:VimSlash(path) abort
  151. return a:path
  152. endfunction
  153. endif
  154. function! s:AbsoluteVimPath(...) abort
  155. if a:0 && type(a:1) == type('')
  156. let path = a:1
  157. else
  158. let path = bufname(a:0 && a:1 > 0 ? a:1 : '')
  159. if getbufvar(a:0 && a:1 > 0 ? a:1 : '', '&buftype') !~# '^\%(nowrite\|acwrite\)\=$'
  160. return path
  161. endif
  162. endif
  163. if s:Slash(path) =~# '^/\|^\a\+:'
  164. return path
  165. else
  166. return getcwd() . matchstr(getcwd(), '[\\/]') . path
  167. endif
  168. endfunction
  169. function! s:Resolve(path) abort
  170. let path = resolve(a:path)
  171. if has('win32')
  172. let path = s:VimSlash(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
  173. endif
  174. return path
  175. endfunction
  176. function! s:FileIgnoreCase(for_completion) abort
  177. return (exists('+fileignorecase') && &fileignorecase)
  178. \ || (a:for_completion && exists('+wildignorecase') && &wildignorecase)
  179. endfunction
  180. function! s:cpath(path, ...) abort
  181. if s:FileIgnoreCase(0)
  182. let path = s:VimSlash(tolower(a:path))
  183. else
  184. let path = s:VimSlash(a:path)
  185. endif
  186. return a:0 ? path ==# s:cpath(a:1) : path
  187. endfunction
  188. let s:quote_chars = {
  189. \ "\007": 'a', "\010": 'b', "\011": 't', "\012": 'n', "\013": 'v', "\014": 'f', "\015": 'r',
  190. \ '"': '"', '\': '\'}
  191. let s:unquote_chars = {
  192. \ 'a': "\007", 'b': "\010", 't': "\011", 'n': "\012", 'v': "\013", 'f': "\014", 'r': "\015",
  193. \ '"': '"', '\': '\'}
  194. function! s:Quote(string) abort
  195. let string = substitute(a:string, "[\001-\037\"\\\177]", '\="\\" . get(s:quote_chars, submatch(0), printf("%03o", char2nr(submatch(0))))', 'g')
  196. if string !=# a:string
  197. return '"' . string . '"'
  198. else
  199. return string
  200. endif
  201. endfunction
  202. function! fugitive#Unquote(string) abort
  203. let string = substitute(a:string, "\t*$", '', '')
  204. if string =~# '^".*"$'
  205. return substitute(string[1:-2], '\\\(\o\o\o\|.\)', '\=get(s:unquote_chars, submatch(1), iconv(nr2char("0" . submatch(1)), "utf-8", "latin1"))', 'g')
  206. else
  207. return string
  208. endif
  209. endfunction
  210. let s:executables = {}
  211. function! s:executable(binary) abort
  212. if !has_key(s:executables, a:binary)
  213. let s:executables[a:binary] = executable(a:binary)
  214. endif
  215. return s:executables[a:binary]
  216. endfunction
  217. if !exists('s:temp_scripts')
  218. let s:temp_scripts = {}
  219. endif
  220. function! s:TempScript(...) abort
  221. let body = join(a:000, "\n")
  222. if !has_key(s:temp_scripts, body)
  223. let s:temp_scripts[body] = tempname() . '.sh'
  224. endif
  225. let temp = s:temp_scripts[body]
  226. if !filereadable(temp)
  227. call writefile(['#!/bin/sh'] + a:000, temp)
  228. endif
  229. return FugitiveGitPath(temp)
  230. endfunction
  231. function! s:DoAutocmd(...) abort
  232. return join(map(copy(a:000), "'doautocmd <nomodeline>' . v:val"), '|')
  233. endfunction
  234. function! s:Map(mode, lhs, rhs, ...) abort
  235. let maps = []
  236. let defer = a:0 && a:1 =~# '<unique>' || get(g:, 'fugitive_defer_to_existing_maps')
  237. let flags = substitute(a:0 ? a:1 : '', '<unique>', '', '') . (a:rhs =~# '<Plug>' ? '' : '<script>') . '<nowait>'
  238. for mode in split(a:mode, '\zs')
  239. if a:0 <= 1
  240. call add(maps, mode.'map <buffer>' . substitute(flags, '<unique>', '', '') . ' <Plug>fugitive:' . a:lhs . ' ' . a:rhs)
  241. endif
  242. let skip = 0
  243. let head = a:lhs
  244. let tail = ''
  245. let keys = get(g:, mode.'remap', {})
  246. if type(keys) == type([])
  247. continue
  248. endif
  249. while !empty(head)
  250. if has_key(keys, head)
  251. let head = keys[head]
  252. let skip = empty(head)
  253. break
  254. endif
  255. let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
  256. let head = substitute(head, '<[^<>]*>$\|.$', '', '')
  257. endwhile
  258. if !skip && (!defer || empty(mapcheck(head.tail, mode)))
  259. call add(maps, mode.'map <buffer>' . flags . ' ' . head.tail . ' ' . a:rhs)
  260. if a:0 > 1 && a:2
  261. let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
  262. \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
  263. endif
  264. endif
  265. endfor
  266. exe join(maps, '|')
  267. return ''
  268. endfunction
  269. function! fugitive#Autowrite() abort
  270. if &autowrite || &autowriteall
  271. try
  272. if &confirm
  273. let reconfirm = 1
  274. setglobal noconfirm
  275. endif
  276. silent! wall
  277. finally
  278. if exists('reconfirm')
  279. setglobal confirm
  280. endif
  281. endtry
  282. endif
  283. return ''
  284. endfunction
  285. function! fugitive#Wait(job_or_jobs, ...) abort
  286. let original = type(a:job_or_jobs) == type([]) ? copy(a:job_or_jobs) : [a:job_or_jobs]
  287. let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
  288. call filter(jobs, 'type(v:val) !=# type("")')
  289. let timeout_ms = a:0 ? a:1 : -1
  290. if exists('*jobwait')
  291. call map(copy(jobs), 'chanclose(v:val, "stdin")')
  292. call jobwait(jobs, timeout_ms)
  293. let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
  294. call filter(jobs, 'type(v:val) !=# type("")')
  295. if len(jobs)
  296. sleep 1m
  297. endif
  298. else
  299. let sleep = has('patch-8.2.2366') ? 'sleep! 1m' : 'sleep 1m'
  300. for job in jobs
  301. if ch_status(job) ==# 'open'
  302. call ch_close_in(job)
  303. endif
  304. endfor
  305. let i = 0
  306. for job in jobs
  307. while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
  308. if i == timeout_ms
  309. break
  310. endif
  311. let i += 1
  312. exe sleep
  313. endwhile
  314. endfor
  315. endif
  316. return a:job_or_jobs
  317. endfunction
  318. function! s:JobVimExit(dict, callback, temp, job, status) abort
  319. let a:dict.exit_status = a:status
  320. let a:dict.stderr = readfile(a:temp . '.err', 'b')
  321. call delete(a:temp . '.err')
  322. let a:dict.stdout = readfile(a:temp . '.out', 'b')
  323. call delete(a:temp . '.out')
  324. call delete(a:temp . '.in')
  325. call remove(a:dict, 'job')
  326. call call(a:callback[0], [a:dict] + a:callback[1:-1])
  327. endfunction
  328. function! s:JobNvimExit(dict, callback, job, data, type) dict abort
  329. let a:dict.stdout = self.stdout
  330. let a:dict.stderr = self.stderr
  331. let a:dict.exit_status = a:data
  332. call remove(a:dict, 'job')
  333. call call(a:callback[0], [a:dict] + a:callback[1:-1])
  334. endfunction
  335. function! s:JobExecute(argv, jopts, stdin, callback, ...) abort
  336. let dict = a:0 ? a:1 : {}
  337. let cb = len(a:callback) ? a:callback : [function('len')]
  338. if exists('*jobstart')
  339. call extend(a:jopts, {
  340. \ 'stdout_buffered': v:true,
  341. \ 'stderr_buffered': v:true,
  342. \ 'on_exit': function('s:JobNvimExit', [dict, cb])})
  343. try
  344. let dict.job = jobstart(a:argv, a:jopts)
  345. if !empty(a:stdin)
  346. call chansend(dict.job, a:stdin)
  347. call chanclose(dict.job, 'stdin')
  348. endif
  349. catch /^Vim\%((\a\+)\)\=:E475:/
  350. let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
  351. endtry
  352. elseif exists('*ch_close_in')
  353. let temp = tempname()
  354. call extend(a:jopts, {
  355. \ 'out_io': 'file',
  356. \ 'out_name': temp . '.out',
  357. \ 'err_io': 'file',
  358. \ 'err_name': temp . '.err',
  359. \ 'exit_cb': function('s:JobVimExit', [dict, cb, temp])})
  360. if a:stdin ==# ['']
  361. let a:jopts.in_io = 'null'
  362. elseif !empty(a:stdin)
  363. let a:jopts.in_io = 'file'
  364. let a:jopts.in_name = temp . '.in'
  365. call writefile(a:stdin, a:jopts.in_name, 'b')
  366. endif
  367. let dict.job = job_start(a:argv, a:jopts)
  368. if job_status(dict.job) ==# 'fail'
  369. let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
  370. unlet dict.job
  371. endif
  372. elseif &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
  373. throw 'fugitive: Vim 8 or higher required to use ' . &shell
  374. else
  375. let cmd = s:shellesc(a:argv)
  376. let outfile = tempname()
  377. try
  378. if len(a:stdin)
  379. call writefile(a:stdin, outfile . '.in', 'b')
  380. let cmd = ' (' . cmd . ' >' . outfile . ' <' . outfile . '.in) '
  381. else
  382. let cmd = ' (' . cmd . ' >' . outfile . ') '
  383. endif
  384. let dict.stderr = split(system(cmd), "\n", 1)
  385. let dict.exit_status = v:shell_error
  386. let dict.stdout = readfile(outfile, 'b')
  387. call call(cb[0], [dict] + cb[1:-1])
  388. finally
  389. call delete(outfile)
  390. call delete(outfile . '.in')
  391. endtry
  392. endif
  393. if empty(a:callback)
  394. call fugitive#Wait(dict)
  395. endif
  396. return dict
  397. endfunction
  398. function! s:add_methods(namespace, method_names) abort
  399. for name in a:method_names
  400. let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
  401. endfor
  402. endfunction
  403. " Section: Git
  404. let s:run_jobs = (exists('*ch_close_in') || exists('*jobstart')) && exists('*bufwinid')
  405. function! s:GitCmd() abort
  406. if !exists('g:fugitive_git_executable')
  407. return ['git']
  408. elseif type(g:fugitive_git_executable) == type([])
  409. return g:fugitive_git_executable
  410. else
  411. let dquote = '"\%([^"]\|""\|\\"\)*"\|'
  412. let string = g:fugitive_git_executable
  413. let list = []
  414. if string =~# '^\w\+='
  415. call add(list, '/usr/bin/env')
  416. endif
  417. while string =~# '\S'
  418. let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
  419. let string = strpart(string, len(arg))
  420. let arg = substitute(arg, '^\s\+', '', '')
  421. let arg = substitute(arg,
  422. \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
  423. \ '\=submatch(0)[0] ==# "\\" ? submatch(0)[1] : submatch(0)[1:-2]', 'g')
  424. call add(list, arg)
  425. endwhile
  426. return list
  427. endif
  428. endfunction
  429. function! s:GitShellCmd() abort
  430. if !exists('g:fugitive_git_executable')
  431. return 'git'
  432. elseif type(g:fugitive_git_executable) == type([])
  433. return s:shellesc(g:fugitive_git_executable)
  434. else
  435. return g:fugitive_git_executable
  436. endif
  437. endfunction
  438. function! s:UserCommandCwd(dir) abort
  439. let tree = s:Tree(a:dir)
  440. return len(tree) ? s:VimSlash(tree) : getcwd()
  441. endfunction
  442. function! s:UserCommandList(...) abort
  443. if !fugitive#GitVersion(1, 8, 5)
  444. throw 'fugitive: Git 1.8.5 or higher required'
  445. endif
  446. if !exists('g:fugitive_git_command')
  447. let git = s:GitCmd()
  448. elseif type(g:fugitive_git_command) == type([])
  449. let git = g:fugitive_git_command
  450. else
  451. let git = split(g:fugitive_git_command, '\s\+')
  452. endif
  453. let flags = []
  454. if a:0 && type(a:1) == type({})
  455. let git = copy(get(a:1, 'git', git))
  456. let flags = get(a:1, 'flags', flags)
  457. let dir = a:1.git_dir
  458. elseif a:0
  459. let dir = s:GitDir(a:1)
  460. else
  461. let dir = ''
  462. endif
  463. if len(dir)
  464. let tree = s:Tree(dir)
  465. if empty(tree)
  466. call add(git, '--git-dir=' . FugitiveGitPath(dir))
  467. else
  468. if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
  469. call add(git, '--git-dir=' . FugitiveGitPath(dir))
  470. endif
  471. if !s:cpath(tree, getcwd())
  472. call extend(git, ['-C', FugitiveGitPath(tree)])
  473. endif
  474. endif
  475. endif
  476. return git + flags
  477. endfunction
  478. let s:git_versions = {}
  479. function! fugitive#GitVersion(...) abort
  480. let git = s:GitShellCmd()
  481. if !has_key(s:git_versions, git)
  482. let s:git_versions[git] = matchstr(get(s:JobExecute(s:GitCmd() + ['--version'], {}, [], [], {}).stdout, 0, ''), '\d[^[:space:]]\+')
  483. endif
  484. if !a:0
  485. return s:git_versions[git]
  486. endif
  487. let components = split(s:git_versions[git], '\D\+')
  488. if empty(components)
  489. return -1
  490. endif
  491. for i in range(len(a:000))
  492. if a:000[i] > +get(components, i)
  493. return 0
  494. elseif a:000[i] < +get(components, i)
  495. return 1
  496. endif
  497. endfor
  498. return a:000[i] ==# get(components, i)
  499. endfunction
  500. function! s:Dir(...) abort
  501. return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
  502. endfunction
  503. function! s:GitDir(...) abort
  504. return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
  505. endfunction
  506. function! s:InitializeBuffer(repo) abort
  507. let b:git_dir = s:GitDir(a:repo)
  508. endfunction
  509. function! s:SameRepo(one, two) abort
  510. let one = s:GitDir(a:one)
  511. return !empty(one) && one ==# s:GitDir(a:two)
  512. endfunction
  513. if exists('+shellslash')
  514. function! s:DirUrlPrefix(dir) abort
  515. let gd = s:GitDir(a:dir)
  516. return 'fugitive://' . (gd =~# '^[^/]' ? '/' : '') . s:PathUrlEncode(gd) . '//'
  517. endfunction
  518. else
  519. function! s:DirUrlPrefix(dir) abort
  520. return 'fugitive://' . s:PathUrlEncode(s:GitDir(a:dir)) . '//'
  521. endfunction
  522. endif
  523. function! s:Tree(...) abort
  524. return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
  525. endfunction
  526. function! s:HasOpt(args, ...) abort
  527. let args = a:args[0 : index(a:args, '--')]
  528. let opts = copy(a:000)
  529. if type(opts[0]) == type([])
  530. if empty(args) || index(opts[0], args[0]) == -1
  531. return 0
  532. endif
  533. call remove(opts, 0)
  534. endif
  535. for opt in opts
  536. if index(args, opt) != -1
  537. return 1
  538. endif
  539. endfor
  540. endfunction
  541. function! s:PreparePathArgs(cmd, dir, literal, explicit) abort
  542. if !a:explicit
  543. call insert(a:cmd, '--literal-pathspecs')
  544. endif
  545. let split = index(a:cmd, '--')
  546. for i in range(split < 0 ? len(a:cmd) : split)
  547. if type(a:cmd[i]) == type(0)
  548. if a:literal
  549. let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
  550. else
  551. let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
  552. endif
  553. endif
  554. endfor
  555. if split < 0
  556. return a:cmd
  557. endif
  558. for i in range(split + 1, len(a:cmd) - 1)
  559. if type(a:cmd[i]) == type(0)
  560. if a:literal
  561. let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
  562. else
  563. let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
  564. endif
  565. elseif !a:explicit
  566. let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
  567. endif
  568. endfor
  569. return a:cmd
  570. endfunction
  571. let s:git_index_file_env = {}
  572. function! s:GitIndexFileEnv() abort
  573. if $GIT_INDEX_FILE =~# '^/\|^\a:' && !has_key(s:git_index_file_env, $GIT_INDEX_FILE)
  574. let s:git_index_file_env[$GIT_INDEX_FILE] = s:Slash(FugitiveVimPath($GIT_INDEX_FILE))
  575. endif
  576. return get(s:git_index_file_env, $GIT_INDEX_FILE, '')
  577. endfunction
  578. function! s:PrepareEnv(env, dir) abort
  579. if len($GIT_INDEX_FILE) && len(s:Tree(a:dir)) && !has_key(a:env, 'GIT_INDEX_FILE')
  580. let index_dir = substitute(s:GitIndexFileEnv(), '[^/]\+$', '', '')
  581. let our_dir = fugitive#Find('.git/', a:dir)
  582. if !s:cpath(index_dir, our_dir) && !s:cpath(resolve(index_dir), our_dir)
  583. let a:env['GIT_INDEX_FILE'] = FugitiveGitPath(fugitive#Find('.git/index', a:dir))
  584. endif
  585. endif
  586. if len($GIT_WORK_TREE)
  587. let a:env['GIT_WORK_TREE'] = '.'
  588. endif
  589. endfunction
  590. let s:prepare_env = {
  591. \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
  592. \ 'core.editor': 'GIT_EDITOR',
  593. \ 'core.askpass': 'GIT_ASKPASS',
  594. \ }
  595. function! fugitive#PrepareDirEnvGitFlagsArgs(...) abort
  596. if !fugitive#GitVersion(1, 8, 5)
  597. throw 'fugitive: Git 1.8.5 or higher required'
  598. endif
  599. let git = s:GitCmd()
  600. if a:0 == 1 && type(a:1) == type({}) && (has_key(a:1, 'fugitive_dir') || has_key(a:1, 'git_dir')) && has_key(a:1, 'flags') && has_key(a:1, 'args')
  601. let cmd = a:1.flags + a:1.args
  602. let dir = s:Dir(a:1)
  603. if has_key(a:1, 'git')
  604. let git = a:1.git
  605. endif
  606. let env = get(a:1, 'env', {})
  607. else
  608. let list_args = []
  609. let cmd = []
  610. for l:.arg in a:000
  611. if type(arg) ==# type([])
  612. call extend(list_args, arg)
  613. else
  614. call add(cmd, arg)
  615. endif
  616. endfor
  617. call extend(cmd, list_args)
  618. let env = {}
  619. endif
  620. let autoenv = {}
  621. let explicit_pathspec_option = 0
  622. let literal_pathspecs = 1
  623. let i = 0
  624. let arg_count = 0
  625. while i < len(cmd)
  626. if type(cmd[i]) == type({})
  627. if has_key(cmd[i], 'fugitive_dir') || has_key(cmd[i], 'git_dir')
  628. let dir = s:Dir(cmd[i])
  629. endif
  630. if has_key(cmd[i], 'git')
  631. let git = cmd[i].git
  632. endif
  633. if has_key(cmd[i], 'env')
  634. call extend(env, cmd[i].env)
  635. endif
  636. call remove(cmd, i)
  637. elseif cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
  638. let dir = s:Dir(remove(cmd, i))
  639. elseif cmd[i] =~# '^--git-dir='
  640. let dir = s:Dir(remove(cmd, i)[10:-1])
  641. elseif type(cmd[i]) ==# type(0)
  642. let dir = s:Dir(remove(cmd, i))
  643. elseif cmd[i] ==# '-c' && len(cmd) > i + 1
  644. let key = matchstr(cmd[i+1], '^[^=]*')
  645. if has_key(s:prepare_env, tolower(key))
  646. let var = s:prepare_env[tolower(key)]
  647. let val = matchstr(cmd[i+1], '=\zs.*')
  648. let autoenv[var] = val
  649. endif
  650. let i += 2
  651. elseif cmd[i] =~# '^--.*pathspecs$'
  652. let literal_pathspecs = (cmd[i] ==# '--literal-pathspecs')
  653. let explicit_pathspec_option = 1
  654. let i += 1
  655. elseif cmd[i] !~# '^-'
  656. let arg_count = len(cmd) - i
  657. break
  658. else
  659. let i += 1
  660. endif
  661. endwhile
  662. if !exists('dir')
  663. let dir = s:Dir()
  664. endif
  665. call extend(autoenv, env)
  666. call s:PrepareEnv(autoenv, dir)
  667. if len($GPG_TTY) && !has_key(autoenv, 'GPG_TTY')
  668. let autoenv.GPG_TTY = ''
  669. endif
  670. call s:PreparePathArgs(cmd, dir, literal_pathspecs, explicit_pathspec_option)
  671. return [dir, env, extend(autoenv, env), git, cmd[0 : -arg_count-1], arg_count ? cmd[-arg_count : -1] : []]
  672. endfunction
  673. function! s:BuildEnvPrefix(env) abort
  674. let pre = ''
  675. let env = items(a:env)
  676. if empty(env)
  677. return ''
  678. elseif &shell =~# '\%(powershell\|pwsh\)\%(\.exe\)\=$'
  679. return join(map(env, '"$Env:" . v:val[0] . " = ''" . substitute(v:val[1], "''", "''''", "g") . "''; "'), '')
  680. elseif s:winshell()
  681. return join(map(env, '"set " . substitute(join(v:val, "="), "[&|<>^]", "^^^&", "g") . "& "'), '')
  682. else
  683. return '/usr/bin/env ' . s:shellesc(map(env, 'join(v:val, "=")')) . ' '
  684. endif
  685. endfunction
  686. function! s:JobOpts(cmd, env) abort
  687. if empty(a:env)
  688. return [a:cmd, {}]
  689. elseif has('patch-8.2.0239') ||
  690. \ has('nvim') && api_info().version.api_level - api_info().version.api_prerelease >= 7 ||
  691. \ has('patch-8.0.0902') && !has('nvim') && (!has('win32') || empty(filter(keys(a:env), 'exists("$" . v:val)')))
  692. return [a:cmd, {'env': a:env}]
  693. endif
  694. let envlist = map(items(a:env), 'join(v:val, "=")')
  695. if !has('win32')
  696. return [['/usr/bin/env'] + envlist + a:cmd, {}]
  697. else
  698. let pre = join(map(envlist, '"set " . substitute(v:val, "[&|<>^]", "^^^&", "g") . "& "'), '')
  699. if len(a:cmd) == 3 && a:cmd[0] ==# 'cmd.exe' && a:cmd[1] ==# '/c'
  700. return [a:cmd[0:1] + [pre . a:cmd[2]], {}]
  701. else
  702. return [['cmd.exe', '/c', pre . s:WinShellEsc(a:cmd)], {}]
  703. endif
  704. endif
  705. endfunction
  706. function! s:PrepareJob(opts) abort
  707. let dict = {'argv': a:opts.argv}
  708. if has_key(a:opts, 'env')
  709. let dict.env = a:opts.env
  710. endif
  711. let [argv, jopts] = s:JobOpts(a:opts.argv, get(a:opts, 'env', {}))
  712. if has_key(a:opts, 'cwd')
  713. if has('patch-8.0.0902')
  714. let jopts.cwd = a:opts.cwd
  715. let dict.cwd = a:opts.cwd
  716. else
  717. throw 'fugitive: cwd unsupported'
  718. endif
  719. endif
  720. return [argv, jopts, dict]
  721. endfunction
  722. function! fugitive#PrepareJob(...) abort
  723. if a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'argv') && !has_key(a:1, 'args')
  724. return s:PrepareJob(a:1)
  725. endif
  726. let [repo, user_env, exec_env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
  727. let dir = s:GitDir(repo)
  728. let dict = {'git': git, 'git_dir': dir, 'flags': flags, 'args': args}
  729. if len(user_env)
  730. let dict.env = user_env
  731. endif
  732. let cmd = flags + args
  733. let tree = s:Tree(repo)
  734. if empty(tree) || index(cmd, '--') == len(cmd) - 1
  735. let dict.cwd = getcwd()
  736. call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
  737. else
  738. let dict.cwd = s:VimSlash(tree)
  739. call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
  740. if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
  741. call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
  742. endif
  743. endif
  744. call extend(cmd, git, 'keep')
  745. return s:JobOpts(cmd, exec_env) + [dict]
  746. endfunction
  747. function! fugitive#Execute(...) abort
  748. let cb = copy(a:000)
  749. let cmd = []
  750. let stdin = []
  751. while len(cb) && type(cb[0]) !=# type(function('tr'))
  752. if type(cb[0]) ==# type({}) && has_key(cb[0], 'stdin')
  753. if type(cb[0].stdin) == type([])
  754. call extend(stdin, cb[0].stdin)
  755. elseif type(cb[0].stdin) == type('')
  756. call extend(stdin, readfile(cb[0].stdin, 'b'))
  757. endif
  758. if len(keys(cb[0])) == 1
  759. call remove(cb, 0)
  760. continue
  761. endif
  762. endif
  763. call add(cmd, remove(cb, 0))
  764. endwhile
  765. let [argv, jopts, dict] = call('fugitive#PrepareJob', cmd)
  766. return s:JobExecute(argv, jopts, stdin, cb, dict)
  767. endfunction
  768. function! s:BuildShell(dir, env, git, args) abort
  769. let cmd = copy(a:args)
  770. let tree = s:Tree(a:dir)
  771. let pre = s:BuildEnvPrefix(a:env)
  772. if empty(tree) || index(cmd, '--') == len(cmd) - 1
  773. call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
  774. else
  775. call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
  776. if !s:cpath(tree . '/.git', a:dir) || len($GIT_DIR)
  777. call extend(cmd, ['--git-dir=' . FugitiveGitPath(a:dir)], 'keep')
  778. endif
  779. endif
  780. return pre . join(map(a:git + cmd, 's:shellesc(v:val)'))
  781. endfunction
  782. function! s:JobNvimCallback(lines, job, data, type) abort
  783. let a:lines[-1] .= remove(a:data, 0)
  784. call extend(a:lines, a:data)
  785. endfunction
  786. function! s:SystemList(cmd) abort
  787. let exit = []
  788. if exists('*jobstart')
  789. let lines = ['']
  790. let jopts = {
  791. \ 'on_stdout': function('s:JobNvimCallback', [lines]),
  792. \ 'on_stderr': function('s:JobNvimCallback', [lines]),
  793. \ 'on_exit': { j, code, _ -> add(exit, code) }}
  794. let job = jobstart(a:cmd, jopts)
  795. call chanclose(job, 'stdin')
  796. call jobwait([job])
  797. if empty(lines[-1])
  798. call remove(lines, -1)
  799. endif
  800. return [lines, exit[0]]
  801. elseif exists('*ch_close_in')
  802. let lines = []
  803. let jopts = {
  804. \ 'out_cb': { j, str -> add(lines, str) },
  805. \ 'err_cb': { j, str -> add(lines, str) },
  806. \ 'exit_cb': { j, code -> add(exit, code) }}
  807. let job = job_start(a:cmd, jopts)
  808. call ch_close_in(job)
  809. let sleep = has('patch-8.2.2366') ? 'sleep! 1m' : 'sleep 1m'
  810. while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
  811. exe sleep
  812. endwhile
  813. return [lines, exit[0]]
  814. else
  815. let [output, exec_error] = s:SystemError(s:shellesc(a:cmd))
  816. let lines = split(output, "\n", 1)
  817. if empty(lines[-1])
  818. call remove(lines, -1)
  819. endif
  820. return [lines, v:shell_error]
  821. endif
  822. endfunction
  823. function! fugitive#ShellCommand(...) abort
  824. let [repo, _, env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
  825. return s:BuildShell(s:GitDir(repo), env, git, flags + args)
  826. endfunction
  827. function! s:SystemError(cmd, ...) abort
  828. let cmd = type(a:cmd) == type([]) ? s:shellesc(a:cmd) : a:cmd
  829. try
  830. if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
  831. let shellredir = &shellredir
  832. if &shell =~# 'csh'
  833. set shellredir=>&
  834. else
  835. set shellredir=>%s\ 2>&1
  836. endif
  837. endif
  838. if exists('+guioptions') && &guioptions =~# '!'
  839. let guioptions = &guioptions
  840. set guioptions-=!
  841. endif
  842. let out = call('system', [cmd] + a:000)
  843. return [out, v:shell_error]
  844. catch /^Vim\%((\a\+)\)\=:E484:/
  845. let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
  846. call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
  847. call map(opts, 'v:val."=".eval("&".v:val)')
  848. call s:throw('failed to run `' . cmd . '` with ' . join(opts, ' '))
  849. finally
  850. if exists('shellredir')
  851. let &shellredir = shellredir
  852. endif
  853. if exists('guioptions')
  854. let &guioptions = guioptions
  855. endif
  856. endtry
  857. endfunction
  858. function! s:ChompStderr(...) abort
  859. let r = call('fugitive#Execute', a:000)
  860. return !r.exit_status ? '' : len(r.stderr) > 1 ? s:JoinChomp(r.stderr) : 'unknown Git error' . string(r)
  861. endfunction
  862. function! s:ChompDefault(default, ...) abort
  863. let r = call('fugitive#Execute', a:000)
  864. return r.exit_status ? a:default : s:JoinChomp(r.stdout)
  865. endfunction
  866. function! s:LinesError(...) abort
  867. let r = call('fugitive#Execute', a:000)
  868. if empty(r.stdout[-1])
  869. call remove(r.stdout, -1)
  870. endif
  871. return [r.exit_status ? [] : r.stdout, r.exit_status]
  872. endfunction
  873. function! s:NullError(cmd) abort
  874. let r = fugitive#Execute(a:cmd)
  875. let list = r.exit_status ? [] : split(tr(join(r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
  876. return [list, s:JoinChomp(r.stderr), r.exit_status]
  877. endfunction
  878. function! s:TreeChomp(...) abort
  879. let r = call('fugitive#Execute', a:000)
  880. if !r.exit_status
  881. return s:JoinChomp(r.stdout)
  882. endif
  883. throw 'fugitive: error running `' . call('fugitive#ShellCommand', a:000) . '`: ' . s:JoinChomp(r.stderr)
  884. endfunction
  885. function! s:StdoutToFile(out, cmd, ...) abort
  886. let [argv, jopts, _] = fugitive#PrepareJob(a:cmd)
  887. let exit = []
  888. if exists('*jobstart')
  889. call extend(jopts, {
  890. \ 'stdout_buffered': v:true,
  891. \ 'stderr_buffered': v:true,
  892. \ 'on_exit': { j, code, _ -> add(exit, code) }})
  893. let job = jobstart(argv, jopts)
  894. if a:0
  895. call chansend(job, a:1)
  896. endif
  897. call chanclose(job, 'stdin')
  898. call jobwait([job])
  899. if len(a:out)
  900. call writefile(jopts.stdout, a:out, 'b')
  901. endif
  902. return [join(jopts.stderr, "\n"), exit[0]]
  903. elseif exists('*ch_close_in')
  904. try
  905. let err = tempname()
  906. call extend(jopts, {
  907. \ 'out_io': len(a:out) ? 'file' : 'null',
  908. \ 'out_name': a:out,
  909. \ 'err_io': 'file',
  910. \ 'err_name': err,
  911. \ 'exit_cb': { j, code -> add(exit, code) }})
  912. let job = job_start(argv, jopts)
  913. if a:0
  914. call ch_sendraw(job, a:1)
  915. endif
  916. call ch_close_in(job)
  917. while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
  918. exe has('patch-8.2.2366') ? 'sleep! 1m' : 'sleep 1m'
  919. endwhile
  920. return [join(readfile(err, 'b'), "\n"), exit[0]]
  921. finally
  922. call delete(err)
  923. endtry
  924. elseif s:winshell() || &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
  925. throw 'fugitive: Vim 8 or higher required to use ' . &shell
  926. else
  927. let cmd = fugitive#ShellCommand(a:cmd)
  928. return s:SystemError(' (' . cmd . ' >' . a:out . ') ')
  929. endif
  930. endfunction
  931. let s:head_cache = {}
  932. function! fugitive#Head(...) abort
  933. let dir = a:0 > 1 ? a:2 : s:Dir()
  934. if empty(dir)
  935. return ''
  936. endif
  937. let file = FugitiveActualDir() . '/HEAD'
  938. let ftime = getftime(file)
  939. if ftime == -1
  940. return ''
  941. elseif ftime != get(s:head_cache, file, [-1])[0]
  942. let s:head_cache[file] = [ftime, readfile(file)[0]]
  943. endif
  944. let head = s:head_cache[file][1]
  945. let len = a:0 ? a:1 : 0
  946. if head =~# '^ref: '
  947. if len < 0
  948. return strpart(head, 5)
  949. else
  950. return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
  951. endif
  952. elseif head =~# '^\x\{40,\}$'
  953. return len < 0 ? head : strpart(head, 0, len)
  954. else
  955. return ''
  956. endif
  957. endfunction
  958. function! fugitive#RevParse(rev, ...) abort
  959. let hash = s:ChompDefault('', [a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
  960. if hash =~# '^\x\{40,\}$'
  961. return hash
  962. endif
  963. throw 'fugitive: failed to parse revision ' . a:rev
  964. endfunction
  965. " Section: Git config
  966. function! s:ConfigTimestamps(dir, dict) abort
  967. let files = ['/etc/gitconfig', '~/.gitconfig',
  968. \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
  969. if len(a:dir)
  970. call add(files, fugitive#Find('.git/config', a:dir))
  971. endif
  972. call extend(files, get(a:dict, 'include.path', []))
  973. return join(map(files, 'getftime(expand(v:val))'), ',')
  974. endfunction
  975. function! s:ConfigCallback(r, into) abort
  976. let dict = a:into[1]
  977. if has_key(dict, 'job')
  978. call remove(dict, 'job')
  979. endif
  980. let lines = a:r.exit_status ? [] : split(tr(join(a:r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
  981. for line in lines
  982. let key = matchstr(line, "^[^\n]*")
  983. if !has_key(dict, key)
  984. let dict[key] = []
  985. endif
  986. if len(key) ==# len(line)
  987. call add(dict[key], 1)
  988. else
  989. call add(dict[key], strpart(line, len(key) + 1))
  990. endif
  991. endfor
  992. let callbacks = remove(dict, 'callbacks')
  993. lockvar! dict
  994. let a:into[0] = s:ConfigTimestamps(dict.git_dir, dict)
  995. for callback in callbacks
  996. call call(callback[0], [dict] + callback[1:-1])
  997. endfor
  998. endfunction
  999. let s:config_prototype = {}
  1000. let s:config = {}
  1001. function! fugitive#ExpireConfig(...) abort
  1002. if !a:0 || a:1 is# 0
  1003. let s:config = {}
  1004. else
  1005. let key = a:1 is# '' ? '_' : s:GitDir(a:0 ? a:1 : -1)
  1006. if len(key) && has_key(s:config, key)
  1007. call remove(s:config, key)
  1008. endif
  1009. endif
  1010. endfunction
  1011. function! fugitive#Config(...) abort
  1012. let name = ''
  1013. let default = get(a:, 3, '')
  1014. if a:0 && type(a:1) == type(function('tr'))
  1015. let dir = s:Dir()
  1016. let callback = a:000
  1017. elseif a:0 > 1 && type(a:2) == type(function('tr'))
  1018. if type(a:1) == type({}) && has_key(a:1, 'GetAll')
  1019. if has_key(a:1, 'callbacks')
  1020. call add(a:1.callbacks, a:000[1:-1])
  1021. else
  1022. call call(a:2, [a:1] + a:000[2:-1])
  1023. endif
  1024. return a:1
  1025. else
  1026. let dir = s:Dir(a:1)
  1027. let callback = a:000[1:-1]
  1028. endif
  1029. elseif a:0 >= 2 && type(a:2) == type({}) && has_key(a:2, 'GetAll')
  1030. return get(fugitive#ConfigGetAll(a:1, a:2), 0, default)
  1031. elseif a:0 >= 2
  1032. let dir = s:Dir(a:2)
  1033. let name = a:1
  1034. elseif a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'GetAll')
  1035. return a:1
  1036. elseif a:0 == 1 && type(a:1) == type('') && a:1 =~# '^[[:alnum:]-]\+\.'
  1037. let dir = s:Dir()
  1038. let name = a:1
  1039. elseif a:0 == 1
  1040. let dir = s:Dir(a:1)
  1041. else
  1042. let dir = s:Dir()
  1043. endif
  1044. let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
  1045. let git_dir = s:GitDir(dir)
  1046. let dir_key = len(git_dir) ? git_dir : '_'
  1047. let [ts, dict] = get(s:config, dir_key, ['new', {}])
  1048. if !has_key(dict, 'job') && ts !=# s:ConfigTimestamps(git_dir, dict)
  1049. let dict = copy(s:config_prototype)
  1050. let dict.git_dir = git_dir
  1051. let into = ['running', dict]
  1052. let dict.callbacks = []
  1053. let exec = fugitive#Execute([dir, 'config', '--list', '-z', '--'], function('s:ConfigCallback'), into)
  1054. if has_key(exec, 'job')
  1055. let dict.job = exec.job
  1056. endif
  1057. let s:config[dir_key] = into
  1058. endif
  1059. if !exists('l:callback')
  1060. call fugitive#Wait(dict)
  1061. elseif has_key(dict, 'callbacks')
  1062. call add(dict.callbacks, callback)
  1063. else
  1064. call call(callback[0], [dict] + callback[1:-1])
  1065. endif
  1066. return len(name) ? get(fugitive#ConfigGetAll(name, dict), 0, default) : dict
  1067. endfunction
  1068. function! fugitive#ConfigGetAll(name, ...) abort
  1069. if a:0 && (type(a:name) !=# type('') || a:name !~# '^[[:alnum:]-]\+\.' && type(a:1) ==# type('') && a:1 =~# '^[[:alnum:]-]\+\.')
  1070. let config = fugitive#Config(a:name)
  1071. let name = a:1
  1072. else
  1073. let config = fugitive#Config(a:0 ? a:1 : s:Dir())
  1074. let name = a:name
  1075. endif
  1076. let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
  1077. call fugitive#Wait(config)
  1078. return name =~# '\.' ? copy(get(config, name, [])) : []
  1079. endfunction
  1080. function! fugitive#ConfigGetRegexp(pattern, ...) abort
  1081. if type(a:pattern) !=# type('')
  1082. let config = fugitive#Config(a:name)
  1083. let pattern = a:0 ? a:1 : '.*'
  1084. else
  1085. let config = fugitive#Config(a:0 ? a:1 : s:Dir())
  1086. let pattern = a:pattern
  1087. endif
  1088. call fugitive#Wait(config)
  1089. let filtered = map(filter(copy(config), 'v:key =~# "\\." && v:key =~# pattern'), 'copy(v:val)')
  1090. if pattern !~# '\\\@<!\%(\\\\\)*\\z[se]'
  1091. return filtered
  1092. endif
  1093. let transformed = {}
  1094. for [k, v] in items(filtered)
  1095. let k = matchstr(k, pattern)
  1096. if len(k)
  1097. let transformed[k] = v
  1098. endif
  1099. endfor
  1100. return transformed
  1101. endfunction
  1102. function! s:config_GetAll(name) dict abort
  1103. let name = substitute(a:name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
  1104. call fugitive#Wait(self)
  1105. return name =~# '\.' ? copy(get(self, name, [])) : []
  1106. endfunction
  1107. function! s:config_Get(name, ...) dict abort
  1108. return get(self.GetAll(a:name), 0, a:0 ? a:1 : '')
  1109. endfunction
  1110. function! s:config_GetRegexp(pattern) dict abort
  1111. return fugitive#ConfigGetRegexp(self, a:pattern)
  1112. endfunction
  1113. call s:add_methods('config', ['GetAll', 'Get', 'GetRegexp'])
  1114. function! s:RemoteDefault(dir) abort
  1115. let head = FugitiveHead(0, a:dir)
  1116. let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
  1117. let i = 10
  1118. while remote ==# '.' && i > 0
  1119. let head = matchstr(FugitiveConfigGet('branch.' . head . '.merge', a:dir), 'refs/heads/\zs.*')
  1120. let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
  1121. let i -= 1
  1122. endwhile
  1123. return remote =~# '^\.\=$' ? 'origin' : remote
  1124. endfunction
  1125. function! s:SshParseHost(value) abort
  1126. let patterns = []
  1127. let negates = []
  1128. for host in split(a:value, '\s\+')
  1129. let pattern = substitute(host, '[\\^$.*~?]', '\=submatch(0) == "*" ? ".*" : submatch(0) == "?" ? "." : "\\" . submatch(0)', 'g')
  1130. if pattern[0] ==# '!'
  1131. call add(negates, '\&\%(^' . pattern[1 : -1] . '$\)\@!')
  1132. else
  1133. call add(patterns, pattern)
  1134. endif
  1135. endfor
  1136. return '^\%(' . join(patterns, '\|') . '\)$' . join(negates, '')
  1137. endfunction
  1138. function! s:SshParseConfig(into, root, file, ...) abort
  1139. if !filereadable(a:file)
  1140. return a:into
  1141. endif
  1142. let host = a:0 ? a:1 : '^\%(.*\)$'
  1143. for line in readfile(a:file)
  1144. let key = tolower(matchstr(line, '^\s*\zs\w\+\ze\s'))
  1145. let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S')
  1146. if key ==# 'match'
  1147. let host = value ==# 'all' ? '^\%(.*\)$' : ''
  1148. elseif key ==# 'host'
  1149. let host = s:SshParseHost(value)
  1150. elseif key ==# 'include'
  1151. call s:SshParseInclude(a:into, a:root, host, value)
  1152. elseif len(key) && len(host)
  1153. call extend(a:into, {key : []}, 'keep')
  1154. call add(a:into[key], [host, value])
  1155. endif
  1156. endfor
  1157. return a:into
  1158. endfunction
  1159. function! s:SshParseInclude(into, root, host, value) abort
  1160. for glob in split(a:value)
  1161. if glob !~# '^/'
  1162. let glob = a:root . glob
  1163. endif
  1164. for file in split(glob(glob), "\n")
  1165. call s:SshParseConfig(a:into, a:root, file, a:host)
  1166. endfor
  1167. endfor
  1168. endfunction
  1169. unlet! s:ssh_config
  1170. function! fugitive#SshConfig(host, ...) abort
  1171. if !exists('s:ssh_config')
  1172. let s:ssh_config = {}
  1173. for file in [expand("~/.ssh/config"), "/etc/ssh/ssh_config"]
  1174. call s:SshParseConfig(s:ssh_config, substitute(file, '\w*$', '', ''), file)
  1175. endfor
  1176. endif
  1177. let host_config = {}
  1178. for key in a:0 ? a:1 : keys(s:ssh_config)
  1179. for [host_pattern, value] in get(s:ssh_config, key, [])
  1180. if a:host =~# host_pattern
  1181. let host_config[key] = value
  1182. break
  1183. endif
  1184. endfor
  1185. endfor
  1186. return host_config
  1187. endfunction
  1188. function! fugitive#SshHostAlias(authority) abort
  1189. let [_, user, host, port; __] = matchlist(a:authority, '^\%(\([^/@]\+\)@\)\=\(.\{-\}\)\%(:\(\d\+\)\)\=$')
  1190. let c = fugitive#SshConfig(host, ['user', 'hostname', 'port'])
  1191. if empty(user)
  1192. let user = get(c, 'user', '')
  1193. endif
  1194. if empty(port)
  1195. let port = get(c, 'port', '')
  1196. endif
  1197. return (len(user) ? user . '@' : '') . get(c, 'hostname', host) . (port =~# '^\%(22\)\=$' ? '' : ':' . port)
  1198. endfunction
  1199. function! s:CurlResponse(result) abort
  1200. let a:result.headers = {}
  1201. for line in a:result.exit_status ? [] : remove(a:result, 'stdout')
  1202. let header = matchlist(line, '^\([[:alnum:]-]\+\):\s\(.\{-\}\)'. "\r\\=$")
  1203. if len(header)
  1204. let k = tolower(header[1])
  1205. if has_key(a:result.headers, k)
  1206. let a:result.headers[k] .= ', ' . header[2]
  1207. else
  1208. let a:result.headers[k] = header[2]
  1209. endif
  1210. elseif empty(line)
  1211. break
  1212. endif
  1213. endfor
  1214. endfunction
  1215. let s:remote_headers = {}
  1216. function! fugitive#RemoteHttpHeaders(remote) abort
  1217. let remote = type(a:remote) ==# type({}) ? get(a:remote, 'remote', '') : a:remote
  1218. if type(remote) !=# type('') || remote !~# '^https\=://.' || !s:executable('curl')
  1219. return {}
  1220. endif
  1221. let remote = substitute(remote, '#.*', '', '')
  1222. if !has_key(s:remote_headers, remote)
  1223. let url = remote . '/info/refs?service=git-upload-pack'
  1224. let exec = s:JobExecute(
  1225. \ ['curl', '--disable', '--silent', '--max-time', '5', '-X', 'GET', '-I',
  1226. \ url], {}, [], [function('s:CurlResponse')], {})
  1227. call fugitive#Wait(exec)
  1228. let s:remote_headers[remote] = exec.headers
  1229. endif
  1230. return s:remote_headers[remote]
  1231. endfunction
  1232. function! s:UrlParse(url) abort
  1233. let scp_authority = matchstr(a:url, '^[^:/]\+\ze:\%(//\)\@!')
  1234. if len(scp_authority) && !(has('win32') && scp_authority =~# '^\a:[\/]')
  1235. let url = {'scheme': 'ssh', 'authority': s:UrlEncode(scp_authority), 'hash': '',
  1236. \ 'path': s:UrlEncode(strpart(a:url, len(scp_authority) + 1))}
  1237. elseif empty(a:url)
  1238. let url = {'scheme': '', 'authority': '', 'path': '', 'hash': ''}
  1239. else
  1240. let match = matchlist(a:url, '^\([[:alnum:].+-]\+\)://\([^/]*\)\(/[^#]*\)\=\(#.*\)\=$')
  1241. if empty(match)
  1242. let url = {'scheme': 'file', 'authority': '', 'hash': '',
  1243. \ 'path': s:UrlEncode(a:url)}
  1244. else
  1245. let url = {'scheme': match[1], 'authority': match[2], 'hash': match[4]}
  1246. let url.path = empty(match[3]) ? '/' : match[3]
  1247. endif
  1248. endif
  1249. return url
  1250. endfunction
  1251. function! s:UrlPopulate(string, into) abort
  1252. let url = a:into
  1253. let url.protocol = substitute(url.scheme, '.\zs$', ':', '')
  1254. let url.user = fugitive#UrlDecode(matchstr(url.authority, '.\{-\}\ze@', '', ''))
  1255. let url.host = substitute(url.authority, '.\{-\}@', '', '')
  1256. let url.hostname = substitute(url.host, ':\d\+$', '', '')
  1257. let url.port = matchstr(url.host, ':\zs\d\+$', '', '')
  1258. let url.origin = substitute(url.scheme, '.\zs$', '://', '') . url.host
  1259. let url.search = matchstr(url.path, '?.*')
  1260. let url.pathname = '/' . matchstr(url.path, '^/\=\zs[^?]*')
  1261. if (url.scheme ==# 'ssh' || url.scheme ==# 'git') && url.path[0:1] ==# '/~'
  1262. let url.path = strpart(url.path, 1)
  1263. endif
  1264. if url.path =~# '^/'
  1265. let url.href = url.scheme . '://' . url.authority . url.path . url.hash
  1266. elseif url.path =~# '^\~'
  1267. let url.href = url.scheme . '://' . url.authority . '/' . url.path . url.hash
  1268. elseif url.scheme ==# 'ssh' && url.authority !~# ':'
  1269. let url.href = url.authority . ':' . url.path . url.hash
  1270. else
  1271. let url.href = a:string
  1272. endif
  1273. let url.path = fugitive#UrlDecode(matchstr(url.path, '^[^?]*'))
  1274. let url.url = matchstr(url.href, '^[^#]*')
  1275. endfunction
  1276. function! s:RemoteResolve(url, flags) abort
  1277. let remote = s:UrlParse(a:url)
  1278. if remote.scheme =~# '^https\=$' && index(a:flags, ':nohttp') < 0
  1279. let headers = fugitive#RemoteHttpHeaders(a:url)
  1280. let loc = matchstr(get(headers, 'location', ''), '^https\=://.\{-\}\ze/info/refs?')
  1281. if len(loc)
  1282. let remote = s:UrlParse(loc)
  1283. else
  1284. let remote.headers = headers
  1285. endif
  1286. elseif remote.scheme ==# 'ssh'
  1287. let remote.authority = fugitive#SshHostAlias(remote.authority)
  1288. endif
  1289. return remote
  1290. endfunction
  1291. function! s:ConfigLengthSort(i1, i2) abort
  1292. return len(a:i2[0]) - len(a:i1[0])
  1293. endfunction
  1294. function! s:RemoteCallback(config, into, flags, cb) abort
  1295. if a:into.remote_name =~# '^\.\=$'
  1296. let a:into.remote_name = s:RemoteDefault(a:config)
  1297. endif
  1298. let url = a:into.remote_name
  1299. if url ==# '.git'
  1300. let url = s:GitDir(a:config)
  1301. elseif url !~# ':\|^/\|^\a:[\/]\|^\.\.\=/'
  1302. let url = FugitiveConfigGet('remote.' . url . '.url', a:config)
  1303. endif
  1304. let instead_of = []
  1305. for [k, vs] in items(fugitive#ConfigGetRegexp('^url\.\zs.\{-\}\ze\.insteadof$', a:config))
  1306. for v in vs
  1307. call add(instead_of, [v, k])
  1308. endfor
  1309. endfor
  1310. call sort(instead_of, 's:ConfigLengthSort')
  1311. for [orig, replacement] in instead_of
  1312. if strpart(url, 0, len(orig)) ==# orig
  1313. let url = replacement . strpart(url, len(orig))
  1314. break
  1315. endif
  1316. endfor
  1317. if index(a:flags, ':noresolve') < 0
  1318. call extend(a:into, s:RemoteResolve(url, a:flags))
  1319. else
  1320. call extend(a:into, s:UrlParse(url))
  1321. endif
  1322. call s:UrlPopulate(url, a:into)
  1323. if len(a:cb)
  1324. call call(a:cb[0], [a:into] + a:cb[1:-1])
  1325. endif
  1326. endfunction
  1327. function! s:Remote(dir, remote, flags, cb) abort
  1328. let into = {'remote_name': a:remote, 'git_dir': s:GitDir(a:dir)}
  1329. let config = fugitive#Config(a:dir, function('s:RemoteCallback'), into, a:flags, a:cb)
  1330. if len(a:cb)
  1331. return config
  1332. else
  1333. call fugitive#Wait(config)
  1334. return into
  1335. endif
  1336. endfunction
  1337. function! s:RemoteParseArgs(args) abort
  1338. " Extract ':noresolve' style flags and an optional callback
  1339. let args = []
  1340. let flags = []
  1341. let cb = copy(a:args)
  1342. while len(cb)
  1343. if type(cb[0]) ==# type(function('tr'))
  1344. break
  1345. elseif len(args) > 1 || type(cb[0]) ==# type('') && cb[0] =~# '^:'
  1346. call add(flags, remove(cb, 0))
  1347. else
  1348. call add(args, remove(cb, 0))
  1349. endif
  1350. endwhile
  1351. " From the remaining 0-2 arguments, extract the remote and Git config
  1352. let remote = ''
  1353. if empty(args)
  1354. let dir_or_config = s:Dir()
  1355. elseif len(args) == 1 && type(args[0]) ==# type('') && args[0] !~# '^/\|^\a:[\\/]'
  1356. let dir_or_config = s:Dir()
  1357. let remote = args[0]
  1358. elseif len(args) == 1
  1359. let dir_or_config = args[0]
  1360. if type(args[0]) ==# type({}) && has_key(args[0], 'remote_name')
  1361. let remote = args[0].remote_name
  1362. endif
  1363. elseif type(args[1]) !=# type('') || args[1] =~# '^/\|^\a:[\\/]'
  1364. let dir_or_config = args[1]
  1365. let remote = args[0]
  1366. else
  1367. let dir_or_config = args[0]
  1368. let remote = args[1]
  1369. endif
  1370. return [dir_or_config, remote, flags, cb]
  1371. endfunction
  1372. function! fugitive#Remote(...) abort
  1373. let [dir_or_config, remote, flags, cb] = s:RemoteParseArgs(a:000)
  1374. return s:Remote(dir_or_config, remote, flags, cb)
  1375. endfunction
  1376. function! s:RemoteUrlCallback(remote, callback) abort
  1377. return call(a:callback[0], [a:remote.url] + a:callback[1:-1])
  1378. endfunction
  1379. function! fugitive#RemoteUrl(...) abort
  1380. let [dir_or_config, remote_url, flags, cb] = s:RemoteParseArgs(a:000)
  1381. if len(cb)
  1382. let cb = [function('s:RemoteUrlCallback'), cb]
  1383. endif
  1384. let remote = s:Remote(dir_or_config, remote_url, flags, cb)
  1385. return get(remote, 'url', remote_url)
  1386. endfunction
  1387. " Section: Quickfix
  1388. function! s:QuickfixGet(nr, ...) abort
  1389. if a:nr < 0
  1390. return call('getqflist', a:000)
  1391. else
  1392. return call('getloclist', [a:nr] + a:000)
  1393. endif
  1394. endfunction
  1395. function! s:QuickfixSet(nr, ...) abort
  1396. if a:nr < 0
  1397. return call('setqflist', a:000)
  1398. else
  1399. return call('setloclist', [a:nr] + a:000)
  1400. endif
  1401. endfunction
  1402. function! s:QuickfixCreate(nr, opts) abort
  1403. if has('patch-7.4.2200')
  1404. call s:QuickfixSet(a:nr, [], ' ', a:opts)
  1405. else
  1406. call s:QuickfixSet(a:nr, [], ' ')
  1407. endif
  1408. endfunction
  1409. function! s:QuickfixOpen(nr, mods) abort
  1410. let mods = substitute(s:Mods(a:mods), '\<tab\>', '', '')
  1411. return mods . (a:nr < 0 ? 'c' : 'l').'open' . (mods =~# '\<vertical\>' ? ' 20' : '')
  1412. endfunction
  1413. function! s:QuickfixStream(nr, event, title, cmd, first, mods, callback, ...) abort
  1414. call s:BlurStatus()
  1415. let opts = {'title': a:title, 'context': {'items': []}}
  1416. call s:QuickfixCreate(a:nr, opts)
  1417. let event = (a:nr < 0 ? 'c' : 'l') . 'fugitive-' . a:event
  1418. exe s:DoAutocmd('QuickFixCmdPre ' . event)
  1419. let winnr = winnr()
  1420. exe s:QuickfixOpen(a:nr, a:mods)
  1421. if winnr != winnr()
  1422. wincmd p
  1423. endif
  1424. let buffer = []
  1425. let lines = s:SystemList(a:cmd)[0]
  1426. for line in lines
  1427. call extend(buffer, call(a:callback, a:000 + [line]))
  1428. if len(buffer) >= 20
  1429. let contexts = map(copy(buffer), 'get(v:val, "context", {})')
  1430. lockvar contexts
  1431. call extend(opts.context.items, contexts)
  1432. unlet contexts
  1433. call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
  1434. if a:mods !~# '\<silent\>'
  1435. redraw
  1436. endif
  1437. endif
  1438. endfor
  1439. call extend(buffer, call(a:callback, a:000 + [0]))
  1440. call extend(opts.context.items, map(copy(buffer), 'get(v:val, "context", {})'))
  1441. lockvar opts.context.items
  1442. call s:QuickfixSet(a:nr, buffer, 'a')
  1443. exe s:DoAutocmd('QuickFixCmdPost ' . event)
  1444. if a:first && len(s:QuickfixGet(a:nr))
  1445. return (a:nr < 0 ? 'cfirst' : 'lfirst')
  1446. else
  1447. return 'exe'
  1448. endif
  1449. endfunction
  1450. function! fugitive#Cwindow() abort
  1451. if &buftype == 'quickfix'
  1452. cwindow
  1453. else
  1454. botright cwindow
  1455. if &buftype == 'quickfix'
  1456. wincmd p
  1457. endif
  1458. endif
  1459. endfunction
  1460. " Section: Repository Object
  1461. let s:repo_prototype = {}
  1462. function! fugitive#repo(...) abort
  1463. let dir = a:0 ? s:GitDir(a:1) : (len(s:GitDir()) ? s:GitDir() : FugitiveExtractGitDir(expand('%:p')))
  1464. if dir !=# ''
  1465. return extend({'git_dir': dir, 'fugitive_dir': dir}, s:repo_prototype, 'keep')
  1466. endif
  1467. throw 'fugitive: not a Git repository'
  1468. endfunction
  1469. function! s:repo_dir(...) dict abort
  1470. if !a:0
  1471. return self.git_dir
  1472. endif
  1473. throw 'fugitive: fugitive#repo().dir("...") has been replaced by FugitiveFind(".git/...")'
  1474. endfunction
  1475. function! s:repo_tree(...) dict abort
  1476. let tree = s:Tree(self.git_dir)
  1477. if empty(tree)
  1478. throw 'fugitive: no work tree'
  1479. elseif !a:0
  1480. return tree
  1481. endif
  1482. throw 'fugitive: fugitive#repo().tree("...") has been replaced by FugitiveFind(":(top)...")'
  1483. endfunction
  1484. function! s:repo_bare() dict abort
  1485. throw 'fugitive: fugitive#repo().bare() has been replaced by !empty(FugitiveWorkTree())'
  1486. endfunction
  1487. function! s:repo_find(object) dict abort
  1488. throw 'fugitive: fugitive#repo().find(...) has been replaced by FugitiveFind(...)'
  1489. endfunction
  1490. function! s:repo_translate(rev) dict abort
  1491. throw 'fugitive: fugitive#repo().translate(...) has been replaced by FugitiveFind(...)'
  1492. endfunction
  1493. function! s:repo_head(...) dict abort
  1494. throw 'fugitive: fugitive#repo().head(...) has been replaced by FugitiveHead(...)'
  1495. endfunction
  1496. call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
  1497. function! s:repo_git_command(...) dict abort
  1498. throw 'fugitive: fugitive#repo().git_command(...) has been replaced by FugitiveShellCommand(...)'
  1499. endfunction
  1500. function! s:repo_git_chomp(...) dict abort
  1501. silent return substitute(system(fugitive#ShellCommand(a:000, self.git_dir)), '\n$', '', '')
  1502. endfunction
  1503. function! s:repo_git_chomp_in_tree(...) dict abort
  1504. return call(self.git_chomp, a:000, self)
  1505. endfunction
  1506. function! s:repo_rev_parse(rev) dict abort
  1507. throw 'fugitive: fugitive#repo().rev_parse(...) has been replaced by FugitiveExecute("rev-parse", "--verify", ...).stdout'
  1508. endfunction
  1509. call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
  1510. function! s:repo_config(name) dict abort
  1511. return FugitiveConfigGet(a:name, self.git_dir)
  1512. endfunction
  1513. call s:add_methods('repo',['config'])
  1514. " Section: File API
  1515. function! s:DirCommitFile(path) abort
  1516. let vals = matchlist(s:Slash(a:path), s:dir_commit_file)
  1517. if empty(vals)
  1518. return ['', '', '']
  1519. endif
  1520. return [s:Dir(fugitive#UrlDecode(vals[1])), vals[2], empty(vals[2]) ? '/.git/index' : fugitive#UrlDecode(vals[3])]
  1521. endfunction
  1522. function! s:DirRev(url) abort
  1523. let [dir, commit, file] = s:DirCommitFile(a:url)
  1524. return [dir, commit . file ==# '/.git/index' ? ':' : (!empty(dir) && commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
  1525. endfunction
  1526. function! fugitive#Parse(url) abort
  1527. return reverse(s:DirRev(a:url))
  1528. endfunction
  1529. let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
  1530. function! s:MergeHead(dir) abort
  1531. let dir = fugitive#Find('.git/', a:dir)
  1532. for head in s:merge_heads
  1533. if filereadable(dir . head)
  1534. return head
  1535. endif
  1536. endfor
  1537. return ''
  1538. endfunction
  1539. function! s:Owner(path, ...) abort
  1540. let dir = a:0 ? s:Dir(a:1) : s:Dir()
  1541. if empty(dir)
  1542. return ''
  1543. endif
  1544. let actualdir = fugitive#Find('.git/', dir)
  1545. let [pdir, commit, file] = s:DirCommitFile(a:path)
  1546. if s:SameRepo(dir, pdir)
  1547. if commit =~# '^\x\{40,\}$'
  1548. return commit
  1549. elseif commit ==# '2'
  1550. return '@'
  1551. elseif commit ==# '0'
  1552. return ''
  1553. endif
  1554. let merge_head = s:MergeHead(dir)
  1555. if empty(merge_head)
  1556. return ''
  1557. endif
  1558. if commit ==# '3'
  1559. return merge_head
  1560. elseif commit ==# '1'
  1561. return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
  1562. endif
  1563. endif
  1564. let path = fnamemodify(a:path, ':p')
  1565. if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
  1566. return strpart(path, len(actualdir))
  1567. endif
  1568. let refs = fugitive#Find('.git/refs', dir)
  1569. if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
  1570. return strpart(path, len(refs) - 4)
  1571. endif
  1572. return ''
  1573. endfunction
  1574. function! fugitive#Real(url) abort
  1575. if empty(a:url)
  1576. return ''
  1577. endif
  1578. let [dir, commit, file] = s:DirCommitFile(a:url)
  1579. if len(dir)
  1580. let tree = s:Tree(dir)
  1581. return s:VimSlash((len(tree) ? tree : s:GitDir(dir)) . file)
  1582. endif
  1583. let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
  1584. if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
  1585. let url = {pre}Real(a:url)
  1586. else
  1587. let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
  1588. endif
  1589. return s:VimSlash(empty(url) ? a:url : url)
  1590. endfunction
  1591. function! fugitive#Path(url, ...) abort
  1592. if empty(a:url)
  1593. return ''
  1594. endif
  1595. let repo = call('s:Dir', a:000[1:-1])
  1596. let dir_s = fugitive#Find('.git/', repo)
  1597. let tree = fugitive#Find(':/', repo)
  1598. if !a:0
  1599. return fugitive#Real(a:url)
  1600. elseif a:1 =~# '\.$'
  1601. let path = s:Slash(fugitive#Real(a:url))
  1602. let cwd = getcwd()
  1603. let lead = ''
  1604. while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
  1605. if s:cpath(cwd . '/', path[0 : len(cwd)])
  1606. if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
  1607. break
  1608. endif
  1609. return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
  1610. endif
  1611. let cwd = fnamemodify(cwd, ':h')
  1612. let lead .= '../'
  1613. endwhile
  1614. return a:1[0:-2] . path
  1615. endif
  1616. let url = a:url
  1617. let temp_state = s:TempState(url)
  1618. if has_key(temp_state, 'origin_bufnr')
  1619. let url = bufname(temp_state.origin_bufnr)
  1620. endif
  1621. let url = s:Slash(fnamemodify(url, ':p'))
  1622. if url =~# '/$' && s:Slash(a:url) !~# '/$'
  1623. let url = url[0:-2]
  1624. endif
  1625. let [argdir, commit, file] = s:DirCommitFile(url)
  1626. if !empty(argdir) && !s:SameRepo(argdir, repo)
  1627. let file = ''
  1628. elseif len(dir_s) && s:cpath(strpart(url, 0, len(dir_s)), dir_s)
  1629. let file = '/.git' . strpart(url, len(dir_s)-1)
  1630. elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
  1631. let file = url[len(tree) : -1]
  1632. elseif s:cpath(url) ==# s:cpath(tree)
  1633. let file = '/'
  1634. endif
  1635. if empty(file) && a:1 =~# '^$\|^[.:]/$'
  1636. return FugitiveGitPath(fugitive#Real(a:url))
  1637. endif
  1638. return substitute(file, '^/', '\=a:1', '')
  1639. endfunction
  1640. function! s:Relative(...) abort
  1641. return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
  1642. endfunction
  1643. function! fugitive#Find(object, ...) abort
  1644. if type(a:object) == type(0)
  1645. let name = bufname(a:object)
  1646. return s:VimSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
  1647. elseif a:object =~# '^[~$]'
  1648. let prefix = matchstr(a:object, '^[~$]\i*')
  1649. let owner = expand(prefix)
  1650. return s:VimSlash(FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix))))
  1651. endif
  1652. let rev = s:Slash(a:object)
  1653. if rev =~# '^\a\+://' && rev !~# '^fugitive:'
  1654. return rev
  1655. elseif rev =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
  1656. return s:VimSlash(a:object)
  1657. elseif rev =~# '^\.\.\=\%(/\|$\)'
  1658. return s:VimSlash(simplify(getcwd() . '/' . a:object))
  1659. endif
  1660. let dir = call('s:GitDir', a:000)
  1661. if empty(dir)
  1662. let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs\%(\.\.\=$\|\.\.\=/.*\|/.*\|\w:/.*\)')
  1663. let dir = FugitiveExtractGitDir(file)
  1664. if empty(dir)
  1665. return ''
  1666. endif
  1667. endif
  1668. let tree = s:Tree(dir)
  1669. let urlprefix = s:DirUrlPrefix(dir)
  1670. let base = len(tree) ? tree : urlprefix . '0'
  1671. if rev ==# '.git'
  1672. let f = len(tree) && len(getftype(tree . '/.git')) ? tree . '/.git' : dir
  1673. elseif rev =~# '^\.git/'
  1674. let f = strpart(rev, 5)
  1675. let fdir = simplify(FugitiveActualDir(dir) . '/')
  1676. let cdir = simplify(FugitiveCommonDir(dir) . '/')
  1677. if f =~# '^\.\./\.\.\%(/\|$\)'
  1678. let f = simplify(len(tree) ? tree . f[2:-1] : fdir . f)
  1679. elseif f =~# '^\.\.\%(/\|$\)'
  1680. let f = s:PathJoin(base, f[2:-1])
  1681. elseif cdir !=# fdir && (
  1682. \ f =~# '^\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
  1683. \ f !~# '^\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
  1684. \ getftime(fdir . f) < 0 && getftime(cdir . f) >= 0)
  1685. let f = simplify(cdir . f)
  1686. else
  1687. let f = simplify(fdir . f)
  1688. endif
  1689. elseif rev ==# ':/'
  1690. let f = tree
  1691. elseif rev =~# '^\.\%(/\|$\)'
  1692. let f = s:PathJoin(base, rev[1:-1])
  1693. elseif rev =~# '^::\%(/\|\a\+\:\)'
  1694. let f = rev[2:-1]
  1695. elseif rev =~# '^::\.\.\=\%(/\|$\)'
  1696. let f = simplify(getcwd() . '/' . rev[2:-1])
  1697. elseif rev =~# '^::'
  1698. let f = s:PathJoin(base, '/' . rev[2:-1])
  1699. elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
  1700. let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
  1701. if s:cpath(base . '/', (f . '/')[0 : len(base)])
  1702. let f = s:PathJoin(urlprefix, +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1))
  1703. else
  1704. let altdir = FugitiveExtractGitDir(f)
  1705. if len(altdir) && !s:cpath(dir, altdir)
  1706. return fugitive#Find(a:object, altdir)
  1707. endif
  1708. endif
  1709. elseif rev =~# '^:[0-3]:'
  1710. let f = s:PathJoin(urlprefix, rev[1] . '/' . rev[3:-1])
  1711. elseif rev ==# ':'
  1712. let f = urlprefix
  1713. elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
  1714. let f = matchstr(rev, ')\zs.*')
  1715. if f=~# '^\.\.\=\%(/\|$\)'
  1716. let f = simplify(getcwd() . '/' . f)
  1717. elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
  1718. let f = s:PathJoin(base, '/' . f)
  1719. endif
  1720. elseif rev =~# '^:/\@!'
  1721. let f = s:PathJoin(urlprefix, '0/' . rev[1:-1])
  1722. else
  1723. if !exists('f')
  1724. let commit = matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\|^:.*')
  1725. let file = substitute(matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\zs:.*'), '^:', '/', '')
  1726. if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
  1727. let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
  1728. if s:cpath(base . '/', (file . '/')[0 : len(base)])
  1729. let file = '/' . strpart(file, len(base) + 1)
  1730. else
  1731. let altdir = FugitiveExtractGitDir(file)
  1732. if len(altdir) && !s:cpath(dir, altdir)
  1733. return fugitive#Find(a:object, altdir)
  1734. endif
  1735. return file
  1736. endif
  1737. endif
  1738. let commits = split(commit, '\.\.\.-\@!', 1)
  1739. if len(commits) == 2
  1740. call map(commits, 'empty(v:val) ? "@" : v:val')
  1741. let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
  1742. endif
  1743. if commit !~# '^[0-9a-f]\{40,\}$\|^$'
  1744. let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit . (len(file) ? '^{}' : ''), '--']), '\<[0-9a-f]\{40,\}\>')
  1745. if empty(commit) && len(file)
  1746. let commit = repeat('0', 40)
  1747. endif
  1748. endif
  1749. if len(commit)
  1750. let f = s:PathJoin(urlprefix, commit . file)
  1751. else
  1752. let f = s:PathJoin(base, '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', ''))
  1753. endif
  1754. endif
  1755. endif
  1756. return s:VimSlash(f)
  1757. endfunction
  1758. function! s:Generate(object, ...) abort
  1759. let dir = a:0 ? a:1 : s:Dir()
  1760. let f = fugitive#Find(a:object, dir)
  1761. if !empty(f)
  1762. return f
  1763. elseif a:object ==# ':/'
  1764. return len(dir) ? s:VimSlash(s:DirUrlPrefix(dir) . '0') : '.'
  1765. endif
  1766. let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\=\zs.*')
  1767. return empty(file) ? '' : fnamemodify(s:VimSlash(file), ':p')
  1768. endfunction
  1769. function! s:DotRelative(path, ...) abort
  1770. let cwd = a:0 ? a:1 : getcwd()
  1771. let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
  1772. if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
  1773. return '.' . strpart(path, len(cwd))
  1774. endif
  1775. return a:path
  1776. endfunction
  1777. function! fugitive#Object(...) abort
  1778. let dir = a:0 > 1 ? s:Dir(a:2) : s:Dir()
  1779. let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
  1780. if !s:SameRepo(dir, fdir)
  1781. let rev = ''
  1782. endif
  1783. let tree = s:Tree(dir)
  1784. let full = a:0 ? a:1 : s:BufName('%')
  1785. let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
  1786. if empty(rev) && empty(tree)
  1787. return FugitiveGitPath(full)
  1788. elseif empty(rev)
  1789. let rev = fugitive#Path(full, './', dir)
  1790. if rev =~# '^\./.git\%(/\|$\)'
  1791. return FugitiveGitPath(full)
  1792. endif
  1793. endif
  1794. if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
  1795. return rev
  1796. else
  1797. return FugitiveGitPath(tree . rev[1:-1])
  1798. endif
  1799. endfunction
  1800. let s:var = '\%(<\%(cword\|cWORD\|cexpr\|cfile\|sfile\|slnum\|afile\|abuf\|amatch' . (has('clientserver') ? '\|client' : '') . '\)>\|%\|#<\=\d\+\|##\=\)'
  1801. let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
  1802. let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
  1803. function! s:BufName(var) abort
  1804. if a:var ==# '%'
  1805. return bufname(get(s:TempState(), 'origin_bufnr', ''))
  1806. elseif a:var =~# '^#\d*$'
  1807. let nr = get(s:TempState(+a:var[1:-1]), 'origin_bufnr', '')
  1808. return bufname(nr ? nr : +a:var[1:-1])
  1809. else
  1810. return expand(a:var)
  1811. endif
  1812. endfunction
  1813. function! s:ExpandVar(other, var, flags, esc, ...) abort
  1814. let cwd = a:0 ? a:1 : getcwd()
  1815. if a:other =~# '^\'
  1816. return a:other[1:-1]
  1817. elseif a:other =~# '^'''
  1818. return substitute(a:other[1:-2], "''", "'", "g")
  1819. elseif a:other =~# '^"'
  1820. return substitute(a:other[1:-2], '""', '"', "g")
  1821. elseif a:other =~# '^!'
  1822. let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
  1823. let owner = s:Owner(buffer)
  1824. return len(owner) ? owner : '@'
  1825. elseif a:other =~# '^\~[~.]$'
  1826. return s:Slash(getcwd())
  1827. elseif len(a:other)
  1828. return expand(a:other)
  1829. elseif a:var ==# '<cfile>'
  1830. let bufnames = [expand('<cfile>')]
  1831. if get(maparg('<Plug><cfile>', 'c', 0, 1), 'expr')
  1832. try
  1833. let bufnames = [eval(maparg('<Plug><cfile>', 'c'))]
  1834. if bufnames[0] ==# "\<C-R>\<C-F>"
  1835. let bufnames = [expand('<cfile>')]
  1836. endif
  1837. catch
  1838. endtry
  1839. endif
  1840. elseif a:var =~# '^<'
  1841. let bufnames = [s:BufName(a:var)]
  1842. elseif a:var ==# '##'
  1843. let bufnames = map(argv(), 'fugitive#Real(v:val)')
  1844. else
  1845. let bufnames = [fugitive#Real(s:BufName(a:var))]
  1846. endif
  1847. let files = []
  1848. for bufname in bufnames
  1849. let flags = a:flags
  1850. let file = s:DotRelative(bufname, cwd)
  1851. while len(flags)
  1852. let flag = matchstr(flags, s:flag)
  1853. let flags = strpart(flags, len(flag))
  1854. if flag ==# ':.'
  1855. let file = s:DotRelative(fugitive#Real(file), cwd)
  1856. else
  1857. let file = fnamemodify(file, flag)
  1858. endif
  1859. endwhile
  1860. let file = s:Slash(file)
  1861. if file =~# '^fugitive://'
  1862. let [dir, commit, file_candidate] = s:DirCommitFile(file)
  1863. let tree = s:Tree(dir)
  1864. if len(tree) && len(file_candidate)
  1865. let file = (commit =~# '^.$' ? ':' : '') . commit . ':' .
  1866. \ s:DotRelative(tree . file_candidate)
  1867. elseif empty(file_candidate) && commit !~# '^.$'
  1868. let file = commit
  1869. endif
  1870. endif
  1871. call add(files, len(a:esc) ? shellescape(file) : file)
  1872. endfor
  1873. return join(files, "\1")
  1874. endfunction
  1875. let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
  1876. function! s:Expand(rev, ...) abort
  1877. if a:rev =~# '^>' && s:Slash(@%) =~# '^fugitive://' && empty(s:DirCommitFile(@%)[1])
  1878. return s:Slash(@%)
  1879. elseif a:rev =~# '^>\=:[0-3]$'
  1880. let file = len(expand('%')) ? a:rev[-2:-1] . ':%' : '%'
  1881. elseif a:rev =~# '^>\%(:\=/\)\=$'
  1882. let file = '%'
  1883. elseif a:rev =~# '^>[> ]\@!' && @% !~# '^fugitive:' && s:Slash(@%) =~# '://\|^$'
  1884. let file = '%'
  1885. elseif a:rev ==# '>:'
  1886. let file = empty(s:DirCommitFile(@%)[0]) ? ':0:%' : '%'
  1887. elseif a:rev =~# '^>[> ]\@!'
  1888. let rev = (a:rev =~# '^>[~^]' ? '!' : '') . a:rev[1:-1]
  1889. let prefix = matchstr(rev, '^\%(\\.\|{[^{}]*}\|[^:]\)*')
  1890. if prefix !=# rev
  1891. let file = rev
  1892. else
  1893. let file = len(expand('%')) ? rev . ':%' : '%'
  1894. endif
  1895. elseif s:Slash(a:rev) =~# '^\a\a\+://'
  1896. let file = substitute(a:rev, '\\\@<!\%(#\a\|%\x\x\)', '\\&', 'g')
  1897. else
  1898. let file = a:rev
  1899. endif
  1900. return substitute(file,
  1901. \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\|^\~[~.]\)\|' . s:expand,
  1902. \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd()), "\1", " ")', 'g')
  1903. endfunction
  1904. function! fugitive#Expand(object) abort
  1905. return substitute(a:object,
  1906. \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\|^\~[~.]\)\|' . s:expand,
  1907. \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5)), "\1", " ")', 'g')
  1908. endfunction
  1909. function! s:SplitExpandChain(string, ...) abort
  1910. let list = []
  1911. let string = a:string
  1912. let dquote = '"\%([^"]\|""\|\\"\)*"\|'
  1913. let cwd = a:0 ? a:1 : getcwd()
  1914. while string =~# '\S'
  1915. if string =~# '^\s*|'
  1916. return [list, substitute(string, '^\s*', '', '')]
  1917. endif
  1918. let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
  1919. let string = strpart(string, len(arg))
  1920. let arg = substitute(arg, '^\s\+', '', '')
  1921. if !exists('seen_separator')
  1922. let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\%((literal)\)\=\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
  1923. \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
  1924. endif
  1925. let arg = substitute(arg,
  1926. \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\|^\~[~]\|^\~\w*\|\$\w\+\)\|' . s:expand,
  1927. \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
  1928. call extend(list, split(arg, "\1", 1))
  1929. if arg ==# '--'
  1930. let seen_separator = 1
  1931. endif
  1932. endwhile
  1933. return [list, '']
  1934. endfunction
  1935. let s:trees = {}
  1936. let s:indexes = {}
  1937. function! s:TreeInfo(dir, commit) abort
  1938. let key = s:GitDir(a:dir)
  1939. if a:commit =~# '^:\=[0-3]$'
  1940. let index = get(s:indexes, key, [])
  1941. let newftime = getftime(fugitive#Find('.git/index', a:dir))
  1942. if get(index, 0, -2) < newftime
  1943. let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
  1944. let s:indexes[key] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
  1945. if exec_error
  1946. return [{}, -1]
  1947. endif
  1948. for line in lines
  1949. let [info, filename] = split(line, "\t")
  1950. let [mode, sha, stage] = split(info, '\s\+')
  1951. let s:indexes[key][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
  1952. while filename =~# '/'
  1953. let filename = substitute(filename, '/[^/]*$', '', '')
  1954. let s:indexes[key][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
  1955. endwhile
  1956. endfor
  1957. endif
  1958. return [get(s:indexes[key][1], a:commit[-1:-1], {}), newftime]
  1959. elseif a:commit =~# '^\x\{40,\}$'
  1960. if !has_key(s:trees, key)
  1961. let s:trees[key] = {}
  1962. endif
  1963. if !has_key(s:trees[key], a:commit)
  1964. let ftime = s:ChompDefault('', [a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
  1965. if empty(ftime)
  1966. let s:trees[key][a:commit] = [{}, -1]
  1967. return s:trees[key][a:commit]
  1968. endif
  1969. let s:trees[key][a:commit] = [{}, +ftime]
  1970. let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
  1971. if exec_error
  1972. return s:trees[key][a:commit]
  1973. endif
  1974. for line in lines
  1975. let [info, filename] = split(line, "\t")
  1976. let [mode, type, sha, size] = split(info, '\s\+')
  1977. let s:trees[key][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
  1978. endfor
  1979. endif
  1980. return s:trees[key][a:commit]
  1981. endif
  1982. return [{}, -1]
  1983. endfunction
  1984. function! s:PathInfo(url) abort
  1985. let [dir, commit, file] = s:DirCommitFile(a:url)
  1986. if empty(dir) || !get(g:, 'fugitive_file_api', 1)
  1987. return [-1, '000000', '', '', -1]
  1988. endif
  1989. let path = substitute(file[1:-1], '/*$', '', '')
  1990. let [tree, ftime] = s:TreeInfo(dir, commit)
  1991. let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
  1992. if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
  1993. return [-1, '000000', '', '', -1]
  1994. else
  1995. return entry
  1996. endif
  1997. endfunction
  1998. function! fugitive#simplify(url) abort
  1999. let [dir, commit, file] = s:DirCommitFile(a:url)
  2000. if empty(dir)
  2001. return ''
  2002. elseif empty(commit)
  2003. return s:VimSlash(s:DirUrlPrefix(simplify(s:GitDir(dir))))
  2004. endif
  2005. if file =~# '/\.\.\%(/\|$\)'
  2006. let tree = s:Tree(dir)
  2007. if len(tree)
  2008. let path = simplify(tree . file)
  2009. if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
  2010. return s:VimSlash(path)
  2011. endif
  2012. endif
  2013. endif
  2014. return s:VimSlash(s:PathJoin(s:DirUrlPrefix(simplify(s:GitDir(dir))), commit . simplify(file)))
  2015. endfunction
  2016. function! fugitive#resolve(url) abort
  2017. let url = fugitive#simplify(a:url)
  2018. if url =~? '^fugitive:'
  2019. return url
  2020. else
  2021. return resolve(url)
  2022. endif
  2023. endfunction
  2024. function! fugitive#getftime(url) abort
  2025. return s:PathInfo(a:url)[0]
  2026. endfunction
  2027. function! fugitive#getfsize(url) abort
  2028. let entry = s:PathInfo(a:url)
  2029. if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
  2030. let dir = s:DirCommitFile(a:url)[0]
  2031. let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
  2032. endif
  2033. return entry[4]
  2034. endfunction
  2035. function! fugitive#getftype(url) abort
  2036. return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
  2037. endfunction
  2038. function! fugitive#filereadable(url) abort
  2039. return s:PathInfo(a:url)[2] ==# 'blob'
  2040. endfunction
  2041. function! fugitive#filewritable(url) abort
  2042. let [dir, commit, file] = s:DirCommitFile(a:url)
  2043. if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
  2044. return 0
  2045. endif
  2046. return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
  2047. endfunction
  2048. function! fugitive#isdirectory(url) abort
  2049. return s:PathInfo(a:url)[2] ==# 'tree'
  2050. endfunction
  2051. function! fugitive#getfperm(url) abort
  2052. let [dir, commit, file] = s:DirCommitFile(a:url)
  2053. let perm = getfperm(dir)
  2054. let fperm = s:PathInfo(a:url)[1]
  2055. if fperm ==# '040000'
  2056. let fperm = '000755'
  2057. endif
  2058. if fperm !~# '[15]'
  2059. let perm = tr(perm, 'x', '-')
  2060. endif
  2061. if fperm !~# '[45]$'
  2062. let perm = tr(perm, 'rw', '--')
  2063. endif
  2064. if commit !~# '^\d$'
  2065. let perm = tr(perm, 'w', '-')
  2066. endif
  2067. return perm ==# '---------' ? '' : perm
  2068. endfunction
  2069. function! s:UpdateIndex(dir, info) abort
  2070. let info = join(a:info[0:-2]) . "\t" . a:info[-1] . "\n"
  2071. let [error, exec_error] = s:StdoutToFile('', [a:dir, 'update-index', '--index-info'], info)
  2072. return !exec_error ? '' : len(error) ? error : 'unknown update-index error'
  2073. endfunction
  2074. function! fugitive#setfperm(url, perm) abort
  2075. let [dir, commit, file] = s:DirCommitFile(a:url)
  2076. let entry = s:PathInfo(a:url)
  2077. let perm = fugitive#getfperm(a:url)
  2078. if commit !~# '^\d$' || entry[2] !=# 'blob' ||
  2079. \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
  2080. return -2
  2081. endif
  2082. let error = s:UpdateIndex(dir, [a:perm =~# 'x' ? '000755' : '000644', entry[3], commit, file[1:-1]])
  2083. return len(error) ? -1 : 0
  2084. endfunction
  2085. if !exists('s:blobdirs')
  2086. let s:blobdirs = {}
  2087. endif
  2088. function! s:BlobTemp(url) abort
  2089. let [dir, commit, file] = s:DirCommitFile(a:url)
  2090. if empty(file)
  2091. return ''
  2092. endif
  2093. let key = s:GitDir(dir)
  2094. if !has_key(s:blobdirs, key)
  2095. let s:blobdirs[key] = tempname()
  2096. endif
  2097. let tempfile = s:blobdirs[key] . '/' . commit . file
  2098. let tempparent = fnamemodify(tempfile, ':h')
  2099. if !isdirectory(tempparent)
  2100. call mkdir(tempparent, 'p')
  2101. elseif isdirectory(tempfile)
  2102. if commit =~# '^\d$' && has('patch-7.4.1107')
  2103. call delete(tempfile, 'rf')
  2104. else
  2105. return ''
  2106. endif
  2107. endif
  2108. if commit =~# '^\d$' || !filereadable(tempfile)
  2109. let rev = s:DirRev(a:url)[1]
  2110. let blob_or_filters = fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
  2111. let exec_error = s:StdoutToFile(tempfile, [dir, 'cat-file', blob_or_filters, rev])[1]
  2112. if exec_error
  2113. call delete(tempfile)
  2114. return ''
  2115. endif
  2116. endif
  2117. return s:Resolve(tempfile)
  2118. endfunction
  2119. function! fugitive#readfile(url, ...) abort
  2120. let entry = s:PathInfo(a:url)
  2121. if entry[2] !=# 'blob'
  2122. return []
  2123. endif
  2124. let temp = s:BlobTemp(a:url)
  2125. if empty(temp)
  2126. return []
  2127. endif
  2128. return call('readfile', [temp] + a:000)
  2129. endfunction
  2130. function! fugitive#writefile(lines, url, ...) abort
  2131. let url = type(a:url) ==# type('') ? a:url : ''
  2132. let [dir, commit, file] = s:DirCommitFile(url)
  2133. let entry = s:PathInfo(url)
  2134. if commit =~# '^\d$' && entry[2] !=# 'tree'
  2135. let temp = tempname()
  2136. if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
  2137. call writefile(fugitive#readfile(url, 'b'), temp, 'b')
  2138. endif
  2139. call call('writefile', [a:lines, temp] + a:000)
  2140. let hash = s:ChompDefault('', [dir, '--literal-pathspecs', 'hash-object', '-w', FugitiveGitPath(temp)])
  2141. let mode = entry[1] !=# '000000' ? entry[1] : '100644'
  2142. if hash =~# '^\x\{40,\}$'
  2143. let error = s:UpdateIndex(dir, [mode, hash, commit, file[1:-1]])
  2144. if empty(error)
  2145. return 0
  2146. endif
  2147. endif
  2148. endif
  2149. return call('writefile', [a:lines, a:url] + a:000)
  2150. endfunction
  2151. let s:globsubs = {
  2152. \ '/**/': '/\%([^./][^/]*/\)*',
  2153. \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
  2154. \ '**/': '[^/]*\%(/[^./][^/]*\)*',
  2155. \ '**': '.*',
  2156. \ '/*': '/[^/.][^/]*',
  2157. \ '*': '[^/]*',
  2158. \ '?': '[^/]'}
  2159. function! fugitive#glob(url, ...) abort
  2160. let [repo, commit, glob] = s:DirCommitFile(a:url)
  2161. let dirglob = s:GitDir(repo)
  2162. let append = matchstr(glob, '/*$')
  2163. let glob = substitute(glob, '/*$', '', '')
  2164. let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
  2165. let results = []
  2166. for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
  2167. if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
  2168. continue
  2169. endif
  2170. let files = items(s:TreeInfo(dir, commit)[0])
  2171. if len(append)
  2172. call filter(files, 'v:val[1][2] ==# "tree"')
  2173. endif
  2174. call map(files, 'v:val[0]')
  2175. call filter(files, 'v:val =~# pattern')
  2176. let prepend = s:DirUrlPrefix(dir) . substitute(commit, '^:', '', '') . '/'
  2177. call sort(files)
  2178. call map(files, 's:VimSlash(s:PathJoin(prepend, v:val . append))')
  2179. call extend(results, files)
  2180. endfor
  2181. if a:0 > 1 && a:2
  2182. return results
  2183. else
  2184. return join(results, "\n")
  2185. endif
  2186. endfunction
  2187. function! fugitive#delete(url, ...) abort
  2188. let [dir, commit, file] = s:DirCommitFile(a:url)
  2189. if a:0 && len(a:1) || commit !~# '^\d$'
  2190. return -1
  2191. endif
  2192. let entry = s:PathInfo(a:url)
  2193. if entry[2] !=# 'blob'
  2194. return -1
  2195. endif
  2196. let error = s:UpdateIndex(dir, ['000000', '0000000000000000000000000000000000000000', commit, file[1:-1]])
  2197. return len(error) ? -1 : 0
  2198. endfunction
  2199. " Section: Completion
  2200. function! s:FilterEscape(items, ...) abort
  2201. let items = copy(a:items)
  2202. call map(items, 'fnameescape(v:val)')
  2203. if !a:0 || type(a:1) != type('')
  2204. let match = ''
  2205. else
  2206. let match = substitute(a:1, '^[+>]\|\\\@<![' . substitute(s:fnameescape, '\\', '', '') . ']', '\\&', 'g')
  2207. endif
  2208. let cmp = s:FileIgnoreCase(1) ? '==?' : '==#'
  2209. return filter(items, 'strpart(v:val, 0, strlen(match)) ' . cmp . ' match')
  2210. endfunction
  2211. function! s:GlobComplete(lead, pattern, ...) abort
  2212. if a:lead ==# '/'
  2213. return []
  2214. else
  2215. let results = glob(substitute(a:lead . a:pattern, '[\{}]', '\\&', 'g'), a:0 ? a:1 : 0, 1)
  2216. endif
  2217. call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
  2218. call map(results, 'v:val[ strlen(a:lead) : -1 ]')
  2219. return results
  2220. endfunction
  2221. function! fugitive#CompletePath(base, ...) abort
  2222. let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
  2223. let stripped = matchstr(a:base, '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\)')
  2224. let base = strpart(a:base, len(stripped))
  2225. if len(stripped) || a:0 < 4
  2226. let root = s:Tree(dir)
  2227. else
  2228. let root = a:4
  2229. endif
  2230. if root !=# '/' && len(root)
  2231. let root .= '/'
  2232. endif
  2233. if empty(stripped)
  2234. let stripped = matchstr(a:base, '^\%(:(literal)\|:\)')
  2235. let base = strpart(a:base, len(stripped))
  2236. endif
  2237. if base =~# '^\.git/' && len(dir)
  2238. let pattern = s:gsub(base[5:-1], '/', '*&').'*'
  2239. let fdir = fugitive#Find('.git/', dir)
  2240. let matches = s:GlobComplete(fdir, pattern)
  2241. let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
  2242. if len(cdir) && s:cpath(fdir) !=# s:cpath(cdir)
  2243. call extend(matches, s:GlobComplete(cdir, pattern))
  2244. endif
  2245. call s:Uniq(matches)
  2246. call map(matches, "'.git/' . v:val")
  2247. elseif base =~# '^\~/'
  2248. let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
  2249. elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/'
  2250. let matches = s:GlobComplete('', base . '*')
  2251. elseif len(root)
  2252. let matches = s:GlobComplete(root, s:gsub(base, '/', '*&').'*')
  2253. else
  2254. let matches = []
  2255. endif
  2256. call map(matches, 's:fnameescape(s:Slash(stripped . v:val))')
  2257. return matches
  2258. endfunction
  2259. function! fugitive#PathComplete(...) abort
  2260. return call('fugitive#CompletePath', a:000)
  2261. endfunction
  2262. function! s:CompleteHeads(dir) abort
  2263. if empty(a:dir)
  2264. return []
  2265. endif
  2266. let dir = fugitive#Find('.git/', a:dir)
  2267. return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
  2268. \ sort(s:LinesError([a:dir, 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes'])[0])
  2269. endfunction
  2270. function! fugitive#CompleteObject(base, ...) abort
  2271. let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
  2272. let tree = s:Tree(dir)
  2273. let cwd = getcwd()
  2274. let subdir = ''
  2275. if len(tree) && s:cpath(tree . '/', cwd[0 : len(tree)])
  2276. let subdir = strpart(cwd, len(tree) + 1) . '/'
  2277. endif
  2278. let base = s:Expand(a:base)
  2279. if a:base =~# '^!\d*$' && base !~# '^!'
  2280. return [base]
  2281. elseif base =~# '^\.\=/\|^:(' || base !~# ':'
  2282. let results = []
  2283. if base =~# '^refs/'
  2284. let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
  2285. let results += map(s:GlobComplete(cdir, base . '*'), 's:Slash(v:val)')
  2286. call map(results, 's:fnameescape(v:val)')
  2287. elseif base !~# '^\.\=/\|^:('
  2288. let heads = s:CompleteHeads(dir)
  2289. if filereadable(fugitive#Find('.git/refs/stash', dir))
  2290. let heads += ["stash"]
  2291. let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
  2292. endif
  2293. let results += s:FilterEscape(heads, fnameescape(base))
  2294. endif
  2295. let results += a:0 == 1 || a:0 >= 3 ? fugitive#CompletePath(base, 0, '', dir, a:0 >= 4 ? a:4 : tree) : fugitive#CompletePath(base)
  2296. return results
  2297. elseif base =~# '^:'
  2298. let entries = s:LinesError(['ls-files','--stage'], dir)[0]
  2299. if base =~# ':\./'
  2300. call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
  2301. endif
  2302. call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
  2303. if base !~# '^:[0-3]\%(:\|$\)'
  2304. call filter(entries,'v:val[1] == "0"')
  2305. call map(entries,'v:val[2:-1]')
  2306. endif
  2307. else
  2308. let parent = matchstr(base, '.*[:/]')
  2309. let entries = s:LinesError(['ls-tree', substitute(parent, ':\zs\./', '\=subdir', '')], dir)[0]
  2310. call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
  2311. call map(entries,'parent.s:sub(v:val,".*\t","")')
  2312. endif
  2313. return s:FilterEscape(entries, fnameescape(base))
  2314. endfunction
  2315. function! s:CompleteSub(subcommand, A, L, P, ...) abort
  2316. let pre = strpart(a:L, 0, a:P)
  2317. if pre =~# ' -- '
  2318. return fugitive#CompletePath(a:A)
  2319. elseif a:A =~# '^-' || a:A is# 0
  2320. return s:FilterEscape(split(s:ChompDefault('', [a:subcommand, '--git-completion-helper']), ' '), a:A)
  2321. elseif !a:0
  2322. return fugitive#CompleteObject(a:A, s:Dir())
  2323. elseif type(a:1) == type(function('tr'))
  2324. return call(a:1, [a:A, a:L, a:P] + (a:0 > 1 ? a:2 : []))
  2325. else
  2326. return s:FilterEscape(a:1, a:A)
  2327. endif
  2328. endfunction
  2329. function! s:CompleteRevision(A, L, P, ...) abort
  2330. return s:FilterEscape(s:CompleteHeads(a:0 ? a:1 : s:Dir()), a:A)
  2331. endfunction
  2332. function! s:CompleteRemote(A, L, P, ...) abort
  2333. let dir = a:0 ? a:1 : s:Dir()
  2334. let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
  2335. if !empty(remote)
  2336. let matches = s:LinesError([dir, 'ls-remote', remote])[0]
  2337. call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
  2338. call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
  2339. else
  2340. let matches = s:LinesError([dir, 'remote'])[0]
  2341. endif
  2342. return s:FilterEscape(matches, a:A)
  2343. endfunction
  2344. " Section: Buffer auto-commands
  2345. augroup fugitive_dummy_events
  2346. autocmd!
  2347. autocmd User Fugitive* "
  2348. autocmd BufWritePre,FileWritePre,FileWritePost * "
  2349. autocmd BufNewFile * "
  2350. autocmd QuickfixCmdPre,QuickfixCmdPost * "
  2351. augroup END
  2352. function! s:ReplaceCmd(cmd) abort
  2353. let temp = tempname()
  2354. let [err, exec_error] = s:StdoutToFile(temp, a:cmd)
  2355. if exec_error
  2356. throw 'fugitive: ' . (len(err) ? substitute(err, "\n$", '', '') : 'unknown error running ' . string(a:cmd))
  2357. endif
  2358. setlocal noswapfile
  2359. silent exe 'lockmarks keepalt noautocmd 0read ++edit' s:fnameescape(temp)
  2360. if &foldenable && foldlevel('$') > 0
  2361. set nofoldenable
  2362. silent keepjumps $delete _
  2363. set foldenable
  2364. else
  2365. silent keepjumps $delete _
  2366. endif
  2367. call delete(temp)
  2368. if s:cpath(s:AbsoluteVimPath(bufnr('$')), temp)
  2369. silent! noautocmd execute bufnr('$') . 'bwipeout'
  2370. endif
  2371. endfunction
  2372. function! s:QueryLog(refspec, limit) abort
  2373. let lines = s:LinesError(['log', '-n', '' . a:limit, '--pretty=format:%h%x09%s'] + a:refspec + ['--'])[0]
  2374. call map(lines, 'split(v:val, "\t", 1)')
  2375. call map(lines, '{"type": "Log", "commit": v:val[0], "subject": join(v:val[1 : -1], "\t")}')
  2376. return lines
  2377. endfunction
  2378. function! s:FormatLog(dict) abort
  2379. return a:dict.commit . ' ' . a:dict.subject
  2380. endfunction
  2381. function! s:FormatRebase(dict) abort
  2382. return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
  2383. endfunction
  2384. function! s:FormatFile(dict) abort
  2385. return a:dict.status . ' ' . a:dict.filename
  2386. endfunction
  2387. function! s:Format(val) abort
  2388. if type(a:val) == type({})
  2389. return s:Format{a:val.type}(a:val)
  2390. elseif type(a:val) == type([])
  2391. return map(copy(a:val), 's:Format(v:val)')
  2392. else
  2393. return '' . a:val
  2394. endif
  2395. endfunction
  2396. function! s:AddHeader(key, value) abort
  2397. if empty(a:value)
  2398. return
  2399. endif
  2400. let before = 1
  2401. while !empty(getline(before))
  2402. let before += 1
  2403. endwhile
  2404. call append(before - 1, [a:key . ':' . (len(a:value) ? ' ' . a:value : '')])
  2405. if before == 1 && line('$') == 2
  2406. silent keepjumps 2delete _
  2407. endif
  2408. endfunction
  2409. function! s:AddSection(label, lines, ...) abort
  2410. let note = a:0 ? a:1 : ''
  2411. if empty(a:lines) && empty(note)
  2412. return
  2413. endif
  2414. call append(line('$'), ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
  2415. endfunction
  2416. function! s:AddLogSection(label, refspec) abort
  2417. let limit = 256
  2418. let log = s:QueryLog(a:refspec, limit)
  2419. if empty(log)
  2420. return
  2421. elseif len(log) == limit
  2422. call remove(log, -1)
  2423. let label = a:label . ' (' . (limit - 1). '+)'
  2424. else
  2425. let label = a:label . ' (' . len(log) . ')'
  2426. endif
  2427. call append(line('$'), ['', label] + s:Format(log))
  2428. endfunction
  2429. let s:rebase_abbrevs = {
  2430. \ 'p': 'pick',
  2431. \ 'r': 'reword',
  2432. \ 'e': 'edit',
  2433. \ 's': 'squash',
  2434. \ 'f': 'fixup',
  2435. \ 'x': 'exec',
  2436. \ 'd': 'drop',
  2437. \ 'l': 'label',
  2438. \ 't': 'reset',
  2439. \ 'm': 'merge',
  2440. \ 'b': 'break',
  2441. \ }
  2442. function! fugitive#BufReadStatus(...) abort
  2443. let amatch = s:Slash(expand('%:p'))
  2444. unlet! b:fugitive_reltime b:fugitive_type
  2445. try
  2446. doautocmd BufReadPre
  2447. let config = fugitive#Config()
  2448. let cmd = [s:Dir()]
  2449. setlocal noreadonly modifiable nomodeline buftype=nowrite
  2450. if amatch !~# '^fugitive:' && s:cpath($GIT_INDEX_FILE !=# '' ? resolve(s:GitIndexFileEnv()) : fugitive#Find('.git/index')) !=# s:cpath(amatch)
  2451. let cmd += [{'env': {'GIT_INDEX_FILE': FugitiveGitPath(amatch)}}]
  2452. endif
  2453. if fugitive#GitVersion(2, 15)
  2454. call add(cmd, '--no-optional-locks')
  2455. endif
  2456. let b:fugitive_files = {'Staged': {}, 'Unstaged': {}}
  2457. let [staged, unstaged, untracked] = [[], [], []]
  2458. let props = {}
  2459. let pull = ''
  2460. if empty(s:Tree())
  2461. let branch = FugitiveHead(0)
  2462. let head = FugitiveHead(11)
  2463. elseif fugitive#GitVersion(2, 11)
  2464. let cmd += ['status', '--porcelain=v2', '-bz']
  2465. let [output, message, exec_error] = s:NullError(cmd)
  2466. if exec_error
  2467. throw 'fugitive: ' . message
  2468. endif
  2469. let i = 0
  2470. while i < len(output)
  2471. let line = output[i]
  2472. let prop = matchlist(line, '# \(\S\+\) \(.*\)')
  2473. if len(prop)
  2474. let props[prop[1]] = prop[2]
  2475. elseif line[0] ==# '?'
  2476. call add(untracked, {'type': 'File', 'status': line[0], 'filename': line[2:-1], 'relative': [line[2:-1]]})
  2477. elseif line[0] !=# '#'
  2478. if line[0] ==# 'u'
  2479. let file = matchstr(line, '^.\{37\} \x\{40,\} \x\{40,\} \x\{40,\} \zs.*$')
  2480. else
  2481. let file = matchstr(line, '^.\{30\} \x\{40,\} \x\{40,\} \zs.*$')
  2482. endif
  2483. if line[0] ==# '2'
  2484. let i += 1
  2485. let file = matchstr(file, ' \zs.*')
  2486. let relative = [file, output[i]]
  2487. else
  2488. let relative = [file]
  2489. endif
  2490. let filename = join(reverse(copy(relative)), ' -> ')
  2491. let sub = matchstr(line, '^[12u] .. \zs....')
  2492. if line[2] !=# '.'
  2493. call add(staged, {'type': 'File', 'status': line[2], 'filename': filename, 'relative': relative, 'submodule': sub})
  2494. endif
  2495. if line[3] !=# '.'
  2496. let sub = matchstr(line, '^[12u] .. \zs....')
  2497. call add(unstaged, {'type': 'File', 'status': get({'C':'M','M':'?','U':'?'}, matchstr(sub, 'S\.*\zs[CMU]'), line[3]), 'filename': file, 'relative': [file], 'submodule': sub})
  2498. endif
  2499. endif
  2500. let i += 1
  2501. endwhile
  2502. let branch = substitute(get(props, 'branch.head', '(unknown)'), '\C^(\%(detached\|unknown\))$', '', '')
  2503. if len(branch)
  2504. let head = branch
  2505. elseif has_key(props, 'branch.oid')
  2506. let head = props['branch.oid'][0:10]
  2507. else
  2508. let head = FugitiveHead(11)
  2509. endif
  2510. let pull = get(props, 'branch.upstream', '')
  2511. else " git < 2.11
  2512. let cmd += ['status', '--porcelain', '-bz']
  2513. let [output, message, exec_error] = s:NullError(cmd)
  2514. if exec_error
  2515. throw 'fugitive: ' . message
  2516. endif
  2517. while get(output, 0, '') =~# '^\l\+:'
  2518. call remove(output, 0)
  2519. endwhile
  2520. let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
  2521. if head =~# '\.\.\.'
  2522. let [head, pull] = split(head, '\.\.\.')
  2523. let branch = head
  2524. elseif head ==# 'HEAD' || empty(head)
  2525. let head = FugitiveHead(11)
  2526. let branch = ''
  2527. else
  2528. let branch = head
  2529. endif
  2530. let i = 0
  2531. while i < len(output)
  2532. let line = output[i]
  2533. let file = line[3:-1]
  2534. let i += 1
  2535. if line[2] !=# ' '
  2536. continue
  2537. endif
  2538. if line[0:1] =~# '[RC]'
  2539. let relative = [file, output[i]]
  2540. let i += 1
  2541. else
  2542. let relative = [file]
  2543. endif
  2544. let filename = join(reverse(copy(relative)), ' -> ')
  2545. if line[0] !~# '[ ?!#]'
  2546. call add(staged, {'type': 'File', 'status': line[0], 'filename': filename, 'relative': relative, 'submodule': ''})
  2547. endif
  2548. if line[0:1] ==# '??'
  2549. call add(untracked, {'type': 'File', 'status': line[1], 'filename': filename, 'relative': relative})
  2550. elseif line[1] !~# '[ !#]'
  2551. call add(unstaged, {'type': 'File', 'status': line[1], 'filename': file, 'relative': [file], 'submodule': ''})
  2552. endif
  2553. endwhile
  2554. endif
  2555. let diff = {'Staged': {'stdout': ['']}, 'Unstaged': {'stdout': ['']}}
  2556. if len(staged)
  2557. let diff['Staged'] =
  2558. \ fugitive#Execute(['-c', 'diff.suppressBlankEmpty=false', '-c', 'core.quotePath=false', 'diff', '--color=never', '--no-ext-diff', '--no-prefix', '--cached'], function('len'))
  2559. endif
  2560. if len(unstaged)
  2561. let diff['Unstaged'] =
  2562. \ fugitive#Execute(['-c', 'diff.suppressBlankEmpty=false', '-c', 'core.quotePath=false', 'diff', '--color=never', '--no-ext-diff', '--no-prefix'], function('len'))
  2563. endif
  2564. for dict in staged
  2565. let b:fugitive_files['Staged'][dict.filename] = dict
  2566. endfor
  2567. for dict in unstaged
  2568. let b:fugitive_files['Unstaged'][dict.filename] = dict
  2569. endfor
  2570. let pull_type = 'Pull'
  2571. if len(pull)
  2572. let rebase = FugitiveConfigGet('branch.' . branch . '.rebase', config)
  2573. if empty(rebase)
  2574. let rebase = FugitiveConfigGet('pull.rebase', config)
  2575. endif
  2576. if rebase =~# '^\%(true\|yes\|on\|1\|interactive\|merges\|preserve\)$'
  2577. let pull_type = 'Rebase'
  2578. elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
  2579. let pull_type = 'Merge'
  2580. endif
  2581. endif
  2582. let push_remote = FugitiveConfigGet('branch.' . branch . '.pushRemote', config)
  2583. if empty(push_remote)
  2584. let push_remote = FugitiveConfigGet('remote.pushDefault', config)
  2585. endif
  2586. let fetch_remote = FugitiveConfigGet('branch.' . branch . '.remote', config)
  2587. if empty(fetch_remote)
  2588. let fetch_remote = 'origin'
  2589. endif
  2590. if empty(push_remote)
  2591. let push_remote = fetch_remote
  2592. endif
  2593. let push_default = FugitiveConfigGet('push.default', config)
  2594. if empty(push_default)
  2595. let push_default = fugitive#GitVersion(2) ? 'simple' : 'matching'
  2596. endif
  2597. if push_default ==# 'upstream'
  2598. let push = pull
  2599. else
  2600. let push = len(branch) ? (push_remote ==# '.' ? '' : push_remote . '/') . branch : ''
  2601. endif
  2602. if isdirectory(fugitive#Find('.git/rebase-merge/'))
  2603. let rebasing_dir = fugitive#Find('.git/rebase-merge/')
  2604. elseif isdirectory(fugitive#Find('.git/rebase-apply/'))
  2605. let rebasing_dir = fugitive#Find('.git/rebase-apply/')
  2606. endif
  2607. let rebasing = []
  2608. let rebasing_head = 'detached HEAD'
  2609. if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
  2610. let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
  2611. let len = 11
  2612. let lines = readfile(rebasing_dir . 'git-rebase-todo')
  2613. for line in lines
  2614. let hash = matchstr(line, '^[^a-z].*\s\zs[0-9a-f]\{4,\}\ze\.\.')
  2615. if len(hash)
  2616. let len = len(hash)
  2617. break
  2618. endif
  2619. endfor
  2620. if getfsize(rebasing_dir . 'done') > 0
  2621. let done = readfile(rebasing_dir . 'done')
  2622. call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
  2623. let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
  2624. let lines = done + lines
  2625. endif
  2626. call reverse(lines)
  2627. for line in lines
  2628. let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
  2629. if len(match) && match[1] !~# 'exec\|merge\|label'
  2630. call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
  2631. endif
  2632. endfor
  2633. endif
  2634. let b:fugitive_diff = diff
  2635. if get(a:, 1, v:cmdbang)
  2636. unlet! b:fugitive_expanded
  2637. endif
  2638. let expanded = get(b:, 'fugitive_expanded', {'Staged': {}, 'Unstaged': {}})
  2639. let b:fugitive_expanded = {'Staged': {}, 'Unstaged': {}}
  2640. silent keepjumps %delete_
  2641. call s:AddHeader('Head', head)
  2642. call s:AddHeader(pull_type, pull)
  2643. if push !=# pull
  2644. call s:AddHeader('Push', push)
  2645. endif
  2646. if empty(s:Tree())
  2647. if get(fugitive#ConfigGetAll('core.bare', config), 0, '') !~# '^\%(false\|no|off\|0\|\)$'
  2648. call s:AddHeader('Bare', 'yes')
  2649. else
  2650. call s:AddHeader('Error', s:worktree_error)
  2651. endif
  2652. endif
  2653. if get(fugitive#ConfigGetAll('advice.statusHints', config), 0, 'true') !~# '^\%(false\|no|off\|0\|\)$'
  2654. call s:AddHeader('Help', 'g?')
  2655. endif
  2656. call s:AddSection('Rebasing ' . rebasing_head, rebasing)
  2657. call s:AddSection('Untracked', untracked)
  2658. call s:AddSection('Unstaged', unstaged)
  2659. let unstaged_end = len(unstaged) ? line('$') : 0
  2660. call s:AddSection('Staged', staged)
  2661. let staged_end = len(staged) ? line('$') : 0
  2662. if len(push) && !(push ==# pull && get(props, 'branch.ab') =~# '^+0 ')
  2663. call s:AddLogSection('Unpushed to ' . push, [push . '..' . head])
  2664. endif
  2665. if len(pull) && push !=# pull
  2666. call s:AddLogSection('Unpushed to ' . pull, [pull . '..' . head])
  2667. endif
  2668. if empty(pull) && empty(push) && empty(rebasing)
  2669. call s:AddLogSection('Unpushed to *', [head, '--not', '--remotes'])
  2670. endif
  2671. if len(push) && push !=# pull
  2672. call s:AddLogSection('Unpulled from ' . push, [head . '..' . push])
  2673. endif
  2674. if len(pull) && get(props, 'branch.ab') !~# ' -0$'
  2675. call s:AddLogSection('Unpulled from ' . pull, [head . '..' . pull])
  2676. endif
  2677. setlocal nomodified readonly noswapfile
  2678. doautocmd BufReadPost
  2679. setlocal nomodifiable
  2680. if &bufhidden ==# ''
  2681. setlocal bufhidden=delete
  2682. endif
  2683. if !exists('b:dispatch')
  2684. let b:dispatch = ':Git fetch --all'
  2685. endif
  2686. call fugitive#MapJumps()
  2687. call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
  2688. call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
  2689. call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
  2690. call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
  2691. call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
  2692. call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
  2693. call s:Map('n', 'U', ":<C-U>Git reset -q<CR>", '<silent>')
  2694. call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
  2695. call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
  2696. call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
  2697. call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
  2698. call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
  2699. call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
  2700. call s:Map('n', 'C', ":echoerr 'fugitive: C has been removed in favor of cc'<CR>", '<silent><unique>')
  2701. call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
  2702. call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
  2703. call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
  2704. call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide', line('.'),v:count)<CR>", '<silent>')
  2705. call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show', line('.'),v:count)<CR>", '<silent>')
  2706. call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
  2707. call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
  2708. call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
  2709. call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
  2710. call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
  2711. call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
  2712. call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
  2713. call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
  2714. call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
  2715. call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
  2716. call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
  2717. call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
  2718. call s:Map('n', 'p', ":<C-U>if v:count<Bar>silent exe <SID>GF('pedit')<Bar>else<Bar>echoerr 'Use = for inline diff, P for :Git add/reset --patch, 1p for :pedit'<Bar>endif<CR>", '<silent>')
  2719. call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
  2720. call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'))<CR>", '<silent>')
  2721. call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
  2722. call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
  2723. call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic. Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
  2724. call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
  2725. call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
  2726. call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
  2727. call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
  2728. call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
  2729. call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
  2730. call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
  2731. call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
  2732. setlocal filetype=fugitive
  2733. for [lnum, section] in [[staged_end, 'Staged'], [unstaged_end, 'Unstaged']]
  2734. while len(getline(lnum))
  2735. let filename = matchstr(getline(lnum), '^[A-Z?] \zs.*')
  2736. if has_key(expanded[section], filename)
  2737. call s:StageInline('show', lnum)
  2738. endif
  2739. let lnum -= 1
  2740. endwhile
  2741. endfor
  2742. let b:fugitive_reltime = reltime()
  2743. return s:DoAutocmd('User FugitiveIndex')
  2744. catch /^fugitive:/
  2745. return 'echoerr ' . string(v:exception)
  2746. finally
  2747. let b:fugitive_type = 'index'
  2748. endtry
  2749. endfunction
  2750. function! fugitive#FileReadCmd(...) abort
  2751. let amatch = a:0 ? a:1 : expand('<amatch>')
  2752. let [dir, rev] = s:DirRev(amatch)
  2753. let line = a:0 > 1 ? a:2 : line("'[")
  2754. if empty(dir)
  2755. return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
  2756. endif
  2757. if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
  2758. let cmd = [dir, 'log', '--pretty=format:%B', '-1', rev, '--']
  2759. elseif rev ==# ':'
  2760. let cmd = [dir, 'status', '--short']
  2761. else
  2762. let cmd = [dir, 'cat-file', '-p', rev, '--']
  2763. endif
  2764. let temp = tempname()
  2765. let [err, exec_error] = s:StdoutToFile(temp, cmd)
  2766. if exec_error
  2767. call delete(temp)
  2768. return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
  2769. else
  2770. return 'silent keepalt ' . line . 'read ' . s:fnameescape(temp) . '|call delete(' . string(temp) . ')'
  2771. endif
  2772. endfunction
  2773. function! fugitive#FileWriteCmd(...) abort
  2774. let temp = tempname()
  2775. let amatch = a:0 ? a:1 : expand('<amatch>')
  2776. let autype = a:0 > 1 ? 'Buf' : 'File'
  2777. if exists('#' . autype . 'WritePre')
  2778. execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
  2779. endif
  2780. try
  2781. let [dir, commit, file] = s:DirCommitFile(amatch)
  2782. if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
  2783. return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
  2784. endif
  2785. silent execute "noautocmd keepalt '[,']write ".temp
  2786. let hash = s:TreeChomp([dir, '--literal-pathspecs', 'hash-object', '-w', '--', FugitiveGitPath(temp)])
  2787. let old_mode = matchstr(s:ChompDefault('', ['ls-files', '--stage', '.' . file], dir), '^\d\+')
  2788. if empty(old_mode)
  2789. let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
  2790. endif
  2791. let error = s:UpdateIndex(dir, [old_mode, hash, commit, file[1:-1]])
  2792. if empty(error)
  2793. setlocal nomodified
  2794. if exists('#' . autype . 'WritePost')
  2795. execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
  2796. endif
  2797. exe s:DoAutocmdChanged(dir)
  2798. return ''
  2799. else
  2800. return 'echoerr '.string('fugitive: '.error)
  2801. endif
  2802. catch /^fugitive:/
  2803. return 'echoerr ' . string(v:exception)
  2804. finally
  2805. call delete(temp)
  2806. endtry
  2807. endfunction
  2808. function! fugitive#BufReadCmd(...) abort
  2809. let amatch = a:0 ? a:1 : expand('<amatch>')
  2810. try
  2811. let [dir, rev] = s:DirRev(amatch)
  2812. if empty(dir)
  2813. return 'echo "Invalid Fugitive URL"'
  2814. endif
  2815. call s:InitializeBuffer(dir)
  2816. if rev ==# ':'
  2817. return fugitive#BufReadStatus(v:cmdbang)
  2818. elseif rev =~# '^:\d$'
  2819. let b:fugitive_type = 'stage'
  2820. else
  2821. let r = fugitive#Execute([dir, 'cat-file', '-t', rev])
  2822. let b:fugitive_type = get(r.stdout, 0, '')
  2823. if r.exit_status && rev =~# '^:0'
  2824. let r = fugitive#Execute([dir, 'write-tree', '--prefix=' . rev[3:-1]])
  2825. let sha = get(r.stdout, 0, '')
  2826. let b:fugitive_type = 'tree'
  2827. endif
  2828. if r.exit_status
  2829. let error = substitute(join(r.stderr, "\n"), "\n*$", '', '')
  2830. unlet b:fugitive_type
  2831. setlocal noswapfile
  2832. if empty(&bufhidden)
  2833. setlocal bufhidden=delete
  2834. endif
  2835. if rev =~# '^:\d:'
  2836. let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
  2837. return 'doautocmd BufNewFile'
  2838. else
  2839. setlocal readonly nomodifiable
  2840. return 'doautocmd BufNewFile|echo ' . string(error)
  2841. endif
  2842. elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
  2843. return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
  2844. endif
  2845. if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
  2846. let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
  2847. endif
  2848. endif
  2849. if b:fugitive_type !=# 'blob'
  2850. setlocal nomodeline
  2851. endif
  2852. setlocal noreadonly modifiable
  2853. let pos = getpos('.')
  2854. silent keepjumps %delete_
  2855. setlocal endofline
  2856. let events = ['User FugitiveObject', 'User Fugitive' . substitute(b:fugitive_type, '^\l', '\u&', '')]
  2857. try
  2858. if b:fugitive_type !=# 'blob'
  2859. setlocal foldmarker=<<<<<<<<,>>>>>>>>
  2860. endif
  2861. exe s:DoAutocmd('BufReadPre')
  2862. if b:fugitive_type ==# 'tree'
  2863. let b:fugitive_display_format = b:fugitive_display_format % 2
  2864. if b:fugitive_display_format
  2865. call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
  2866. else
  2867. if !exists('sha')
  2868. let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
  2869. endif
  2870. call s:ReplaceCmd([dir, 'show', '--no-color', sha])
  2871. endif
  2872. elseif b:fugitive_type ==# 'tag'
  2873. let b:fugitive_display_format = b:fugitive_display_format % 2
  2874. if b:fugitive_display_format
  2875. call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
  2876. else
  2877. call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
  2878. endif
  2879. elseif b:fugitive_type ==# 'commit'
  2880. let b:fugitive_display_format = b:fugitive_display_format % 2
  2881. if b:fugitive_display_format
  2882. call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
  2883. else
  2884. call s:ReplaceCmd([dir, '-c', 'diff.noprefix=false', '-c', 'log.showRoot=false', 'show', '--no-color', '-m', '--first-parent', '--pretty=format:tree%x20%T%nparent%x20%P%nauthor%x20%an%x20<%ae>%x20%ad%ncommitter%x20%cn%x20<%ce>%x20%cd%nencoding%x20%e%n%n%s%n%n%b', rev])
  2885. keepjumps 1
  2886. keepjumps call search('^parent ')
  2887. if getline('.') ==# 'parent '
  2888. silent lockmarks keepjumps delete_
  2889. else
  2890. silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
  2891. endif
  2892. keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
  2893. if lnum
  2894. silent lockmarks keepjumps delete_
  2895. end
  2896. silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
  2897. keepjumps 1
  2898. endif
  2899. elseif b:fugitive_type ==# 'stage'
  2900. call s:ReplaceCmd([dir, 'ls-files', '--stage'])
  2901. elseif b:fugitive_type ==# 'blob'
  2902. let blob_or_filters = rev =~# ':' && fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
  2903. call s:ReplaceCmd([dir, 'cat-file', blob_or_filters, rev])
  2904. endif
  2905. finally
  2906. keepjumps call setpos('.',pos)
  2907. setlocal nomodified noswapfile
  2908. let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
  2909. if modifiable
  2910. let events = ['User FugitiveStageBlob']
  2911. endif
  2912. let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
  2913. if empty(&bufhidden)
  2914. setlocal bufhidden=delete
  2915. endif
  2916. let &l:modifiable = modifiable
  2917. if b:fugitive_type !=# 'blob'
  2918. setlocal filetype=git
  2919. call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
  2920. call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
  2921. endif
  2922. call fugitive#MapJumps()
  2923. endtry
  2924. setlocal modifiable
  2925. return s:DoAutocmd('BufReadPost') .
  2926. \ (modifiable ? '' : '|setl nomodifiable') . '|' .
  2927. \ call('s:DoAutocmd', events)
  2928. catch /^fugitive:/
  2929. return 'echoerr ' . string(v:exception)
  2930. endtry
  2931. endfunction
  2932. function! fugitive#BufWriteCmd(...) abort
  2933. return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
  2934. endfunction
  2935. function! fugitive#SourceCmd(...) abort
  2936. let amatch = a:0 ? a:1 : expand('<amatch>')
  2937. let temp = s:BlobTemp(amatch)
  2938. if empty(temp)
  2939. return 'noautocmd source ' . s:fnameescape(amatch)
  2940. endif
  2941. if !exists('g:virtual_scriptnames')
  2942. let g:virtual_scriptnames = {}
  2943. endif
  2944. let g:virtual_scriptnames[temp] = amatch
  2945. return 'source ' . s:fnameescape(temp)
  2946. endfunction
  2947. " Section: Temp files
  2948. if !exists('s:temp_files')
  2949. let s:temp_files = {}
  2950. endif
  2951. function! s:TempState(...) abort
  2952. return get(s:temp_files, s:cpath(s:AbsoluteVimPath(a:0 ? a:1 : -1)), {})
  2953. endfunction
  2954. function! fugitive#Result(...) abort
  2955. if !a:0 && exists('g:fugitive_event')
  2956. return get(g:, 'fugitive_result', {})
  2957. elseif !a:0 || type(a:1) == type('') && a:1 =~# '^-\=$'
  2958. return get(g:, '_fugitive_last_job', {})
  2959. elseif type(a:1) == type(0)
  2960. return s:TempState(a:1)
  2961. elseif type(a:1) == type('')
  2962. return s:TempState(a:1)
  2963. elseif type(a:1) == type({}) && has_key(a:1, 'file')
  2964. return s:TempState(a:1.file)
  2965. else
  2966. return {}
  2967. endif
  2968. endfunction
  2969. function! s:TempDotMap() abort
  2970. let cfile = s:cfile()
  2971. if empty(cfile)
  2972. if getline('.') =~# '^[*+] \+\f' && col('.') < 2
  2973. return matchstr(getline('.'), '^. \+\zs\f\+')
  2974. else
  2975. return expand('<cfile>')
  2976. endif
  2977. endif
  2978. let name = fugitive#Find(cfile[0])
  2979. let [dir, commit, file] = s:DirCommitFile(name)
  2980. if len(commit) && empty(file)
  2981. return commit
  2982. elseif s:cpath(s:Tree(), getcwd())
  2983. return fugitive#Path(name, "./")
  2984. else
  2985. return fugitive#Real(name)
  2986. endif
  2987. endfunction
  2988. function! s:TempReadPre(file) abort
  2989. let key = s:cpath(s:AbsoluteVimPath(a:file))
  2990. if has_key(s:temp_files, key)
  2991. let dict = s:temp_files[key]
  2992. setlocal nomodeline
  2993. if empty(&bufhidden)
  2994. setlocal bufhidden=delete
  2995. endif
  2996. setlocal buftype=nowrite
  2997. setlocal nomodifiable
  2998. call s:InitializeBuffer(dict)
  2999. if len(dict.git_dir)
  3000. call extend(b:, {'fugitive_type': 'temp'}, 'keep')
  3001. endif
  3002. endif
  3003. return ''
  3004. endfunction
  3005. function! s:TempReadPost(file) abort
  3006. let key = s:cpath(s:AbsoluteVimPath(a:file))
  3007. if has_key(s:temp_files, key)
  3008. let dict = s:temp_files[key]
  3009. if !has_key(dict, 'job')
  3010. setlocal nobuflisted
  3011. endif
  3012. if get(dict, 'filetype', '') ==# 'git'
  3013. call fugitive#MapJumps()
  3014. call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
  3015. call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
  3016. endif
  3017. if has_key(dict, 'filetype')
  3018. if dict.filetype ==# 'man' && has('nvim')
  3019. let b:man_sect = matchstr(getline(1), '^\w\+(\zs\d\+\ze)')
  3020. endif
  3021. if !get(g:, 'did_load_ftplugin') && dict.filetype ==# 'fugitiveblame'
  3022. call s:BlameMaps(0)
  3023. endif
  3024. let &l:filetype = dict.filetype
  3025. endif
  3026. setlocal foldmarker=<<<<<<<<,>>>>>>>>
  3027. if !&modifiable
  3028. call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
  3029. endif
  3030. endif
  3031. return s:DoAutocmd('User FugitivePager')
  3032. endfunction
  3033. function! s:TempDelete(file) abort
  3034. let key = s:cpath(s:AbsoluteVimPath(a:file))
  3035. if has_key(s:temp_files, key) && !has_key(s:temp_files[key], 'job') && key !=# s:cpath(get(get(g:, '_fugitive_last_job', {}), 'file', ''))
  3036. call delete(a:file)
  3037. call remove(s:temp_files, key)
  3038. endif
  3039. return ''
  3040. endfunction
  3041. augroup fugitive_temp
  3042. autocmd!
  3043. autocmd BufReadPre * exe s:TempReadPre( +expand('<abuf>'))
  3044. autocmd BufReadPost * exe s:TempReadPost(+expand('<abuf>'))
  3045. autocmd BufWipeout * exe s:TempDelete( +expand('<abuf>'))
  3046. augroup END
  3047. " Section: :Git
  3048. function! s:AskPassArgs(dir) abort
  3049. if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) &&
  3050. \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#ConfigGetAll('core.askpass', a:dir))
  3051. if s:executable(s:VimExecPath() . '/git-gui--askpass')
  3052. return ['-c', 'core.askPass=' . s:ExecPath()[0] . '/git-gui--askpass']
  3053. elseif s:executable('ssh-askpass')
  3054. return ['-c', 'core.askPass=ssh-askpass']
  3055. endif
  3056. endif
  3057. return []
  3058. endfunction
  3059. function! s:RunSave(state) abort
  3060. let s:temp_files[s:cpath(a:state.file)] = a:state
  3061. endfunction
  3062. function! s:RunFinished(state, ...) abort
  3063. if has_key(get(g:, '_fugitive_last_job', {}), 'file') && bufnr(g:_fugitive_last_job.file) < 0
  3064. exe s:TempDelete(remove(g:, '_fugitive_last_job').file)
  3065. endif
  3066. let g:_fugitive_last_job = a:state
  3067. let first = join(readfile(a:state.file, '', 2), "\n")
  3068. if get(a:state, 'filetype', '') ==# 'git' && first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
  3069. let a:state.filetype = 'man'
  3070. endif
  3071. if !has_key(a:state, 'capture_bufnr')
  3072. return
  3073. endif
  3074. call fugitive#DidChange(a:state)
  3075. endfunction
  3076. function! s:RunEdit(state, tmp, job) abort
  3077. if get(a:state, 'request', '') !=# 'edit'
  3078. return 0
  3079. endif
  3080. call remove(a:state, 'request')
  3081. let sentinel = a:state.file . '.edit'
  3082. let file = FugitiveVimPath(readfile(sentinel, '', 1)[0])
  3083. try
  3084. if !&equalalways && a:state.mods !~# '\<tab\>' && 3 > (a:state.mods =~# '\<vert' ? winwidth(0) : winheight(0))
  3085. let noequalalways = 1
  3086. setglobal equalalways
  3087. endif
  3088. exe substitute(a:state.mods, '\<tab\>', '-tab', 'g') 'keepalt split' s:fnameescape(file)
  3089. finally
  3090. if exists('l:noequalalways')
  3091. setglobal noequalalways
  3092. endif
  3093. endtry
  3094. set bufhidden=wipe
  3095. call s:InitializeBuffer(a:state)
  3096. let bufnr = bufnr('')
  3097. let s:edit_jobs[bufnr] = [a:state, a:tmp, a:job, sentinel]
  3098. call fugitive#DidChange(a:state.git_dir)
  3099. if bufnr == bufnr('') && !exists('g:fugitive_event')
  3100. try
  3101. let g:fugitive_event = a:state.git_dir
  3102. let g:fugitive_result = a:state
  3103. exe s:DoAutocmd('User FugitiveEditor')
  3104. finally
  3105. unlet! g:fugitive_event g:fugitive_result
  3106. endtry
  3107. endif
  3108. return 1
  3109. endfunction
  3110. function! s:RunReceive(state, tmp, type, job, data, ...) abort
  3111. if a:type ==# 'err' || a:state.pty
  3112. let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
  3113. let data = a:tmp.escape . data
  3114. let escape = "\033]51;[^\007]*"
  3115. let a:tmp.escape = matchstr(data, escape . '$')
  3116. if len(a:tmp.escape)
  3117. let data = strpart(data, 0, len(data) - len(a:tmp.escape))
  3118. endif
  3119. let cmd = matchstr(data, escape . "\007")[5:-2]
  3120. let data = substitute(data, escape . "\007", '', 'g')
  3121. if cmd =~# '^fugitive:'
  3122. let a:state.request = strpart(cmd, 9)
  3123. endif
  3124. let lines = split(a:tmp.err . data, "\r\\=\n", 1)
  3125. let a:tmp.err = lines[-1]
  3126. let lines[-1] = ''
  3127. call map(lines, 'substitute(v:val, ".*\r", "", "")')
  3128. else
  3129. let lines = type(a:data) == type([]) ? a:data : split(a:data, "\n", 1)
  3130. if len(a:tmp.out)
  3131. let lines[0] = a:tmp.out . lines[0]
  3132. endif
  3133. let a:tmp.out = lines[-1]
  3134. let lines[-1] = ''
  3135. endif
  3136. call writefile(lines, a:state.file, 'ba')
  3137. if has_key(a:tmp, 'echo')
  3138. if !exists('l:data')
  3139. let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
  3140. endif
  3141. let a:tmp.echo .= data
  3142. endif
  3143. let line_count = a:tmp.line_count
  3144. let a:tmp.line_count += len(lines) - 1
  3145. if !has_key(a:state, 'capture_bufnr') || !bufloaded(a:state.capture_bufnr)
  3146. return
  3147. endif
  3148. call remove(lines, -1)
  3149. try
  3150. call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
  3151. if !line_count && len(lines) > 1000
  3152. let first = remove(lines, 0, 999)
  3153. call setbufline(a:state.capture_bufnr, 1, first)
  3154. redraw
  3155. call setbufline(a:state.capture_bufnr, 1001, lines)
  3156. else
  3157. call setbufline(a:state.capture_bufnr, line_count + 1, lines)
  3158. endif
  3159. call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
  3160. if !a:state.pager && getwinvar(bufwinid(a:state.capture_bufnr), '&previewwindow')
  3161. let winnr = bufwinnr(a:state.capture_bufnr)
  3162. if winnr > 0
  3163. let old_winnr = winnr()
  3164. exe 'noautocmd' winnr.'wincmd w'
  3165. $
  3166. exe 'noautocmd' old_winnr.'wincmd w'
  3167. endif
  3168. endif
  3169. catch
  3170. endtry
  3171. endfunction
  3172. function! s:RunExit(state, tmp, job, exit_status) abort
  3173. let a:state.exit_status = a:exit_status
  3174. if has_key(a:state, 'job')
  3175. return
  3176. endif
  3177. call s:RunFinished(a:state)
  3178. endfunction
  3179. function! s:RunClose(state, tmp, job, ...) abort
  3180. if a:0
  3181. call s:RunExit(a:state, a:tmp, a:job, a:1)
  3182. endif
  3183. let noeol = substitute(substitute(a:tmp.err, "\r$", '', ''), ".*\r", '', '') . a:tmp.out
  3184. call writefile([noeol], a:state.file, 'ba')
  3185. call remove(a:state, 'job')
  3186. if has_key(a:state, 'capture_bufnr') && bufloaded(a:state.capture_bufnr)
  3187. if len(noeol)
  3188. call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
  3189. call setbufline(a:state.capture_bufnr, a:tmp.line_count + 1, [noeol])
  3190. call setbufvar(a:state.capture_bufnr, '&eol', 0)
  3191. call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
  3192. endif
  3193. call setbufvar(a:state.capture_bufnr, '&modified', 0)
  3194. call setbufvar(a:state.capture_bufnr, '&buflisted', 0)
  3195. if a:state.filetype !=# getbufvar(a:state.capture_bufnr, '&filetype', '')
  3196. call setbufvar(a:state.capture_bufnr, '&filetype', a:state.filetype)
  3197. endif
  3198. endif
  3199. if !has_key(a:state, 'exit_status')
  3200. return
  3201. endif
  3202. call s:RunFinished(a:state)
  3203. endfunction
  3204. function! s:RunSend(job, str) abort
  3205. try
  3206. if type(a:job) == type(0)
  3207. call chansend(a:job, a:str)
  3208. else
  3209. call ch_sendraw(a:job, a:str)
  3210. endif
  3211. return len(a:str)
  3212. catch /^Vim\%((\a\+)\)\=:E90[06]:/
  3213. return 0
  3214. endtry
  3215. endfunction
  3216. function! s:RunCloseIn(job) abort
  3217. try
  3218. if type(a:job) ==# type(0)
  3219. call chanclose(a:job, 'stdin')
  3220. else
  3221. call ch_close_in(a:job)
  3222. endif
  3223. return 1
  3224. catch /^Vim\%((\a\+)\)\=:E90[06]:/
  3225. return 0
  3226. endtry
  3227. endfunction
  3228. function! s:RunEcho(tmp) abort
  3229. if !has_key(a:tmp, 'echo')
  3230. return
  3231. endif
  3232. let data = a:tmp.echo
  3233. let a:tmp.echo = matchstr(data, "[\r\n]\\+$")
  3234. if len(a:tmp.echo)
  3235. let data = strpart(data, 0, len(data) - len(a:tmp.echo))
  3236. endif
  3237. echon substitute(data, "\r\\ze\n", '', 'g')
  3238. endfunction
  3239. function! s:RunTick(job) abort
  3240. if type(a:job) == v:t_number
  3241. return jobwait([a:job], 1)[0] == -1
  3242. elseif type(a:job) == 8
  3243. let running = ch_status(a:job) !~# '^closed$\|^fail$' || job_status(a:job) ==# 'run'
  3244. sleep 1m
  3245. return running
  3246. endif
  3247. endfunction
  3248. if !exists('s:edit_jobs')
  3249. let s:edit_jobs = {}
  3250. endif
  3251. function! s:RunWait(state, tmp, job, ...) abort
  3252. if a:0 && filereadable(a:1)
  3253. call delete(a:1)
  3254. endif
  3255. try
  3256. if a:tmp.no_more && &more
  3257. let more = &more
  3258. let &more = 0
  3259. endif
  3260. while get(a:state, 'request', '') !=# 'edit' && s:RunTick(a:job)
  3261. call s:RunEcho(a:tmp)
  3262. if !get(a:tmp, 'closed_in')
  3263. let peek = getchar(1)
  3264. if peek != 0 && !(has('win32') && peek == 128)
  3265. let c = getchar()
  3266. let c = type(c) == type(0) ? nr2char(c) : c
  3267. if c ==# "\<C-D>" || c ==# "\<Esc>"
  3268. let a:tmp.closed_in = 1
  3269. let can_pedit = s:RunCloseIn(a:job) && exists('*setbufline')
  3270. for winnr in range(1, winnr('$'))
  3271. if getwinvar(winnr, '&previewwindow') && getbufvar(winbufnr(winnr), '&modified')
  3272. let can_pedit = 0
  3273. endif
  3274. endfor
  3275. if can_pedit
  3276. if has_key(a:tmp, 'echo')
  3277. call remove(a:tmp, 'echo')
  3278. endif
  3279. call writefile(['fugitive: aborting edit due to background operation.'], a:state.file . '.exit')
  3280. exe (&splitbelow ? 'botright' : 'topleft') 'silent pedit ++ff=unix' s:fnameescape(a:state.file)
  3281. let a:state.capture_bufnr = bufnr(a:state.file)
  3282. call setbufvar(a:state.capture_bufnr, '&modified', 1)
  3283. let finished = 0
  3284. redraw!
  3285. return ''
  3286. endif
  3287. else
  3288. call s:RunSend(a:job, c)
  3289. if !a:state.pty
  3290. echon c
  3291. endif
  3292. endif
  3293. endif
  3294. endif
  3295. endwhile
  3296. if !has_key(a:state, 'request') && has_key(a:state, 'job') && exists('*job_status') && job_status(a:job) ==# "dead"
  3297. throw 'fugitive: close callback did not fire; this should never happen'
  3298. endif
  3299. call s:RunEcho(a:tmp)
  3300. if has_key(a:tmp, 'echo')
  3301. let a:tmp.echo = substitute(a:tmp.echo, "^\r\\=\n", '', '')
  3302. echo
  3303. endif
  3304. let finished = !s:RunEdit(a:state, a:tmp, a:job)
  3305. finally
  3306. if exists('l:more')
  3307. let &more = more
  3308. endif
  3309. if !exists('finished')
  3310. try
  3311. if a:state.pty && !get(a:tmp, 'closed_in')
  3312. call s:RunSend(a:job, "\<C-C>")
  3313. elseif type(a:job) == type(0)
  3314. call jobstop(a:job)
  3315. else
  3316. call job_stop(a:job)
  3317. endif
  3318. catch /.*/
  3319. endtry
  3320. elseif finished
  3321. call fugitive#DidChange(a:state)
  3322. endif
  3323. endtry
  3324. return ''
  3325. endfunction
  3326. if !exists('s:resume_queue')
  3327. let s:resume_queue = []
  3328. endif
  3329. function! fugitive#Resume() abort
  3330. while len(s:resume_queue)
  3331. let enqueued = remove(s:resume_queue, 0)
  3332. if enqueued[2] isnot# ''
  3333. try
  3334. call call('s:RunWait', enqueued)
  3335. endtry
  3336. endif
  3337. endwhile
  3338. endfunction
  3339. function! s:RunBufDelete(bufnr) abort
  3340. let state = s:TempState(+a:bufnr)
  3341. if has_key(state, 'job')
  3342. try
  3343. if type(state.job) == type(0)
  3344. call jobstop(state.job)
  3345. else
  3346. call job_stop(state.job)
  3347. endif
  3348. catch
  3349. endtry
  3350. endif
  3351. if has_key(s:edit_jobs, a:bufnr) |
  3352. call add(s:resume_queue, remove(s:edit_jobs, a:bufnr))
  3353. call feedkeys("\<C-\>\<C-N>:redraw!|call delete(" . string(s:resume_queue[-1][0].file . '.edit') .
  3354. \ ")|call fugitive#Resume()|checktime\r", 'n')
  3355. endif
  3356. endfunction
  3357. augroup fugitive_job
  3358. autocmd!
  3359. autocmd BufDelete * call s:RunBufDelete(+expand('<abuf>'))
  3360. autocmd VimLeave *
  3361. \ for s:jobbuf in keys(s:edit_jobs) |
  3362. \ call writefile(['Aborting edit due to Vim exit.'], s:edit_jobs[s:jobbuf][0].file . '.exit') |
  3363. \ redraw! |
  3364. \ call call('s:RunWait', remove(s:edit_jobs, s:jobbuf)) |
  3365. \ endfor
  3366. augroup END
  3367. function! fugitive#CanPty() abort
  3368. return get(g:, 'fugitive_pty_debug_override',
  3369. \ has('unix') && !has('win32unix') && (has('patch-8.0.0744') || has('nvim')) && fugitive#GitVersion() !~# '\.windows\>')
  3370. endfunction
  3371. function! fugitive#PagerFor(argv, ...) abort
  3372. let args = a:argv
  3373. if empty(args)
  3374. return 0
  3375. elseif (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
  3376. return 1
  3377. endif
  3378. if args[0] ==# 'config' && (s:HasOpt(args, '-e', '--edit') ||
  3379. \ !s:HasOpt(args, '--list', '--get-all', '--get-regexp', '--get-urlmatch')) ||
  3380. \ args[0] =~# '^\%(tag\|branch\)$' && (
  3381. \ s:HasOpt(args, '--edit-description', '--unset-upstream', '-m', '-M', '--move', '-c', '-C', '--copy', '-d', '-D', '--delete') ||
  3382. \ len(filter(args[1:-1], 'v:val =~# "^[^-]\\|^--set-upstream-to="')) &&
  3383. \ !s:HasOpt(args, '--contains', '--no-contains', '--merged', '--no-merged', '--points-at'))
  3384. return 0
  3385. endif
  3386. let config = a:0 ? a:1 : fugitive#Config()
  3387. let value = get(fugitive#ConfigGetAll('pager.' . args[0], config), 0, -1)
  3388. if value =~# '^\%(true\|yes\|on\|1\)$'
  3389. return 1
  3390. elseif value =~# '^\%(false\|no|off\|0\|\)$'
  3391. return 0
  3392. elseif type(value) == type('')
  3393. return value
  3394. elseif args[0] =~# '^\%(branch\|config\|diff\|grep\|log\|range-diff\|shortlog\|show\|tag\|whatchanged\)$' ||
  3395. \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
  3396. \ (args[0] ==# 'reflog' && get(args, 1, '') !~# '^\%(expire\|delete\|exists\)$') ||
  3397. \ (args[0] ==# 'am' && s:HasOpt(args, '--show-current-patch'))
  3398. return 1
  3399. else
  3400. return 0
  3401. endif
  3402. endfunction
  3403. let s:disable_colors = []
  3404. for s:colortype in ['advice', 'branch', 'diff', 'grep', 'interactive', 'pager', 'push', 'remote', 'showBranch', 'status', 'transport', 'ui']
  3405. call extend(s:disable_colors, ['-c', 'color.' . s:colortype . '=false'])
  3406. endfor
  3407. unlet s:colortype
  3408. function! fugitive#Command(line1, line2, range, bang, mods, arg, ...) abort
  3409. exe s:VersionCheck()
  3410. let dir = call('s:Dir', a:000)
  3411. if len(dir)
  3412. exe s:DirCheck(dir)
  3413. endif
  3414. let config = copy(fugitive#Config(dir))
  3415. let curwin = a:arg =~# '^++curwin\>' || !a:line2
  3416. let [args, after] = s:SplitExpandChain(substitute(a:arg, '^++curwin\>\s*', '', ''), s:Tree(dir))
  3417. let flags = []
  3418. let pager = -1
  3419. let explicit_pathspec_option = 0
  3420. while len(args)
  3421. if args[0] ==# '-c' && len(args) > 1
  3422. call extend(flags, remove(args, 0, 1))
  3423. elseif args[0] =~# '^-p$\|^--paginate$'
  3424. let pager = 2
  3425. call remove(args, 0)
  3426. elseif args[0] =~# '^-P$\|^--no-pager$'
  3427. let pager = 0
  3428. call remove(args, 0)
  3429. elseif args[0] =~# '^--\%([[:lower:]-]\+-pathspecs\)$'
  3430. let explicit_pathspec_option = 1
  3431. call add(flags, remove(args, 0))
  3432. elseif args[0] =~# '^\%(--no-optional-locks\)$'
  3433. call add(flags, remove(args, 0))
  3434. elseif args[0] =~# '^-C$\|^--\%(exec-path=\|git-dir=\|work-tree=\|bare$\)'
  3435. return 'echoerr ' . string('fugitive: ' . args[0] . ' is not supported')
  3436. else
  3437. break
  3438. endif
  3439. endwhile
  3440. if !explicit_pathspec_option
  3441. call insert(flags, '--no-literal-pathspecs')
  3442. endif
  3443. let no_pager = pager is# 0
  3444. if no_pager
  3445. call add(flags, '--no-pager')
  3446. endif
  3447. let env = {}
  3448. let i = 0
  3449. while i < len(flags) - 1
  3450. if flags[i] ==# '-c'
  3451. let i += 1
  3452. let config_name = tolower(matchstr(flags[i], '^[^=]\+'))
  3453. if has_key(s:prepare_env, config_name) && flags[i] =~# '=.'
  3454. let env[s:prepare_env[config_name]] = matchstr(flags[i], '=\zs.*')
  3455. endif
  3456. if flags[i] =~# '='
  3457. let config[config_name] = [matchstr(flags[i], '=\zs.*')]
  3458. else
  3459. let config[config_name] = [1]
  3460. endif
  3461. endif
  3462. let i += 1
  3463. endwhile
  3464. let options = {'git': s:UserCommandList(), 'git_dir': s:GitDir(dir), 'flags': flags, 'curwin': curwin}
  3465. if empty(args) && pager is# -1
  3466. let cmd = s:StatusCommand(a:line1, a:line2, a:range, curwin ? 0 : a:line2, a:bang, a:mods, '', '', [], options)
  3467. return (empty(cmd) ? 'exe' : cmd) . after
  3468. endif
  3469. let alias = FugitiveConfigGet('alias.' . get(args, 0, ''), config)
  3470. if get(args, 1, '') !=# '--help' && alias !~# '^$\|^!\|[\"'']' && !filereadable(s:VimExecPath() . '/git-' . args[0])
  3471. \ && !(has('win32') && filereadable(s:VimExecPath() . '/git-' . args[0] . '.exe'))
  3472. call remove(args, 0)
  3473. call extend(args, split(alias, '\s\+'), 'keep')
  3474. endif
  3475. let name = substitute(get(args, 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
  3476. if pager is# -1 && name =~# '^\a\+$' && exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
  3477. try
  3478. let overrides = s:{name}Subcommand(a:line1, a:line2, a:range, a:bang, a:mods, extend({'subcommand': args[0], 'subcommand_args': args[1:-1]}, options))
  3479. if type(overrides) == type('')
  3480. return 'exe ' . string(overrides) . after
  3481. endif
  3482. let args = [get(overrides, 'command', args[0])] + get(overrides, 'insert_args', []) + args[1:-1]
  3483. catch /^fugitive:/
  3484. return 'echoerr ' . string(v:exception)
  3485. endtry
  3486. else
  3487. let overrides = {}
  3488. endif
  3489. call extend(env, get(overrides, 'env', {}))
  3490. call s:PrepareEnv(env, dir)
  3491. if pager is# -1
  3492. let pager = fugitive#PagerFor(args, config)
  3493. endif
  3494. let wants_terminal = type(pager) ==# type('') ||
  3495. \ (s:HasOpt(args, ['add', 'checkout', 'commit', 'reset', 'restore', 'stage', 'stash'], '-p', '--patch') ||
  3496. \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive')) && pager is# 0
  3497. if wants_terminal
  3498. let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
  3499. let assign = len(dir) ? "|call FugitiveDetect({'git_dir':" . string(options.git_dir) . '})' : ''
  3500. let argv = s:UserCommandList(options) + args
  3501. let term_opts = len(env) ? {'env': env} : {}
  3502. if has('nvim')
  3503. call fugitive#Autowrite()
  3504. return mods . (curwin ? 'enew' : 'new') . '|call termopen(' . string(argv) . ', ' . string(term_opts) . ')' . assign . '|startinsert' . after
  3505. elseif exists('*term_start')
  3506. call fugitive#Autowrite()
  3507. if curwin
  3508. let term_opts.curwin = 1
  3509. endif
  3510. return mods . 'call term_start(' . string(argv) . ', ' . string(term_opts) . ')' . assign . after
  3511. endif
  3512. endif
  3513. let state = {
  3514. \ 'git': options.git,
  3515. \ 'flags': flags,
  3516. \ 'args': args,
  3517. \ 'git_dir': options.git_dir,
  3518. \ 'cwd': s:UserCommandCwd(dir),
  3519. \ 'filetype': 'git',
  3520. \ 'mods': s:Mods(a:mods),
  3521. \ 'file': s:Resolve(tempname())}
  3522. let allow_pty = 1
  3523. let after_edit = ''
  3524. let stream = 0
  3525. if a:bang && pager isnot# 2
  3526. let state.pager = pager
  3527. let pager = 1
  3528. let stream = exists('*setbufline')
  3529. let do_edit = substitute(s:Mods(a:mods, 'Edge'), '\<tab\>', '-tab', 'g') . 'pedit!'
  3530. elseif pager
  3531. let allow_pty = 0
  3532. if pager is# 2 && a:bang && a:line2 >= 0
  3533. let [do_edit, after_edit] = s:ReadPrepare(a:line1, a:line2, a:range, a:mods)
  3534. elseif pager is# 2 && a:bang
  3535. let do_edit = s:Mods(a:mods) . 'pedit'
  3536. elseif !curwin
  3537. let do_edit = s:Mods(a:mods) . 'split'
  3538. else
  3539. let do_edit = s:Mods(a:mods) . 'edit'
  3540. call s:BlurStatus()
  3541. endif
  3542. call extend(env, {'COLUMNS': '' . get(g:, 'fugitive_columns', 80)}, 'keep')
  3543. endif
  3544. if s:run_jobs
  3545. call extend(env, {'COLUMNS': '' . (&columns - 1)}, 'keep')
  3546. let state.pty = allow_pty && fugitive#CanPty()
  3547. if !state.pty
  3548. let args = s:AskPassArgs(dir) + args
  3549. endif
  3550. let tmp = {
  3551. \ 'no_more': no_pager || get(overrides, 'no_more'),
  3552. \ 'line_count': 0,
  3553. \ 'err': '',
  3554. \ 'out': '',
  3555. \ 'escape': ''}
  3556. let env.FUGITIVE = state.file
  3557. let editor = 'sh ' . s:TempScript(
  3558. \ '[ -f "$FUGITIVE.exit" ] && cat "$FUGITIVE.exit" >&2 && exit 1',
  3559. \ 'echo "$1" > "$FUGITIVE.edit"',
  3560. \ 'printf "\033]51;fugitive:edit\007" >&2',
  3561. \ 'while [ -f "$FUGITIVE.edit" -a ! -f "$FUGITIVE.exit" ]; do sleep 0.05 2>/dev/null || sleep 1; done',
  3562. \ 'exit 0')
  3563. call extend(env, {
  3564. \ 'NO_COLOR': '1',
  3565. \ 'GIT_EDITOR': editor,
  3566. \ 'GIT_SEQUENCE_EDITOR': editor,
  3567. \ 'GIT_PAGER': 'cat',
  3568. \ 'PAGER': 'cat'}, 'keep')
  3569. if len($GPG_TTY) && !has_key(env, 'GPG_TTY')
  3570. let env.GPG_TTY = ''
  3571. let did_override_gpg_tty = 1
  3572. endif
  3573. if stream
  3574. call writefile(['fugitive: aborting edit due to background operation.'], state.file . '.exit')
  3575. elseif pager
  3576. call writefile(['fugitive: aborting edit due to use of pager.'], state.file . '.exit')
  3577. let after = '|' . do_edit . ' ' . s:fnameescape(state.file) . after_edit . after
  3578. else
  3579. let env.GIT_MERGE_AUTOEDIT = '1'
  3580. let tmp.echo = ''
  3581. endif
  3582. let args = s:disable_colors + flags + ['-c', 'advice.waitingForEditor=false'] + args
  3583. let argv = s:UserCommandList({'git': options.git, 'git_dir': options.git_dir}) + args
  3584. let [argv, jobopts] = s:JobOpts(argv, env)
  3585. call fugitive#Autowrite()
  3586. call writefile([], state.file, 'b')
  3587. call s:RunSave(state)
  3588. if has_key(tmp, 'echo')
  3589. echo ""
  3590. endif
  3591. if exists('*ch_close_in')
  3592. call extend(jobopts, {
  3593. \ 'mode': 'raw',
  3594. \ 'out_cb': function('s:RunReceive', [state, tmp, 'out']),
  3595. \ 'err_cb': function('s:RunReceive', [state, tmp, 'err']),
  3596. \ 'close_cb': function('s:RunClose', [state, tmp]),
  3597. \ 'exit_cb': function('s:RunExit', [state, tmp]),
  3598. \ })
  3599. if state.pty
  3600. let jobopts.pty = 1
  3601. endif
  3602. let job = job_start(argv, jobopts)
  3603. else
  3604. let job = jobstart(argv, extend(jobopts, {
  3605. \ 'pty': state.pty,
  3606. \ 'TERM': 'dumb',
  3607. \ 'on_stdout': function('s:RunReceive', [state, tmp, 'out']),
  3608. \ 'on_stderr': function('s:RunReceive', [state, tmp, 'err']),
  3609. \ 'on_exit': function('s:RunClose', [state, tmp]),
  3610. \ }))
  3611. endif
  3612. let state.job = job
  3613. if pager
  3614. let tmp.closed_in = 1
  3615. call s:RunCloseIn(job)
  3616. endif
  3617. if stream
  3618. exe 'silent' do_edit '++ff=unix' s:fnameescape(state.file)
  3619. let state.capture_bufnr = bufnr(state.file)
  3620. call setbufvar(state.capture_bufnr, '&modified', 1)
  3621. return (after_edit . after)[1:-1]
  3622. endif
  3623. call add(s:resume_queue, [state, tmp, job])
  3624. return 'call fugitive#Resume()|checktime' . after
  3625. elseif pager
  3626. let pre = s:BuildEnvPrefix(env)
  3627. try
  3628. if exists('+guioptions') && &guioptions =~# '!'
  3629. let guioptions = &guioptions
  3630. set guioptions-=!
  3631. endif
  3632. silent! execute '!' . escape(pre . s:shellesc(s:UserCommandList(options) + s:disable_colors + flags + ['--no-pager'] + args), '!#%') .
  3633. \ (&shell =~# 'csh' ? ' >& ' . s:shellesc(state.file) : ' > ' . s:shellesc(state.file) . ' 2>&1')
  3634. let state.exit_status = v:shell_error
  3635. finally
  3636. if exists('guioptions')
  3637. let &guioptions = guioptions
  3638. endif
  3639. endtry
  3640. redraw!
  3641. call s:RunSave(state)
  3642. call s:RunFinished(state)
  3643. return do_edit . ' ' . s:fnameescape(state.file) . after_edit .
  3644. \ '|call fugitive#DidChange(fugitive#Result(' . string(state.file) . '))' . after
  3645. elseif has('win32')
  3646. return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git on Windows')
  3647. elseif has('gui_running')
  3648. return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git in GVim')
  3649. else
  3650. if !explicit_pathspec_option && get(options.flags, 0, '') ==# '--no-literal-pathspecs'
  3651. call remove(options.flags, 0)
  3652. endif
  3653. if exists('l:did_override_gpg_tty')
  3654. call remove(env, 'GPG_TTY')
  3655. endif
  3656. let cmd = s:BuildEnvPrefix(env) . s:shellesc(s:UserCommandList(options) + args)
  3657. let after = '|call fugitive#DidChange(' . string(dir) . ')' . after
  3658. if !wants_terminal && (no_pager || index(['add', 'clean', 'reset', 'restore', 'stage'], get(args, 0, '')) >= 0 || s:HasOpt(args, ['checkout'], '-q', '--quiet', '--no-progress'))
  3659. let output = substitute(s:SystemError(cmd)[0], "\n$", '', '')
  3660. if len(output)
  3661. try
  3662. if &more && no_pager
  3663. let more = 1
  3664. set nomore
  3665. endif
  3666. echo substitute(output, "\n$", "", "")
  3667. finally
  3668. if exists('l:more')
  3669. set more
  3670. endif
  3671. endtry
  3672. endif
  3673. return 'checktime' . after
  3674. else
  3675. return 'exe ' . string('noautocmd !' . escape(cmd, '!#%')) . after
  3676. endif
  3677. endif
  3678. endfunction
  3679. let s:exec_paths = {}
  3680. function! s:ExecPath() abort
  3681. let git = s:GitShellCmd()
  3682. if !has_key(s:exec_paths, git)
  3683. let path = get(s:JobExecute(s:GitCmd() + ['--exec-path'], {}, [], [], {}).stdout, 0, '')
  3684. let s:exec_paths[git] = [path, FugitiveVimPath(path)]
  3685. endif
  3686. return s:exec_paths[git]
  3687. endfunction
  3688. function! s:VimExecPath() abort
  3689. return s:ExecPath()[1]
  3690. endfunction
  3691. let s:subcommands_before_2_5 = [
  3692. \ 'add', 'am', 'apply', 'archive', 'bisect', 'blame', 'branch', 'bundle',
  3693. \ 'checkout', 'cherry', 'cherry-pick', 'citool', 'clean', 'clone', 'commit', 'config',
  3694. \ 'describe', 'diff', 'difftool', 'fetch', 'format-patch', 'fsck',
  3695. \ 'gc', 'grep', 'gui', 'help', 'init', 'instaweb', 'log',
  3696. \ 'merge', 'mergetool', 'mv', 'notes', 'pull', 'push',
  3697. \ 'rebase', 'reflog', 'remote', 'repack', 'replace', 'request-pull', 'reset', 'revert', 'rm',
  3698. \ 'send-email', 'shortlog', 'show', 'show-branch', 'stash', 'stage', 'status', 'submodule',
  3699. \ 'tag', 'whatchanged',
  3700. \ ]
  3701. let s:path_subcommands = {}
  3702. function! s:CompletableSubcommands(dir) abort
  3703. let c_exec_path = s:cpath(s:VimExecPath())
  3704. if !has_key(s:path_subcommands, c_exec_path)
  3705. if fugitive#GitVersion(2, 18)
  3706. let [lines, exec_error] = s:LinesError([a:dir, '--list-cmds=list-mainporcelain,nohelpers,list-complete'])
  3707. call filter(lines, 'v:val =~# "^\\S\\+$"')
  3708. if !exec_error && len(lines)
  3709. let s:path_subcommands[c_exec_path] = lines
  3710. else
  3711. let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
  3712. \ ['maintenance', 'prune', 'range-diff', 'restore', 'sparse-checkout', 'switch', 'worktree']
  3713. endif
  3714. else
  3715. let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
  3716. \ (fugitive#GitVersion(2, 5) ? ['worktree'] : [])
  3717. endif
  3718. endif
  3719. let commands = copy(s:path_subcommands[c_exec_path])
  3720. for path in split($PATH, has('win32') ? ';' : ':')
  3721. if path !~# '^/\|^\a:[\\/]'
  3722. continue
  3723. endif
  3724. let cpath = s:cpath(path)
  3725. if !has_key(s:path_subcommands, cpath)
  3726. let s:path_subcommands[cpath] = filter(map(s:GlobComplete(path.'/git-', '*', 1),'substitute(v:val,"\\.exe$","","")'), 'v:val !~# "--\\|/"')
  3727. endif
  3728. call extend(commands, s:path_subcommands[cpath])
  3729. endfor
  3730. call extend(commands, keys(fugitive#ConfigGetRegexp('^alias\.\zs[^.]\+$', a:dir)))
  3731. let configured = split(FugitiveConfigGet('completion.commands', a:dir), '\s\+')
  3732. let rejected = {}
  3733. for command in configured
  3734. if command =~# '^-.'
  3735. let rejected[strpart(command, 1)] = 1
  3736. endif
  3737. endfor
  3738. call filter(configured, 'v:val !~# "^-"')
  3739. let results = filter(sort(commands + configured), '!has_key(rejected, v:val)')
  3740. if exists('*uniq')
  3741. return uniq(results)
  3742. else
  3743. let i = 1
  3744. while i < len(results)
  3745. if results[i] ==# results[i-1]
  3746. call remove(results, i)
  3747. else
  3748. let i += 1
  3749. endif
  3750. endwhile
  3751. return results
  3752. endif
  3753. endfunction
  3754. function! fugitive#Complete(lead, ...) abort
  3755. let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? s:Dir(a:3) : s:Dir()
  3756. let root = a:0 >= 4 ? a:4 : s:Tree(s:Dir())
  3757. let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
  3758. let subcmd = matchstr(pre, '\u\w*[! ] *\%(\%(++\S\+\|--\S\+-pathspecs\|-c\s\+\S\+\)\s\+\)*\zs[[:alnum:]][[:alnum:]-]*\ze ')
  3759. if empty(subcmd) && a:lead =~# '^+'
  3760. let results = ['++curwin']
  3761. elseif empty(subcmd) && a:lead =~# '^-'
  3762. let results = ['--literal-pathspecs', '--no-literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs', '--no-optional-locks']
  3763. elseif empty(subcmd)
  3764. let results = s:CompletableSubcommands(dir)
  3765. elseif a:0 ==# 2 && subcmd =~# '^\%(commit\|revert\|push\|fetch\|pull\|merge\|rebase\|bisect\)$'
  3766. let cmdline = substitute(a:1, '\u\w*\([! ] *\)' . subcmd, 'G' . subcmd, '')
  3767. let caps_subcmd = substitute(subcmd, '\%(^\|-\)\l', '\u&', 'g')
  3768. return fugitive#{caps_subcmd}Complete(a:lead, cmdline, a:2 + len(cmdline) - len(a:1), dir, root)
  3769. elseif pre =~# ' -- '
  3770. return fugitive#CompletePath(a:lead, a:1, a:2, dir, root)
  3771. elseif a:lead =~# '^-'
  3772. let results = split(s:ChompDefault('', [dir, subcmd, '--git-completion-helper']), ' ')
  3773. else
  3774. return fugitive#CompleteObject(a:lead, a:1, a:2, dir, root)
  3775. endif
  3776. return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
  3777. endfunction
  3778. function! fugitive#CompleteForWorkingDir(A, L, P, ...) abort
  3779. let path = a:0 ? a:1 : getcwd()
  3780. return fugitive#Complete(a:A, a:L, a:P, FugitiveExtractGitDir(path), path)
  3781. endfunction
  3782. " Section: :Gcd, :Glcd
  3783. function! fugitive#CdComplete(A, L, P) abort
  3784. return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
  3785. endfunction
  3786. function! fugitive#Cd(path, ...) abort
  3787. let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
  3788. if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
  3789. let dir = s:Dir()
  3790. exe s:DirCheck(dir)
  3791. let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
  3792. endif
  3793. return (a:0 && a:1 ? 'lcd ' : 'cd ') . fnameescape(s:VimSlash(path))
  3794. endfunction
  3795. " Section: :Gstatus
  3796. function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
  3797. let dir = a:0 ? s:Dir(a:1) : s:Dir()
  3798. exe s:DirCheck(dir)
  3799. try
  3800. let mods = s:Mods(a:mods, 'Edge')
  3801. let file = fugitive#Find(':', dir)
  3802. let arg = ' +setl\ foldmarker=<<<<<<<<,>>>>>>>>\|let\ w:fugitive_status=FugitiveGitDir() ' .
  3803. \ s:fnameescape(file)
  3804. for tabnr in [tabpagenr()] + (mods =~# '\<tab\>' ? range(1, tabpagenr('$')) : [])
  3805. let bufs = tabpagebuflist(tabnr)
  3806. for winnr in range(1, tabpagewinnr(tabnr, '$'))
  3807. if s:cpath(file, fnamemodify(bufname(bufs[winnr-1]), ':p'))
  3808. if tabnr == tabpagenr() && winnr == winnr()
  3809. call s:ReloadStatus()
  3810. else
  3811. call s:ExpireStatus(dir)
  3812. exe tabnr . 'tabnext'
  3813. exe winnr . 'wincmd w'
  3814. endif
  3815. let w:fugitive_status = dir
  3816. 1
  3817. return ''
  3818. endif
  3819. endfor
  3820. endfor
  3821. if a:count ==# 0
  3822. return mods . 'edit' . (a:bang ? '!' : '') . arg
  3823. elseif a:bang
  3824. return mods . 'pedit' . arg . '|wincmd P'
  3825. else
  3826. return mods . 'keepalt split' . arg
  3827. endif
  3828. catch /^fugitive:/
  3829. return 'echoerr ' . string(v:exception)
  3830. endtry
  3831. return ''
  3832. endfunction
  3833. function! s:StageJump(offset, section, ...) abort
  3834. let line = search('^\%(' . a:section . '\)', 'nw')
  3835. if !line && a:0
  3836. let line = search('^\%(' . a:1 . '\)', 'nw')
  3837. endif
  3838. if line
  3839. exe line
  3840. if a:offset
  3841. for i in range(a:offset)
  3842. call search(s:file_commit_pattern . '\|^$', 'W')
  3843. if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
  3844. call search(s:file_commit_pattern . '\|^$', 'W')
  3845. endif
  3846. if empty(getline('.'))
  3847. return ''
  3848. endif
  3849. endfor
  3850. call s:StageReveal()
  3851. else
  3852. call s:StageReveal()
  3853. +
  3854. endif
  3855. endif
  3856. return ''
  3857. endfunction
  3858. function! s:StageSeek(info, fallback) abort
  3859. let info = a:info
  3860. if empty(info.heading)
  3861. return a:fallback
  3862. endif
  3863. let line = search('^' . escape(info.heading, '^$.*[]~\') . ' (\d\++\=)$', 'wn')
  3864. if !line
  3865. for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
  3866. let line = search('^' . section, 'wn')
  3867. if line
  3868. return line + (info.index > 0 ? 1 : 0)
  3869. endif
  3870. endfor
  3871. return 1
  3872. endif
  3873. let i = 0
  3874. while len(getline(line))
  3875. let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
  3876. if len(filename) &&
  3877. \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
  3878. \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
  3879. \ filename ==# info.filename)
  3880. if info.offset < 0
  3881. return line
  3882. else
  3883. if getline(line+1) !~# '^@'
  3884. exe s:StageInline('show', line)
  3885. endif
  3886. if getline(line+1) !~# '^@'
  3887. return line
  3888. endif
  3889. let type = info.sigil ==# '-' ? '-' : '+'
  3890. let offset = -1
  3891. while offset < info.offset
  3892. let line += 1
  3893. if getline(line) =~# '^@'
  3894. let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
  3895. elseif getline(line) =~# '^[ ' . type . ']'
  3896. let offset += 1
  3897. elseif getline(line) !~# '^[ @\+-]'
  3898. return line - 1
  3899. endif
  3900. endwhile
  3901. return line
  3902. endif
  3903. endif
  3904. let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
  3905. if len(commit) && commit ==# info.commit
  3906. return line
  3907. endif
  3908. if i ==# info.index
  3909. let backup = line
  3910. endif
  3911. let i += getline(line) !~# '^[ @\+-]'
  3912. let line += 1
  3913. endwhile
  3914. return exists('backup') ? backup : line - 1
  3915. endfunction
  3916. function! s:DoAutocmdChanged(dir) abort
  3917. let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
  3918. if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
  3919. return ''
  3920. endif
  3921. try
  3922. let g:fugitive_event = dir
  3923. if type(a:dir) == type({}) && has_key(a:dir, 'args') && has_key(a:dir, 'exit_status')
  3924. let g:fugitive_result = a:dir
  3925. endif
  3926. exe s:DoAutocmd('User FugitiveChanged')
  3927. finally
  3928. unlet! g:fugitive_event g:fugitive_result
  3929. " Force statusline reload with the buffer's Git dir
  3930. let &l:ro = &l:ro
  3931. endtry
  3932. return ''
  3933. endfunction
  3934. function! s:ReloadStatusBuffer(...) abort
  3935. if get(b:, 'fugitive_type', '') !=# 'index'
  3936. return ''
  3937. endif
  3938. let original_lnum = a:0 ? a:1 : line('.')
  3939. let info = s:StageInfo(original_lnum)
  3940. call fugitive#BufReadStatus(0)
  3941. call setpos('.', [0, s:StageSeek(info, original_lnum), 1, 0])
  3942. return ''
  3943. endfunction
  3944. function! s:ReloadStatus(...) abort
  3945. call s:ExpireStatus(-1)
  3946. call s:ReloadStatusBuffer(a:0 ? a:1 : line('.'))
  3947. exe s:DoAutocmdChanged(-1)
  3948. return ''
  3949. endfunction
  3950. let s:last_time = reltime()
  3951. if !exists('s:last_times')
  3952. let s:last_times = {}
  3953. endif
  3954. function! s:ExpireStatus(bufnr) abort
  3955. if a:bufnr is# -2 || a:bufnr is# 0
  3956. let s:head_cache = {}
  3957. let s:last_time = reltime()
  3958. return ''
  3959. endif
  3960. let head_file = fugitive#Find('.git/HEAD', a:bufnr)
  3961. if !empty(head_file)
  3962. let s:last_times[s:Tree(a:bufnr) . '/'] = reltime()
  3963. if has_key(s:head_cache, head_file)
  3964. call remove(s:head_cache, head_file)
  3965. endif
  3966. endif
  3967. return ''
  3968. endfunction
  3969. function! s:ReloadWinStatus(...) abort
  3970. if get(b:, 'fugitive_type', '') !=# 'index' || &modified
  3971. return
  3972. endif
  3973. if !exists('b:fugitive_reltime')
  3974. exe s:ReloadStatusBuffer()
  3975. return
  3976. endif
  3977. let t = b:fugitive_reltime
  3978. if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
  3979. \ reltimestr(reltime(get(s:last_times, s:Tree() . '/', t), t)) =~# '-\|\d\{10\}\.'
  3980. exe s:ReloadStatusBuffer()
  3981. endif
  3982. endfunction
  3983. function! s:ReloadTabStatus(...) abort
  3984. let mytab = tabpagenr()
  3985. let tab = a:0 ? a:1 : mytab
  3986. let winnr = 1
  3987. while winnr <= tabpagewinnr(tab, '$')
  3988. if getbufvar(tabpagebuflist(tab)[winnr-1], 'fugitive_type') ==# 'index'
  3989. execute 'tabnext '.tab
  3990. if winnr != winnr()
  3991. execute winnr.'wincmd w'
  3992. let restorewinnr = 1
  3993. endif
  3994. try
  3995. call s:ReloadWinStatus()
  3996. finally
  3997. if exists('restorewinnr')
  3998. unlet restorewinnr
  3999. wincmd p
  4000. endif
  4001. execute 'tabnext '.mytab
  4002. endtry
  4003. endif
  4004. let winnr += 1
  4005. endwhile
  4006. unlet! t:fugitive_reload_status
  4007. endfunction
  4008. function! fugitive#DidChange(...) abort
  4009. call s:ExpireStatus(a:0 ? a:1 : -1)
  4010. if a:0 > 1 ? a:2 : (!a:0 || a:1 isnot# 0)
  4011. let t = reltime()
  4012. let t:fugitive_reload_status = t
  4013. for tabnr in range(1, tabpagenr('$'))
  4014. call settabvar(tabnr, 'fugitive_reload_status', t)
  4015. endfor
  4016. call s:ReloadTabStatus()
  4017. else
  4018. call s:ReloadWinStatus()
  4019. return ''
  4020. endif
  4021. exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
  4022. return ''
  4023. endfunction
  4024. function! fugitive#ReloadStatus(...) abort
  4025. return call('fugitive#DidChange', a:000)
  4026. endfunction
  4027. function! fugitive#EfmDir(...) abort
  4028. let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
  4029. let dir = substitute(dir, '%%', '%', 'g')
  4030. let dir = substitute(dir, '\\\ze[\,]', '', 'g')
  4031. return dir
  4032. endfunction
  4033. augroup fugitive_status
  4034. autocmd!
  4035. autocmd BufWritePost * call fugitive#DidChange(+expand('<abuf>'), 0)
  4036. autocmd User FileChmodPost,FileUnlinkPost call fugitive#DidChange(+expand('<abuf>'), 0)
  4037. autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#DidChange(0)
  4038. autocmd BufDelete * nested
  4039. \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
  4040. \ if !empty(FugitiveGitDir(+expand('<abuf>'))) |
  4041. \ call fugitive#DidChange(+expand('<abuf>')) |
  4042. \ else |
  4043. \ call fugitive#DidChange(0) |
  4044. \ endif |
  4045. \ endif
  4046. autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
  4047. \ call fugitive#DidChange(fugitive#EfmDir())
  4048. autocmd FocusGained *
  4049. \ if get(g:, 'fugitive_focus_gained', !has('win32')) |
  4050. \ call fugitive#DidChange(0) |
  4051. \ endif
  4052. autocmd BufEnter index,index.lock,fugitive://*//
  4053. \ call s:ReloadWinStatus()
  4054. autocmd TabEnter *
  4055. \ if exists('t:fugitive_reload_status') |
  4056. \ call s:ReloadTabStatus() |
  4057. \ endif
  4058. augroup END
  4059. function! s:StageInfo(...) abort
  4060. let lnum = a:0 ? a:1 : line('.')
  4061. let sigil = matchstr(getline(lnum), '^[ @\+-]')
  4062. let offset = -1
  4063. if len(sigil)
  4064. let [lnum, old_lnum, new_lnum] = s:HunkPosition(lnum)
  4065. let offset = sigil ==# '-' ? old_lnum : new_lnum
  4066. while getline(lnum) =~# '^[ @\+-]'
  4067. let lnum -= 1
  4068. endwhile
  4069. endif
  4070. let slnum = lnum + 1
  4071. let heading = ''
  4072. let index = 0
  4073. while len(getline(slnum - 1)) && empty(heading)
  4074. let slnum -= 1
  4075. let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
  4076. if empty(heading) && getline(slnum) !~# '^[ @\+-]'
  4077. let index += 1
  4078. endif
  4079. endwhile
  4080. let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
  4081. let file = get(get(b:fugitive_files, heading, {}), text, {})
  4082. let relative = get(file, 'relative', len(text) ? [text] : [])
  4083. return {'section': matchstr(heading, '^\u\l\+'),
  4084. \ 'heading': heading,
  4085. \ 'sigil': sigil,
  4086. \ 'offset': offset,
  4087. \ 'filename': text,
  4088. \ 'relative': copy(relative),
  4089. \ 'paths': map(copy(relative), 's:Tree() . "/" . v:val'),
  4090. \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
  4091. \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
  4092. \ 'submodule': get(file, 'submodule', ''),
  4093. \ 'index': index}
  4094. endfunction
  4095. function! s:Selection(arg1, ...) abort
  4096. if a:arg1 ==# 'n'
  4097. let arg1 = line('.')
  4098. let arg2 = -v:count
  4099. elseif a:arg1 ==# 'v'
  4100. let arg1 = line("'<")
  4101. let arg2 = line("'>")
  4102. else
  4103. let arg1 = a:arg1
  4104. let arg2 = a:0 ? a:1 : 0
  4105. endif
  4106. let first = arg1
  4107. if arg2 < 0
  4108. let last = first - arg2 - 1
  4109. elseif arg2 > 0
  4110. let last = arg2
  4111. else
  4112. let last = first
  4113. endif
  4114. while first <= line('$') && getline(first) =~# '^$\|^[A-Z][a-z]'
  4115. let first += 1
  4116. endwhile
  4117. if first > last || &filetype !=# 'fugitive'
  4118. return []
  4119. endif
  4120. let flnum = first
  4121. while getline(flnum) =~# '^[ @\+-]'
  4122. let flnum -= 1
  4123. endwhile
  4124. let slnum = flnum + 1
  4125. let heading = ''
  4126. let index = 0
  4127. while empty(heading)
  4128. let slnum -= 1
  4129. let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
  4130. if empty(heading) && getline(slnum) !~# '^[ @\+-]'
  4131. let index += 1
  4132. endif
  4133. endwhile
  4134. let results = []
  4135. let template = {
  4136. \ 'heading': heading,
  4137. \ 'section': matchstr(heading, '^\u\l\+'),
  4138. \ 'filename': '',
  4139. \ 'relative': [],
  4140. \ 'paths': [],
  4141. \ 'commit': '',
  4142. \ 'status': '',
  4143. \ 'patch': 0,
  4144. \ 'index': index}
  4145. let line = getline(flnum)
  4146. let lnum = first - (arg1 == flnum ? 0 : 1)
  4147. let root = s:Tree() . '/'
  4148. while lnum <= last
  4149. let heading = matchstr(line, '^\u\l\+\ze.\{-\}\ze (\d\++\=)$')
  4150. if len(heading)
  4151. let template.heading = heading
  4152. let template.section = matchstr(heading, '^\u\l\+')
  4153. let template.index = 0
  4154. elseif line =~# '^[ @\+-]'
  4155. let template.index -= 1
  4156. if !results[-1].patch
  4157. let results[-1].patch = lnum
  4158. endif
  4159. let results[-1].lnum = lnum
  4160. elseif line =~# '^[A-Z?] '
  4161. let text = matchstr(line, '^[A-Z?] \zs.*')
  4162. let file = get(get(b:fugitive_files, template.heading, {}), text, {})
  4163. let relative = get(file, 'relative', len(text) ? [text] : [])
  4164. call add(results, extend(deepcopy(template), {
  4165. \ 'lnum': lnum,
  4166. \ 'filename': text,
  4167. \ 'relative': copy(relative),
  4168. \ 'paths': map(copy(relative), 'root . v:val'),
  4169. \ 'status': matchstr(line, '^[A-Z?]'),
  4170. \ }))
  4171. elseif line =~# '^\x\x\x\+ '
  4172. call add(results, extend({
  4173. \ 'lnum': lnum,
  4174. \ 'commit': matchstr(line, '^\x\x\x\+'),
  4175. \ }, template, 'keep'))
  4176. elseif line =~# '^\l\+ \x\x\x\+ '
  4177. call add(results, extend({
  4178. \ 'lnum': lnum,
  4179. \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
  4180. \ 'status': matchstr(line, '^\l\+'),
  4181. \ }, template, 'keep'))
  4182. endif
  4183. let lnum += 1
  4184. let template.index += 1
  4185. let line = getline(lnum)
  4186. endwhile
  4187. if len(results) && results[0].patch && arg2 == 0
  4188. while getline(results[0].patch) =~# '^[ \+-]'
  4189. let results[0].patch -= 1
  4190. endwhile
  4191. while getline(results[0].lnum + 1) =~# '^[ \+-]'
  4192. let results[0].lnum += 1
  4193. endwhile
  4194. endif
  4195. return results
  4196. endfunction
  4197. function! s:StageArgs(visual) abort
  4198. let commits = []
  4199. let paths = []
  4200. for record in s:Selection(a:visual ? 'v' : 'n')
  4201. if len(record.commit)
  4202. call add(commits, record.commit)
  4203. endif
  4204. call extend(paths, record.paths)
  4205. endfor
  4206. if s:cpath(s:Tree(), getcwd())
  4207. call map(paths, 'fugitive#Path(v:val, "./")')
  4208. endif
  4209. return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
  4210. endfunction
  4211. function! s:Do(action, visual) abort
  4212. let line = getline('.')
  4213. let reload = 0
  4214. if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
  4215. let header = matchstr(line, '^\S\+\ze:')
  4216. if len(header) && exists('*s:Do' . a:action . header . 'Header')
  4217. let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
  4218. else
  4219. let section = matchstr(line, '^\S\+')
  4220. if exists('*s:Do' . a:action . section . 'Heading')
  4221. let reload = s:Do{a:action}{section}Heading(line) > 0
  4222. endif
  4223. endif
  4224. return reload ? s:ReloadStatus() : ''
  4225. endif
  4226. let selection = s:Selection(a:visual ? 'v' : 'n')
  4227. if empty(selection)
  4228. return ''
  4229. endif
  4230. call filter(selection, 'v:val.section ==# selection[0].section')
  4231. let status = 0
  4232. let err = ''
  4233. try
  4234. for record in selection
  4235. if exists('*s:Do' . a:action . record.section)
  4236. let status = s:Do{a:action}{record.section}(record)
  4237. else
  4238. continue
  4239. endif
  4240. if !status
  4241. return ''
  4242. endif
  4243. let reload = reload || (status > 0)
  4244. endfor
  4245. if status < 0
  4246. execute record.lnum + 1
  4247. endif
  4248. let success = 1
  4249. catch /^fugitive:/
  4250. return 'echoerr ' . string(v:exception)
  4251. finally
  4252. if reload
  4253. execute s:ReloadStatus()
  4254. endif
  4255. if exists('success')
  4256. call s:StageReveal()
  4257. endif
  4258. endtry
  4259. return ''
  4260. endfunction
  4261. function! s:StageReveal() abort
  4262. exe 'normal! zv'
  4263. let begin = line('.')
  4264. if getline(begin) =~# '^@'
  4265. let end = begin + 1
  4266. while getline(end) =~# '^[ \+-]'
  4267. let end += 1
  4268. endwhile
  4269. elseif getline(begin) =~# '^commit '
  4270. let end = begin
  4271. while end < line('$') && getline(end + 1) !~# '^commit '
  4272. let end += 1
  4273. endwhile
  4274. elseif getline(begin) =~# s:section_pattern
  4275. let end = begin
  4276. while len(getline(end + 1))
  4277. let end += 1
  4278. endwhile
  4279. endif
  4280. if exists('end')
  4281. while line('.') > line('w0') + &scrolloff && end > line('w$')
  4282. execute "normal! \<C-E>"
  4283. endwhile
  4284. endif
  4285. endfunction
  4286. let s:file_pattern = '^[A-Z?] .\|^diff --'
  4287. let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
  4288. let s:item_pattern = s:file_commit_pattern . '\|^@@'
  4289. function! s:NextHunk(count) abort
  4290. if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
  4291. exe s:StageInline('show')
  4292. endif
  4293. for i in range(a:count)
  4294. if &filetype ==# 'fugitive'
  4295. call search(s:file_pattern . '\|^@', 'W')
  4296. if getline('.') =~# s:file_pattern
  4297. exe s:StageInline('show')
  4298. if getline(line('.') + 1) =~# '^@'
  4299. +
  4300. endif
  4301. endif
  4302. else
  4303. call search('^@@', 'W')
  4304. endif
  4305. endfor
  4306. call s:StageReveal()
  4307. return '.'
  4308. endfunction
  4309. function! s:PreviousHunk(count) abort
  4310. normal! 0
  4311. for i in range(a:count)
  4312. if &filetype ==# 'fugitive'
  4313. if getline('.') =~# '^@' && getline(line('.') - 1) =~# s:file_pattern
  4314. -
  4315. endif
  4316. let lnum = search(s:file_pattern . '\|^@','Wbn')
  4317. call s:StageInline('show', lnum)
  4318. call search('^? .\|^@','Wb')
  4319. else
  4320. call search('^@@', 'Wb')
  4321. endif
  4322. endfor
  4323. call s:StageReveal()
  4324. return '.'
  4325. endfunction
  4326. function! s:NextFile(count) abort
  4327. for i in range(a:count)
  4328. exe s:StageInline('hide')
  4329. if !search(s:file_pattern, 'W')
  4330. break
  4331. endif
  4332. endfor
  4333. exe s:StageInline('hide')
  4334. return '.'
  4335. endfunction
  4336. function! s:PreviousFile(count) abort
  4337. exe s:StageInline('hide')
  4338. normal! 0
  4339. for i in range(a:count)
  4340. if !search(s:file_pattern, 'Wb')
  4341. break
  4342. endif
  4343. exe s:StageInline('hide')
  4344. endfor
  4345. return '.'
  4346. endfunction
  4347. function! s:NextItem(count) abort
  4348. for i in range(a:count)
  4349. if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
  4350. call search('^commit ', 'W')
  4351. endif
  4352. endfor
  4353. call s:StageReveal()
  4354. return '.'
  4355. endfunction
  4356. function! s:PreviousItem(count) abort
  4357. normal! 0
  4358. for i in range(a:count)
  4359. if !search(s:item_pattern, 'Wb') && getline('.') !~# s:item_pattern
  4360. call search('^commit ', 'Wb')
  4361. endif
  4362. endfor
  4363. call s:StageReveal()
  4364. return '.'
  4365. endfunction
  4366. let s:section_pattern = '^[A-Z][a-z][^:]*$'
  4367. let s:section_commit_pattern = s:section_pattern . '\|^commit '
  4368. function! s:NextSection(count) abort
  4369. let orig = line('.')
  4370. if getline('.') !~# '^commit '
  4371. -
  4372. endif
  4373. for i in range(a:count)
  4374. if !search(s:section_commit_pattern, 'W')
  4375. break
  4376. endif
  4377. endfor
  4378. if getline('.') =~# s:section_commit_pattern
  4379. call s:StageReveal()
  4380. return getline('.') =~# s:section_pattern ? '+' : ':'
  4381. else
  4382. return orig
  4383. endif
  4384. endfunction
  4385. function! s:PreviousSection(count) abort
  4386. let orig = line('.')
  4387. if getline('.') !~# '^commit '
  4388. -
  4389. endif
  4390. normal! 0
  4391. for i in range(a:count)
  4392. if !search(s:section_commit_pattern . '\|\%^', 'bW')
  4393. break
  4394. endif
  4395. endfor
  4396. if getline('.') =~# s:section_commit_pattern || line('.') == 1
  4397. call s:StageReveal()
  4398. return getline('.') =~# s:section_pattern ? '+' : ':'
  4399. else
  4400. return orig
  4401. endif
  4402. endfunction
  4403. function! s:NextSectionEnd(count) abort
  4404. +
  4405. if empty(getline('.'))
  4406. +
  4407. endif
  4408. for i in range(a:count)
  4409. if !search(s:section_commit_pattern, 'W')
  4410. return '$'
  4411. endif
  4412. endfor
  4413. return search('^.', 'Wb')
  4414. endfunction
  4415. function! s:PreviousSectionEnd(count) abort
  4416. let old = line('.')
  4417. for i in range(a:count)
  4418. if search(s:section_commit_pattern, 'Wb') <= 1
  4419. exe old
  4420. if i
  4421. break
  4422. else
  4423. return ''
  4424. endif
  4425. endif
  4426. let old = line('.')
  4427. endfor
  4428. return search('^.', 'Wb')
  4429. endfunction
  4430. function! s:PatchSearchExpr(reverse) abort
  4431. let line = getline('.')
  4432. if col('.') ==# 1 && line =~# '^[+-]'
  4433. if line =~# '^[+-]\{3\} '
  4434. let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
  4435. else
  4436. let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
  4437. endif
  4438. if a:reverse
  4439. return '?' . escape(pattern, '/?') . "\<CR>"
  4440. else
  4441. return '/' . escape(pattern, '/') . "\<CR>"
  4442. endif
  4443. endif
  4444. return a:reverse ? '#' : '*'
  4445. endfunction
  4446. function! s:StageInline(mode, ...) abort
  4447. if &filetype !=# 'fugitive'
  4448. return ''
  4449. endif
  4450. let lnum1 = a:0 ? a:1 : line('.')
  4451. let lnum = lnum1 + 1
  4452. if a:0 > 1 && a:2 == 0 && lnum1 == 1
  4453. let lnum = line('$') - 1
  4454. elseif a:0 > 1 && a:2 == 0
  4455. let info = s:StageInfo(lnum - 1)
  4456. if empty(info.paths) && len(info.section)
  4457. while len(getline(lnum))
  4458. let lnum += 1
  4459. endwhile
  4460. endif
  4461. elseif a:0 > 1
  4462. let lnum += a:2 - 1
  4463. endif
  4464. while lnum > lnum1
  4465. let lnum -= 1
  4466. while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
  4467. let lnum -= 1
  4468. endwhile
  4469. let info = s:StageInfo(lnum)
  4470. if !has_key(b:fugitive_diff, info.section)
  4471. continue
  4472. endif
  4473. if getline(lnum + 1) =~# '^[ @\+-]'
  4474. let lnum2 = lnum + 1
  4475. while getline(lnum2 + 1) =~# '^[ @\+-]'
  4476. let lnum2 += 1
  4477. endwhile
  4478. if a:mode !=# 'show'
  4479. setlocal modifiable noreadonly
  4480. exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
  4481. call remove(b:fugitive_expanded[info.section], info.filename)
  4482. setlocal nomodifiable readonly nomodified
  4483. endif
  4484. continue
  4485. endif
  4486. if !has_key(b:fugitive_diff, info.section) || info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
  4487. continue
  4488. endif
  4489. let mode = ''
  4490. let diff = []
  4491. if info.status ==# 'U'
  4492. let diff_header = 'diff --cc ' . s:Quote(info.relative[0])
  4493. else
  4494. let diff_header = 'diff --git ' . s:Quote(info.relative[-1]) . ' ' . s:Quote(info.relative[0])
  4495. endif
  4496. let stdout = fugitive#Wait(b:fugitive_diff[info.section]).stdout
  4497. let start = index(stdout, diff_header)
  4498. if start == -1
  4499. continue
  4500. endif
  4501. let index = start + 1
  4502. while get(stdout, index, '@@') !~# '^@@\|^diff '
  4503. let index += 1
  4504. endwhile
  4505. while get(stdout, index, '') =~# '^[@ \+-]'
  4506. call add(diff, stdout[index])
  4507. let index += 1
  4508. endwhile
  4509. if len(diff)
  4510. setlocal modifiable noreadonly
  4511. silent call append(lnum, diff)
  4512. let b:fugitive_expanded[info.section][info.filename] = [start]
  4513. setlocal nomodifiable readonly nomodified
  4514. if foldclosed(lnum+1) > 0
  4515. silent exe (lnum+1) . ',' . (lnum+len(diff)) . 'foldopen!'
  4516. endif
  4517. endif
  4518. endwhile
  4519. return lnum
  4520. endfunction
  4521. function! s:NextExpandedHunk(count) abort
  4522. for i in range(a:count)
  4523. call s:StageInline('show', line('.'), 1)
  4524. call search(s:file_pattern . '\|^@','W')
  4525. endfor
  4526. return '.'
  4527. endfunction
  4528. function! s:StageDiff(diff) abort
  4529. let lnum = line('.')
  4530. let info = s:StageInfo(lnum)
  4531. let prefix = info.offset > 0 ? '+' . info.offset : ''
  4532. if info.submodule =~# '^S'
  4533. if info.section ==# 'Staged'
  4534. return 'Git --paginate diff --no-ext-diff --submodule=log --cached -- ' . info.paths[0]
  4535. elseif info.submodule =~# '^SC'
  4536. return 'Git --paginate diff --no-ext-diff --submodule=log -- ' . info.paths[0]
  4537. else
  4538. return 'Git --paginate diff --no-ext-diff --submodule=diff -- ' . info.paths[0]
  4539. endif
  4540. elseif empty(info.paths) && info.section ==# 'Staged'
  4541. return 'Git --paginate diff --no-ext-diff --cached'
  4542. elseif empty(info.paths)
  4543. return 'Git --paginate diff --no-ext-diff'
  4544. elseif len(info.paths) > 1
  4545. execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
  4546. return a:diff . '! @:'.s:fnameescape(info.paths[1])
  4547. elseif info.section ==# 'Staged' && info.sigil ==# '-'
  4548. execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
  4549. return a:diff . '! :0:%'
  4550. elseif info.section ==# 'Staged'
  4551. execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
  4552. return a:diff . '! @:%'
  4553. elseif info.sigil ==# '-'
  4554. execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
  4555. return a:diff . '! :(top)%'
  4556. else
  4557. execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
  4558. return a:diff . '!'
  4559. endif
  4560. endfunction
  4561. function! s:StageDiffEdit() abort
  4562. let info = s:StageInfo(line('.'))
  4563. let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
  4564. if info.section ==# 'Staged'
  4565. return 'Git --paginate diff --no-ext-diff --cached '.s:fnameescape(arg)
  4566. elseif info.status ==# '?'
  4567. call s:TreeChomp('add', '--intent-to-add', '--', arg)
  4568. return s:ReloadStatus()
  4569. else
  4570. return 'Git --paginate diff --no-ext-diff '.s:fnameescape(arg)
  4571. endif
  4572. endfunction
  4573. function! s:StageApply(info, reverse, extra) abort
  4574. if a:info.status ==# 'R'
  4575. throw 'fugitive: patching renamed file not yet supported'
  4576. endif
  4577. let cmd = ['apply', '-p0', '--recount'] + a:extra
  4578. let info = a:info
  4579. let start = info.patch
  4580. let end = info.lnum
  4581. let lines = getline(start, end)
  4582. if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
  4583. return -1
  4584. endif
  4585. while getline(end) =~# '^[-+\ ]'
  4586. let end += 1
  4587. if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . '\ ]'
  4588. call add(lines, ' ' . getline(end)[1:-1])
  4589. endif
  4590. endwhile
  4591. while start > 0 && getline(start) !~# '^@'
  4592. let start -= 1
  4593. if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
  4594. call insert(lines, ' ' . getline(start)[1:-1])
  4595. elseif getline(start) =~# '^@'
  4596. call insert(lines, getline(start))
  4597. endif
  4598. endwhile
  4599. if start == 0
  4600. throw 'fugitive: could not find hunk'
  4601. elseif getline(start) !~# '^@@ '
  4602. throw 'fugitive: cannot apply conflict hunk'
  4603. endif
  4604. let i = b:fugitive_expanded[info.section][info.filename][0]
  4605. let head = []
  4606. let diff_lines = fugitive#Wait(b:fugitive_diff[info.section]).stdout
  4607. while get(diff_lines, i, '@') !~# '^@'
  4608. let line = diff_lines[i]
  4609. if line ==# '--- /dev/null'
  4610. call add(head, '--- ' . get(diff_lines, i + 1, '')[4:-1])
  4611. elseif line !~# '^new file '
  4612. call add(head, line)
  4613. endif
  4614. let i += 1
  4615. endwhile
  4616. call extend(lines, head, 'keep')
  4617. let temp = tempname()
  4618. call writefile(lines, temp)
  4619. if a:reverse
  4620. call add(cmd, '--reverse')
  4621. endif
  4622. call extend(cmd, ['--', temp])
  4623. let output = s:ChompStderr(cmd)
  4624. if empty(output)
  4625. return 1
  4626. endif
  4627. call s:throw(output)
  4628. endfunction
  4629. function! s:StageDelete(lnum1, lnum2, count) abort
  4630. let restore = []
  4631. let err = ''
  4632. let did_conflict_err = 0
  4633. let reset_commit = matchstr(getline(a:lnum1), '^Un\w\+ \%(to\| from\) \zs\S\+')
  4634. try
  4635. for info in s:Selection(a:lnum1, a:lnum2)
  4636. if empty(info.paths)
  4637. if len(info.commit)
  4638. let reset_commit = info.commit . '^'
  4639. endif
  4640. continue
  4641. endif
  4642. let sub = get(get(get(b:fugitive_files, info.section, {}), info.filename, {}), 'submodule')
  4643. if sub =~# '^S' && info.status ==# 'M'
  4644. let undo = 'Git checkout ' . fugitive#RevParse('HEAD', FugitiveExtractGitDir(info.paths[0]))[0:10] . ' --'
  4645. elseif sub =~# '^S'
  4646. let err .= '|echoerr ' . string('fugitive: will not touch submodule ' . string(info.relative[0]))
  4647. break
  4648. elseif info.status ==# 'D'
  4649. let undo = 'GRemove'
  4650. elseif info.paths[0] =~# '/$'
  4651. let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
  4652. break
  4653. else
  4654. let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
  4655. endif
  4656. if info.patch
  4657. call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
  4658. elseif sub =~# '^S'
  4659. if info.section ==# 'Staged'
  4660. call s:TreeChomp('reset', '--', info.paths[0])
  4661. endif
  4662. call s:TreeChomp('submodule', 'update', '--', info.paths[0])
  4663. elseif info.status ==# '?'
  4664. call s:TreeChomp('clean', '-f', '--', info.paths[0])
  4665. elseif a:count == 2
  4666. if get(b:fugitive_files['Staged'], info.filename, {'status': ''}).status ==# 'D'
  4667. call delete(info.paths[0])
  4668. else
  4669. call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
  4670. endif
  4671. elseif a:count == 3
  4672. if get(b:fugitive_files['Unstaged'], info.filename, {'status': ''}).status ==# 'D'
  4673. call delete(info.paths[0])
  4674. else
  4675. call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
  4676. endif
  4677. elseif info.status =~# '[ADU]' &&
  4678. \ get(b:fugitive_files[info.section ==# 'Staged' ? 'Unstaged' : 'Staged'], info.filename, {'status': ''}).status =~# '[AU]'
  4679. if get(g:, 'fugitive_conflict_x', 0)
  4680. call s:TreeChomp('checkout', info.section ==# 'Unstaged' ? '--ours' : '--theirs', '--', info.paths[0])
  4681. else
  4682. if !did_conflict_err
  4683. let err .= '|echoerr "Use 2X for --ours or 3X for --theirs"'
  4684. let did_conflict_err = 1
  4685. endif
  4686. continue
  4687. endif
  4688. elseif info.status ==# 'U'
  4689. call delete(info.paths[0])
  4690. elseif info.status ==# 'A'
  4691. call s:TreeChomp('rm', '-f', '--', info.paths[0])
  4692. elseif info.section ==# 'Unstaged'
  4693. call s:TreeChomp('checkout', '--', info.paths[0])
  4694. else
  4695. call s:TreeChomp('checkout', '@', '--', info.paths[0])
  4696. endif
  4697. if len(undo)
  4698. call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
  4699. endif
  4700. endfor
  4701. catch /^fugitive:/
  4702. let err .= '|echoerr ' . string(v:exception)
  4703. endtry
  4704. if empty(restore)
  4705. if len(reset_commit) && empty(err)
  4706. call feedkeys(':Git reset ' . reset_commit)
  4707. endif
  4708. return err[1:-1]
  4709. endif
  4710. exe s:ReloadStatus()
  4711. call s:StageReveal()
  4712. return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
  4713. endfunction
  4714. function! s:StageIgnore(lnum1, lnum2, count) abort
  4715. let paths = []
  4716. for info in s:Selection(a:lnum1, a:lnum2)
  4717. call extend(paths, info.relative)
  4718. endfor
  4719. call map(paths, '"/" . v:val')
  4720. if !a:0
  4721. let dir = fugitive#Find('.git/info/')
  4722. if !isdirectory(dir)
  4723. try
  4724. call mkdir(dir)
  4725. catch
  4726. endtry
  4727. endif
  4728. endif
  4729. exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
  4730. let last = line('$')
  4731. if last == 1 && empty(getline(1))
  4732. call setline(last, paths)
  4733. else
  4734. call append(last, paths)
  4735. exe last + 1
  4736. endif
  4737. return ''
  4738. endfunction
  4739. function! s:DoToggleHeadHeader(value) abort
  4740. exe 'edit' fnameescape(fugitive#Find('.git/'))
  4741. call search('\C^index$', 'wc')
  4742. endfunction
  4743. function! s:DoToggleHelpHeader(value) abort
  4744. exe 'help fugitive-map'
  4745. endfunction
  4746. function! s:DoStagePushHeader(value) abort
  4747. let remote = matchstr(a:value, '\zs[^/]\+\ze/')
  4748. if empty(remote)
  4749. let remote = '.'
  4750. endif
  4751. let branch = matchstr(a:value, '\%([^/]\+/\)\=\zs\S\+')
  4752. call feedkeys(':Git push ' . remote . ' ' . branch)
  4753. endfunction
  4754. function! s:DoTogglePushHeader(value) abort
  4755. return s:DoStagePushHeader(a:value)
  4756. endfunction
  4757. function! s:DoStageUnpushedHeading(heading) abort
  4758. let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
  4759. if empty(remote)
  4760. let remote = '.'
  4761. endif
  4762. let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
  4763. if branch ==# '*'
  4764. return
  4765. endif
  4766. call feedkeys(':Git push ' . remote . ' ' . '@:' . 'refs/heads/' . branch)
  4767. endfunction
  4768. function! s:DoToggleUnpushedHeading(heading) abort
  4769. return s:DoStageUnpushedHeading(a:heading)
  4770. endfunction
  4771. function! s:DoStageUnpushed(record) abort
  4772. let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
  4773. if empty(remote)
  4774. let remote = '.'
  4775. endif
  4776. let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
  4777. if branch ==# '*'
  4778. return
  4779. endif
  4780. call feedkeys(':Git push ' . remote . ' ' . a:record.commit . ':' . 'refs/heads/' . branch)
  4781. endfunction
  4782. function! s:DoToggleUnpushed(record) abort
  4783. return s:DoStageUnpushed(a:record)
  4784. endfunction
  4785. function! s:DoUnstageUnpulledHeading(heading) abort
  4786. call feedkeys(':Git rebase')
  4787. endfunction
  4788. function! s:DoToggleUnpulledHeading(heading) abort
  4789. call s:DoUnstageUnpulledHeading(a:heading)
  4790. endfunction
  4791. function! s:DoUnstageUnpulled(record) abort
  4792. call feedkeys(':Git rebase ' . a:record.commit)
  4793. endfunction
  4794. function! s:DoToggleUnpulled(record) abort
  4795. call s:DoUnstageUnpulled(a:record)
  4796. endfunction
  4797. function! s:DoUnstageUnpushed(record) abort
  4798. call feedkeys(':Git -c sequence.editor=true rebase --interactive --autosquash ' . a:record.commit . '^')
  4799. endfunction
  4800. function! s:DoToggleStagedHeading(...) abort
  4801. call s:TreeChomp('reset', '-q')
  4802. return 1
  4803. endfunction
  4804. function! s:DoUnstageStagedHeading(heading) abort
  4805. return s:DoToggleStagedHeading(a:heading)
  4806. endfunction
  4807. function! s:DoToggleUnstagedHeading(...) abort
  4808. call s:TreeChomp('add', '-u')
  4809. return 1
  4810. endfunction
  4811. function! s:DoStageUnstagedHeading(heading) abort
  4812. return s:DoToggleUnstagedHeading(a:heading)
  4813. endfunction
  4814. function! s:DoToggleUntrackedHeading(...) abort
  4815. call s:TreeChomp('add', '.')
  4816. return 1
  4817. endfunction
  4818. function! s:DoStageUntrackedHeading(heading) abort
  4819. return s:DoToggleUntrackedHeading(a:heading)
  4820. endfunction
  4821. function! s:DoToggleStaged(record) abort
  4822. if a:record.patch
  4823. return s:StageApply(a:record, 1, ['--cached'])
  4824. else
  4825. call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
  4826. return 1
  4827. endif
  4828. endfunction
  4829. function! s:DoUnstageStaged(record) abort
  4830. return s:DoToggleStaged(a:record)
  4831. endfunction
  4832. function! s:DoToggleUnstaged(record) abort
  4833. if a:record.patch
  4834. return s:StageApply(a:record, 0, ['--cached'])
  4835. else
  4836. call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
  4837. return 1
  4838. endif
  4839. endfunction
  4840. function! s:DoStageUnstaged(record) abort
  4841. return s:DoToggleUnstaged(a:record)
  4842. endfunction
  4843. function! s:DoUnstageUnstaged(record) abort
  4844. if a:record.status ==# 'A'
  4845. call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
  4846. return 1
  4847. else
  4848. return -1
  4849. endif
  4850. endfunction
  4851. function! s:DoToggleUntracked(record) abort
  4852. call s:TreeChomp(['add', '--'] + a:record.paths)
  4853. return 1
  4854. endfunction
  4855. function! s:DoStageUntracked(record) abort
  4856. return s:DoToggleUntracked(a:record)
  4857. endfunction
  4858. function! s:StagePatch(lnum1,lnum2) abort
  4859. let add = []
  4860. let reset = []
  4861. let intend = []
  4862. for lnum in range(a:lnum1,a:lnum2)
  4863. let info = s:StageInfo(lnum)
  4864. if empty(info.paths) && info.section ==# 'Staged'
  4865. execute 'tab Git reset --patch'
  4866. break
  4867. elseif empty(info.paths) && info.section ==# 'Unstaged'
  4868. execute 'tab Git add --patch'
  4869. break
  4870. elseif empty(info.paths) && info.section ==# 'Untracked'
  4871. execute 'tab Git add --interactive'
  4872. break
  4873. elseif empty(info.paths)
  4874. continue
  4875. endif
  4876. execute lnum
  4877. if info.section ==# 'Staged'
  4878. let reset += info.relative
  4879. elseif info.section ==# 'Untracked'
  4880. let intend += info.paths
  4881. elseif info.status !~# '^D'
  4882. let add += info.relative
  4883. endif
  4884. endfor
  4885. try
  4886. if !empty(intend)
  4887. call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
  4888. endif
  4889. if !empty(add)
  4890. execute "tab Git add --patch -- ".join(map(add,'fnameescape(v:val)'))
  4891. endif
  4892. if !empty(reset)
  4893. execute "tab Git reset --patch -- ".join(map(reset,'fnameescape(v:val)'))
  4894. endif
  4895. catch /^fugitive:/
  4896. return 'echoerr ' . string(v:exception)
  4897. endtry
  4898. return s:ReloadStatus()
  4899. endfunction
  4900. " Section: :Git commit, :Git revert
  4901. function! s:CommitInteractive(line1, line2, range, bang, mods, options, patch) abort
  4902. let status = s:StatusCommand(a:line1, a:line2, a:range, get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2, a:bang, a:mods, '', '', [], a:options)
  4903. let status = len(status) ? status . '|' : ''
  4904. if a:patch
  4905. return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
  4906. else
  4907. return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
  4908. endif
  4909. endfunction
  4910. function! s:CommitSubcommand(line1, line2, range, bang, mods, options) abort
  4911. let argv = copy(a:options.subcommand_args)
  4912. let i = 0
  4913. while get(argv, i, '--') !=# '--'
  4914. if argv[i] =~# '^-[apzsneiovq].'
  4915. call insert(argv, argv[i][0:1])
  4916. let argv[i+1] = '-' . argv[i+1][2:-1]
  4917. else
  4918. let i += 1
  4919. endif
  4920. endwhile
  4921. if s:HasOpt(argv, '-i', '--interactive')
  4922. return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 0)
  4923. elseif s:HasOpt(argv, '-p', '--patch')
  4924. return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 1)
  4925. else
  4926. return {}
  4927. endif
  4928. endfunction
  4929. function! s:RevertSubcommand(line1, line2, range, bang, mods, options) abort
  4930. return {'insert_args': ['--edit']}
  4931. endfunction
  4932. function! fugitive#CommitComplete(A, L, P, ...) abort
  4933. let dir = a:0 ? a:1 : s:Dir()
  4934. if a:A =~# '^--fixup=\|^--squash='
  4935. let commits = s:LinesError([dir, 'log', '--pretty=format:%s', '@{upstream}..'])[0]
  4936. let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
  4937. if pre =~# "'"
  4938. call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
  4939. call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
  4940. return commits
  4941. else
  4942. return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
  4943. endif
  4944. else
  4945. return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'), a:000)
  4946. endif
  4947. return []
  4948. endfunction
  4949. function! fugitive#RevertComplete(A, L, P, ...) abort
  4950. return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
  4951. endfunction
  4952. " Section: :Git merge, :Git rebase, :Git pull
  4953. function! fugitive#MergeComplete(A, L, P, ...) abort
  4954. return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
  4955. endfunction
  4956. function! fugitive#RebaseComplete(A, L, P, ...) abort
  4957. return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
  4958. endfunction
  4959. function! fugitive#PullComplete(A, L, P, ...) abort
  4960. return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
  4961. endfunction
  4962. function! s:MergeSubcommand(line1, line2, range, bang, mods, options) abort
  4963. if empty(a:options.subcommand_args) && (
  4964. \ filereadable(fugitive#Find('.git/MERGE_MSG', a:options)) ||
  4965. \ isdirectory(fugitive#Find('.git/rebase-apply', a:options)) ||
  4966. \ !empty(s:TreeChomp([a:options.git_dir, 'diff-files', '--diff-filter=U'])))
  4967. return 'echoerr ":Git merge for loading conflicts has been removed in favor of :Git mergetool"'
  4968. endif
  4969. return {}
  4970. endfunction
  4971. function! s:RebaseSubcommand(line1, line2, range, bang, mods, options) abort
  4972. let args = a:options.subcommand_args
  4973. if s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '-i', '--interactive')
  4974. return {'env': {'GIT_SEQUENCE_EDITOR': 'true'}, 'insert_args': ['--interactive']}
  4975. endif
  4976. return {}
  4977. endfunction
  4978. " Section: :Git bisect
  4979. function! s:CompleteBisect(A, L, P, ...) abort
  4980. let bisect_subcmd = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
  4981. if empty(bisect_subcmd)
  4982. let subcmds = ['start', 'bad', 'new', 'good', 'old', 'terms', 'skip', 'next', 'reset', 'replay', 'log', 'run']
  4983. return s:FilterEscape(subcmds, a:A)
  4984. endif
  4985. let dir = a:0 ? a:1 : s:Dir()
  4986. return fugitive#CompleteObject(a:A, dir)
  4987. endfunction
  4988. function! fugitive#BisectComplete(A, L, P, ...) abort
  4989. return s:CompleteSub('bisect', a:A, a:L, a:P, function('s:CompleteBisect'), a:000)
  4990. endfunction
  4991. " Section: :Git difftool, :Git mergetool
  4992. function! s:ToolItems(state, from, to, offsets, text, ...) abort
  4993. let items = []
  4994. for i in range(len(a:state.diff))
  4995. let diff = a:state.diff[i]
  4996. let path = (i == len(a:state.diff) - 1) ? a:to : a:from
  4997. if empty(path)
  4998. return []
  4999. endif
  5000. let item = {
  5001. \ 'valid': a:0 ? a:1 : 1,
  5002. \ 'filename': diff.filename . s:VimSlash(path),
  5003. \ 'lnum': matchstr(get(a:offsets, i), '\d\+'),
  5004. \ 'text': a:text}
  5005. if len(get(diff, 'module', ''))
  5006. let item.module = diff.module . path
  5007. endif
  5008. call add(items, item)
  5009. endfor
  5010. let items[-1].context = {'diff': items[0:-2]}
  5011. return [items[-1]]
  5012. endfunction
  5013. function! s:ToolToFrom(str) abort
  5014. if a:str =~# ' => '
  5015. let str = a:str =~# '{.* => .*}' ? a:str : '{' . a:str . '}'
  5016. return [substitute(str, '{.* => \(.*\)}', '\1', ''),
  5017. \ substitute(str, '{\(.*\) => .*}', '\1', '')]
  5018. else
  5019. return [a:str, a:str]
  5020. endif
  5021. endfunction
  5022. function! s:ToolParse(state, line) abort
  5023. if type(a:line) !=# type('') || a:state.mode ==# 'hunk' && a:line =~# '^[ +-]'
  5024. return []
  5025. elseif a:line =~# '^diff '
  5026. let a:state.mode = 'diffhead'
  5027. let a:state.from = ''
  5028. let a:state.to = ''
  5029. elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- [^/]'
  5030. let a:state.from = a:line[4:-1]
  5031. let a:state.to = a:state.from
  5032. elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ [^/]'
  5033. let a:state.to = a:line[4:-1]
  5034. if empty(get(a:state, 'from', ''))
  5035. let a:state.from = a:state.to
  5036. endif
  5037. elseif a:line[0] ==# '@'
  5038. let a:state.mode = 'hunk'
  5039. if has_key(a:state, 'from')
  5040. let offsets = split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' ')
  5041. return s:ToolItems(a:state, a:state.from, a:state.to, offsets, matchstr(a:line, ' @@\+ \zs.*'))
  5042. endif
  5043. elseif a:line =~# '^\* Unmerged path .'
  5044. let file = a:line[16:-1]
  5045. return s:ToolItems(a:state, file, file, [], '')
  5046. elseif a:line =~# '^[A-Z]\d*\t.\|^:.*\t.'
  5047. " --raw, --name-status
  5048. let [status; files] = split(a:line, "\t")
  5049. return s:ToolItems(a:state, files[0], files[-1], [], a:state.name_only ? '' : status)
  5050. elseif a:line =~# '^ \S.* |'
  5051. " --stat
  5052. let [_, to, changes; __] = matchlist(a:line, '^ \(.\{-\}\) \+|\zs \(.*\)$')
  5053. let [to, from] = s:ToolToFrom(to)
  5054. return s:ToolItems(a:state, from, to, [], changes)
  5055. elseif a:line =~# '^ *\([0-9.]\+%\) .'
  5056. " --dirstat
  5057. let [_, changes, to; __] = matchlist(a:line, '^ *\([0-9.]\+%\) \(.*\)')
  5058. return s:ToolItems(a:state, to, to, [], changes)
  5059. elseif a:line =~# '^\(\d\+\|-\)\t\(\d\+\|-\)\t.'
  5060. " --numstat
  5061. let [_, add, remove, to; __] = matchlist(a:line, '^\(\d\+\|-\)\t\(\d\+\|-\)\t\(.*\)')
  5062. let [to, from] = s:ToolToFrom(to)
  5063. return s:ToolItems(a:state, from, to, [], add ==# '-' ? 'Binary file' : '+' . add . ' -' . remove, add !=# '-')
  5064. elseif a:state.mode !=# 'diffhead' && a:state.mode !=# 'hunk' && len(a:line) || a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
  5065. return [{'text': a:line}]
  5066. endif
  5067. return []
  5068. endfunction
  5069. function! s:ToolStream(line1, line2, range, bang, mods, options, args, state) abort
  5070. let i = 0
  5071. let argv = copy(a:args)
  5072. let prompt = 1
  5073. let state = a:state
  5074. while i < len(argv)
  5075. let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
  5076. if len(match) && len(match[2])
  5077. call insert(argv, match[1])
  5078. let argv[i+1] = '-' . match[2]
  5079. continue
  5080. endif
  5081. let arg = argv[i]
  5082. if arg =~# '^-t$\|^--tool=\|^--tool-help$\|^--help$'
  5083. return {}
  5084. elseif arg =~# '^-y$\|^--no-prompt$'
  5085. let prompt = 0
  5086. call remove(argv, i)
  5087. continue
  5088. elseif arg ==# '--prompt'
  5089. let prompt = 1
  5090. call remove(argv, i)
  5091. continue
  5092. elseif arg =~# '^--\%(no-\)\=\(symlinks\|trust-exit-code\|gui\)$'
  5093. call remove(argv, i)
  5094. continue
  5095. elseif arg ==# '--'
  5096. break
  5097. endif
  5098. let i += 1
  5099. endwhile
  5100. call fugitive#Autowrite()
  5101. let a:state.mode = 'init'
  5102. let a:state.from = ''
  5103. let a:state.to = ''
  5104. let exec = s:UserCommandList({'git': a:options.git, 'git_dir': a:options.git_dir}) + ['-c', 'diff.context=0']
  5105. let exec += a:options.flags + ['--no-pager', 'diff', '--no-ext-diff', '--no-color', '--no-prefix'] + argv
  5106. if prompt
  5107. let title = ':Git ' . s:fnameescape(a:options.flags + [a:options.subcommand] + a:options.subcommand_args)
  5108. return s:QuickfixStream(get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2, 'difftool', title, exec, !a:bang, a:mods, s:function('s:ToolParse'), a:state)
  5109. else
  5110. let filename = ''
  5111. let cmd = []
  5112. let tabnr = tabpagenr() + 1
  5113. for line in s:SystemList(exec)[0]
  5114. for item in s:ToolParse(a:state, line)
  5115. if len(get(item, 'filename', '')) && item.filename != filename
  5116. call add(cmd, 'tabedit ' . s:fnameescape(item.filename))
  5117. for i in reverse(range(len(get(item.context, 'diff', []))))
  5118. call add(cmd, (i ? 'rightbelow' : 'leftabove') . ' vertical Gdiffsplit! ' . s:fnameescape(item.context.diff[i].filename))
  5119. endfor
  5120. call add(cmd, 'wincmd =')
  5121. let filename = item.filename
  5122. endif
  5123. endfor
  5124. endfor
  5125. return join(cmd, '|') . (empty(cmd) ? '' : '|' . tabnr . 'tabnext')
  5126. endif
  5127. endfunction
  5128. function! s:MergetoolSubcommand(line1, line2, range, bang, mods, options) abort
  5129. let dir = a:options.git_dir
  5130. exe s:DirCheck(dir)
  5131. let i = 0
  5132. let prompt = 1
  5133. let cmd = ['diff', '--diff-filter=U']
  5134. let state = {'name_only': 0}
  5135. let state.diff = [{'prefix': ':2:', 'module': ':2:'}, {'prefix': ':3:', 'module': ':3:'}, {'prefix': ':(top)'}]
  5136. call map(state.diff, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
  5137. return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, ['--diff-filter=U'] + a:options.subcommand_args, state)
  5138. endfunction
  5139. function! s:DifftoolSubcommand(line1, line2, range, bang, mods, options) abort
  5140. let dir = s:Dir(a:options)
  5141. exe s:DirCheck(dir)
  5142. let i = 0
  5143. let argv = copy(a:options.subcommand_args)
  5144. let commits = []
  5145. let cached = 0
  5146. let reverse = 1
  5147. let prompt = 1
  5148. let state = {'name_only': 0}
  5149. let merge_base_against = {}
  5150. let dash = (index(argv, '--') > i ? ['--'] : [])
  5151. while i < len(argv)
  5152. let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
  5153. if len(match) && len(match[2])
  5154. call insert(argv, match[1])
  5155. let argv[i+1] = '-' . match[2]
  5156. continue
  5157. endif
  5158. let arg = argv[i]
  5159. if arg ==# '--cached'
  5160. let cached = 1
  5161. elseif arg ==# '-R'
  5162. let reverse = 1
  5163. elseif arg ==# '--name-only'
  5164. let state.name_only = 1
  5165. let argv[0] = '--name-status'
  5166. elseif arg ==# '--'
  5167. break
  5168. elseif arg !~# '^-\|^\.\.\=\%(/\|$\)'
  5169. let parsed = s:LinesError(['rev-parse', '--revs-only', substitute(arg, ':.*', '', '')] + dash)[0]
  5170. call map(parsed, '{"uninteresting": v:val =~# "^\\^", "prefix": substitute(v:val, "^\\^", "", "") . ":"}')
  5171. let merge_base_against = {}
  5172. if arg =~# '\.\.\.' && len(parsed) > 2
  5173. let display = map(split(arg, '\.\.\.', 1), 'empty(v:val) ? "@" : v:val')
  5174. if len(display) == 2
  5175. let parsed[0].module = display[1] . ':'
  5176. let parsed[1].module = display[0] . ':'
  5177. endif
  5178. let parsed[2].module = arg . ':'
  5179. if empty(commits)
  5180. let merge_base_against = parsed[0]
  5181. let parsed = [parsed[2]]
  5182. endif
  5183. elseif arg =~# '\.\.' && len(parsed) == 2
  5184. let display = map(split(arg, '\.\.', 1), 'empty(v:val) ? "@" : v:val')
  5185. if len(display) == 2
  5186. let parsed[0].module = display[0] . ':'
  5187. let parsed[1].module = display[1] . ':'
  5188. endif
  5189. elseif len(parsed) == 1
  5190. let parsed[0].module = arg . ':'
  5191. endif
  5192. call extend(commits, parsed)
  5193. endif
  5194. let i += 1
  5195. endwhile
  5196. if len(merge_base_against)
  5197. call add(commits, merge_base_against)
  5198. endif
  5199. let commits = filter(copy(commits), 'v:val.uninteresting') + filter(commits, '!v:val.uninteresting')
  5200. if cached
  5201. if empty(commits)
  5202. call add(commits, {'prefix': '@:', 'module': '@:'})
  5203. endif
  5204. call add(commits, {'prefix': ':0:', 'module': ':0:'})
  5205. elseif len(commits) < 2
  5206. call add(commits, {'prefix': ':(top)'})
  5207. if len(commits) < 2
  5208. call insert(commits, {'prefix': ':0:', 'module': ':0:'})
  5209. endif
  5210. endif
  5211. if reverse
  5212. let commits = [commits[-1]] + repeat([commits[0]], len(commits) - 1)
  5213. call reverse(commits)
  5214. endif
  5215. if len(commits) > 2
  5216. call add(commits, remove(commits, 0))
  5217. endif
  5218. call map(commits, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
  5219. let state.diff = commits
  5220. return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, argv, state)
  5221. endfunction
  5222. " Section: :Ggrep, :Glog
  5223. if !exists('g:fugitive_summary_format')
  5224. let g:fugitive_summary_format = '%s'
  5225. endif
  5226. function! fugitive#GrepComplete(A, L, P) abort
  5227. return s:CompleteSub('grep', a:A, a:L, a:P)
  5228. endfunction
  5229. function! fugitive#LogComplete(A, L, P) abort
  5230. return s:CompleteSub('log', a:A, a:L, a:P)
  5231. endfunction
  5232. function! s:GrepParseLine(options, quiet, dir, line) abort
  5233. if !a:quiet
  5234. echo a:line
  5235. endif
  5236. let entry = {'valid': 1}
  5237. let match = matchlist(a:line, '^\(.\{-\}\):\([1-9]\d*\):\([1-9]\d*:\)\=\(.*\)$')
  5238. if a:line =~# '^git: \|^usage: \|^error: \|^fatal: \|^BUG: '
  5239. return {'text': a:line}
  5240. elseif len(match)
  5241. let entry.module = match[1]
  5242. let entry.lnum = +match[2]
  5243. let entry.col = +match[3]
  5244. let entry.text = match[4]
  5245. else
  5246. let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
  5247. if len(entry.module)
  5248. let entry.text = 'Binary file'
  5249. let entry.valid = 0
  5250. endif
  5251. endif
  5252. if empty(entry.module) && !a:options.line_number
  5253. let match = matchlist(a:line, '^\(.\{-\}\):\(.*\)$')
  5254. if len(match)
  5255. let entry.module = match[1]
  5256. let entry.pattern = '\M^' . escape(match[2], '\.^$/') . '$'
  5257. endif
  5258. endif
  5259. if empty(entry.module) && a:options.name_count && a:line =~# ':\d\+$'
  5260. let entry.text = matchstr(a:line, '\d\+$')
  5261. let entry.module = strpart(a:line, 0, len(a:line) - len(entry.text) - 1)
  5262. endif
  5263. if empty(entry.module) && a:options.name_only
  5264. let entry.module = a:line
  5265. endif
  5266. if empty(entry.module)
  5267. return {'text': a:line}
  5268. endif
  5269. if entry.module !~# ':'
  5270. let entry.filename = s:PathJoin(a:options.prefix, entry.module)
  5271. else
  5272. let entry.filename = fugitive#Find(entry.module, a:dir)
  5273. endif
  5274. return entry
  5275. endfunction
  5276. let s:grep_combine_flags = '[aiIrhHEGPFnlLzocpWq]\{-\}'
  5277. function! s:GrepOptions(args, dir) abort
  5278. let options = {'name_only': 0, 'name_count': 0, 'line_number': 0}
  5279. let tree = s:Tree(a:dir)
  5280. let prefix = empty(tree) ? fugitive#Find(':0:', a:dir) :
  5281. \ s:cpath(getcwd(), tree) ? '' : s:VimSlash(tree . '/')
  5282. let options.prefix = prefix
  5283. for arg in a:args
  5284. if arg ==# '--'
  5285. break
  5286. endif
  5287. if arg =~# '^\%(-' . s:grep_combine_flags . 'c\|--count\)$'
  5288. let options.name_count = 1
  5289. endif
  5290. if arg =~# '^\%(-' . s:grep_combine_flags . 'n\|--line-number\)$'
  5291. let options.line_number = 1
  5292. elseif arg =~# '^\%(--no-line-number\)$'
  5293. let options.line_number = 0
  5294. endif
  5295. if arg =~# '^\%(-' . s:grep_combine_flags . '[lL]\|--files-with-matches\|--name-only\|--files-without-match\)$'
  5296. let options.name_only = 1
  5297. endif
  5298. if arg ==# '--cached'
  5299. let options.prefix = fugitive#Find(':0:', a:dir)
  5300. elseif arg ==# '--no-cached'
  5301. let options.prefix = prefix
  5302. endif
  5303. endfor
  5304. return options
  5305. endfunction
  5306. function! s:GrepCfile(result) abort
  5307. let options = s:GrepOptions(a:result.args, a:result)
  5308. let entry = s:GrepParseLine(options, 1, a:result, getline('.'))
  5309. if get(entry, 'col')
  5310. return [entry.filename, entry.lnum, "norm!" . entry.col . "|"]
  5311. elseif has_key(entry, 'lnum')
  5312. return [entry.filename, entry.lnum]
  5313. elseif has_key(entry, 'pattern')
  5314. return [entry.filename, '', 'silent /' . entry.pattern]
  5315. elseif has_key(entry, 'filename')
  5316. return [entry.filename]
  5317. else
  5318. return []
  5319. endif
  5320. endfunction
  5321. function! s:GrepSubcommand(line1, line2, range, bang, mods, options) abort
  5322. let args = copy(a:options.subcommand_args)
  5323. let handle = -1
  5324. let quiet = 0
  5325. let i = 0
  5326. while i < len(args) && args[i] !=# '--'
  5327. let partition = matchstr(args[i], '^-' . s:grep_combine_flags . '\ze[qzO]')
  5328. if len(partition) > 1
  5329. call insert(args, '-' . strpart(args[i], len(partition)), i+1)
  5330. let args[i] = partition
  5331. elseif args[i] =~# '^\%(-' . s:grep_combine_flags . '[eABC]\|--max-depth\|--context\|--after-context\|--before-context\|--threads\)$'
  5332. let i += 1
  5333. elseif args[i] =~# '^\%(-O\|--open-files-in-pager\)$'
  5334. let handle = 1
  5335. call remove(args, i)
  5336. continue
  5337. elseif args[i] =~# '^\%(-O\|--open-files-in-pager=\)'
  5338. let handle = 0
  5339. elseif args[i] =~# '^-[qz].'
  5340. let args[i] = '-' . args[i][2:-1]
  5341. let quiet = 1
  5342. elseif args[i] =~# '^\%(-[qz]\|--quiet\)$'
  5343. let quiet = 1
  5344. call remove(args, i)
  5345. continue
  5346. elseif args[i] =~# '^--no-quiet$'
  5347. let quiet = 0
  5348. elseif args[i] =~# '^\%(--heading\)$'
  5349. call remove(args, i)
  5350. continue
  5351. endif
  5352. let i += 1
  5353. endwhile
  5354. if handle < 0 ? !quiet : !handle
  5355. return {}
  5356. endif
  5357. call fugitive#Autowrite()
  5358. let listnr = get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2
  5359. if s:HasOpt(args, '--no-line-number')
  5360. let lc = []
  5361. else
  5362. let lc = fugitive#GitVersion(2, 19) ? ['-n', '--column'] : ['-n']
  5363. endif
  5364. let cmd = ['grep', '--no-color', '--full-name'] + lc
  5365. let dir = s:Dir(a:options)
  5366. let options = s:GrepOptions(lc + args, dir)
  5367. if listnr > 0
  5368. exe listnr 'wincmd w'
  5369. else
  5370. call s:BlurStatus()
  5371. endif
  5372. let title = (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)
  5373. call s:QuickfixCreate(listnr, {'title': title})
  5374. let tempfile = tempname()
  5375. let state = {
  5376. \ 'git': a:options.git,
  5377. \ 'flags': a:options.flags,
  5378. \ 'args': cmd + args,
  5379. \ 'git_dir': s:GitDir(a:options),
  5380. \ 'cwd': s:UserCommandCwd(a:options),
  5381. \ 'filetype': 'git',
  5382. \ 'mods': s:Mods(a:mods),
  5383. \ 'file': s:Resolve(tempfile)}
  5384. let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
  5385. exe s:DoAutocmd('QuickFixCmdPre ' . event)
  5386. try
  5387. if !quiet && &more
  5388. let more = 1
  5389. set nomore
  5390. endif
  5391. if !quiet
  5392. echo title
  5393. endif
  5394. let list = s:SystemList(s:UserCommandList(a:options) + cmd + args)[0]
  5395. call writefile(list + [''], tempfile, 'b')
  5396. call s:RunSave(state)
  5397. call map(list, 's:GrepParseLine(options, ' . quiet . ', dir, v:val)')
  5398. call s:QuickfixSet(listnr, list, 'a')
  5399. let press_enter_shortfall = &cmdheight - len(list)
  5400. if press_enter_shortfall > 0 && !quiet
  5401. echo repeat("\n", press_enter_shortfall - 1)
  5402. endif
  5403. finally
  5404. if exists('l:more')
  5405. let &more = more
  5406. endif
  5407. endtry
  5408. call s:RunFinished(state)
  5409. exe s:DoAutocmd('QuickFixCmdPost ' . event)
  5410. if quiet
  5411. let bufnr = bufnr('')
  5412. exe s:QuickfixOpen(listnr, a:mods)
  5413. if bufnr != bufnr('') && !a:bang
  5414. wincmd p
  5415. endif
  5416. end
  5417. if !a:bang && !empty(list)
  5418. return 'silent ' . (listnr < 0 ? 'c' : 'l').'first'
  5419. else
  5420. return ''
  5421. endif
  5422. endfunction
  5423. function! fugitive#GrepCommand(line1, line2, range, bang, mods, arg) abort
  5424. return fugitive#Command(a:line1, a:line2, a:range, a:bang, a:mods,
  5425. \ "grep -O " . a:arg)
  5426. endfunction
  5427. let s:log_diff_context = '{"filename": fugitive#Find(v:val . from, a:dir), "lnum": get(offsets, v:key), "module": strpart(v:val, 0, len(a:state.base_module)) . from}'
  5428. function! s:LogFlushQueue(state, dir) abort
  5429. let queue = remove(a:state, 'queue')
  5430. if a:state.child_found && get(a:state, 'ignore_commit')
  5431. call remove(queue, 0)
  5432. elseif len(queue) && len(a:state.target) && len(get(a:state, 'parents', []))
  5433. let from = substitute(a:state.target, '^/', ':', '')
  5434. let offsets = []
  5435. let queue[0].context.diff = map(copy(a:state.parents), s:log_diff_context)
  5436. endif
  5437. if len(queue) && queue[-1] ==# {'text': ''}
  5438. call remove(queue, -1)
  5439. endif
  5440. return queue
  5441. endfunction
  5442. function! s:LogParse(state, dir, prefix, line) abort
  5443. if a:state.mode ==# 'hunk' && a:line =~# '^[-+ ]'
  5444. return []
  5445. endif
  5446. let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
  5447. if len(list)
  5448. let queue = s:LogFlushQueue(a:state, a:dir)
  5449. let a:state.mode = 'commit'
  5450. let a:state.base = a:prefix . list[2]
  5451. if len(list[1])
  5452. let [a:state.base_module; a:state.parents] = split(list[1], ' ')
  5453. else
  5454. let a:state.base_module = list[2]
  5455. let a:state.parents = []
  5456. endif
  5457. let a:state.message = list[3]
  5458. let a:state.from = ''
  5459. let a:state.to = ''
  5460. let context = {}
  5461. let a:state.queue = [{
  5462. \ 'valid': 1,
  5463. \ 'context': context,
  5464. \ 'filename': s:PathJoin(a:state.base, a:state.target),
  5465. \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
  5466. \ 'text': a:state.message}]
  5467. let a:state.child_found = 0
  5468. return queue
  5469. elseif type(a:line) == type(0)
  5470. return s:LogFlushQueue(a:state, a:dir)
  5471. elseif a:line =~# '^diff'
  5472. let a:state.mode = 'diffhead'
  5473. let a:state.from = ''
  5474. let a:state.to = ''
  5475. elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- \w/'
  5476. let a:state.from = a:line[6:-1]
  5477. let a:state.to = a:state.from
  5478. elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ \w/'
  5479. let a:state.to = a:line[6:-1]
  5480. if empty(get(a:state, 'from', ''))
  5481. let a:state.from = a:state.to
  5482. endif
  5483. elseif a:line =~# '^@@[^@]*+\d' && len(get(a:state, 'to', '')) && has_key(a:state, 'base')
  5484. let a:state.mode = 'hunk'
  5485. if empty(a:state.target) || a:state.target ==# '/' . a:state.to
  5486. if !a:state.child_found && len(a:state.queue) && a:state.queue[-1] ==# {'text': ''}
  5487. call remove(a:state.queue, -1)
  5488. endif
  5489. let a:state.child_found = 1
  5490. let offsets = map(split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' '), '+matchstr(v:val, "\\d\\+")')
  5491. let context = {}
  5492. if len(a:state.parents)
  5493. let from = ":" . a:state.from
  5494. let context.diff = map(copy(a:state.parents), s:log_diff_context)
  5495. endif
  5496. call add(a:state.queue, {
  5497. \ 'valid': 1,
  5498. \ 'context': context,
  5499. \ 'filename': s:VimSlash(a:state.base . '/' . a:state.to),
  5500. \ 'module': a:state.base_module . ':' . a:state.to,
  5501. \ 'lnum': offsets[-1],
  5502. \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
  5503. endif
  5504. elseif a:state.follow &&
  5505. \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
  5506. let rename = matchstr(a:line, '^ \%(copy\|rename\) \zs.* => .*\ze (\d\+%)$')
  5507. if len(rename)
  5508. let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
  5509. if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
  5510. let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
  5511. endif
  5512. endif
  5513. if !get(a:state, 'ignore_summary')
  5514. call add(a:state.queue, {'text': a:line})
  5515. endif
  5516. elseif a:state.mode ==# 'commit' || a:state.mode ==# 'init'
  5517. call add(a:state.queue, {'text': a:line})
  5518. endif
  5519. return []
  5520. endfunction
  5521. function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
  5522. let dir = s:Dir()
  5523. exe s:DirCheck(dir)
  5524. let listnr = a:type =~# '^l' ? 0 : -1
  5525. let [args, after] = s:SplitExpandChain('log ' . a:args, s:Tree(dir))
  5526. call remove(args, 0)
  5527. let split = index(args, '--')
  5528. if split > 0
  5529. let paths = args[split : -1]
  5530. let args = args[0 : split - 1]
  5531. elseif split == 0
  5532. let paths = args
  5533. let args = []
  5534. else
  5535. let paths = []
  5536. endif
  5537. if a:line1 == 0 && a:count
  5538. let path = fugitive#Path(bufname(a:count), '/', dir)
  5539. let titlepre = ':0,' . a:count
  5540. elseif a:count >= 0
  5541. let path = fugitive#Path(@%, '/', dir)
  5542. let titlepre = a:count == 0 ? ':0,' . bufnr('') : ':'
  5543. else
  5544. let titlepre = ':'
  5545. let path = ''
  5546. endif
  5547. let range = ''
  5548. let extra_args = []
  5549. let extra_paths = []
  5550. let state = {'mode': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
  5551. if path =~# '^/\.git\%(/\|$\)\|^$'
  5552. let path = ''
  5553. elseif a:line1 == 0
  5554. let range = "0," . (a:count ? a:count : bufnr(''))
  5555. let extra_paths = ['.' . path]
  5556. if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
  5557. let state.follow = 1
  5558. if !s:HasOpt(args, '--follow')
  5559. call insert(extra_args, '--follow')
  5560. endif
  5561. if !s:HasOpt(args, '--summary')
  5562. call insert(extra_args, '--summary')
  5563. let state.ignore_summary = 1
  5564. endif
  5565. endif
  5566. let state.ignore_commit = 1
  5567. elseif a:count > 0
  5568. if !s:HasOpt(args, '--merges', '--no-merges')
  5569. call insert(extra_args, '--no-merges')
  5570. endif
  5571. call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
  5572. let state.ignore_commit = 1
  5573. endif
  5574. if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
  5575. let owner = s:Owner(@%, dir)
  5576. if len(owner)
  5577. call add(args, owner . (owner =~# '^\x\{40,}' ? '' : '^{}'))
  5578. endif
  5579. endif
  5580. if empty(extra_paths)
  5581. let path = ''
  5582. endif
  5583. if s:HasOpt(args, '-g', '--walk-reflogs')
  5584. let format = "%gd %P\t%H %gs"
  5585. else
  5586. let format = "%h %P\t%H " . g:fugitive_summary_format
  5587. endif
  5588. let cmd = ['--no-pager']
  5589. call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'] +
  5590. \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
  5591. \ args + extra_args + paths + extra_paths)
  5592. let state.target = path
  5593. let title = titlepre . (listnr < 0 ? 'Gclog ' : 'Gllog ') . s:fnameescape(args + paths)
  5594. return s:QuickfixStream(listnr, 'log', title, s:UserCommandList(dir) + cmd, !a:bang, a:mods, s:function('s:LogParse'), state, dir, s:DirUrlPrefix(dir)) . after
  5595. endfunction
  5596. " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
  5597. function! s:UsableWin(nr) abort
  5598. return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
  5599. \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
  5600. \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
  5601. \ index(['nofile','help','quickfix', 'terminal'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
  5602. endfunction
  5603. function! s:ArgSplit(string) abort
  5604. let string = a:string
  5605. let args = []
  5606. while string =~# '\S'
  5607. let arg = matchstr(string, '^\s*\%(\\.\|\S\)\+')
  5608. let string = strpart(string, len(arg))
  5609. let arg = substitute(arg, '^\s\+', '', '')
  5610. call add(args, substitute(arg, '\\\+[|" ]', '\=submatch(0)[len(submatch(0))/2 : -1]', 'g'))
  5611. endwhile
  5612. return args
  5613. endfunction
  5614. function! s:PlusEscape(string) abort
  5615. return substitute(a:string, '\\*[|" ]', '\=repeat("\\", len(submatch(0))).submatch(0)', 'g')
  5616. endfunction
  5617. function! s:OpenParse(string, wants_cmd, wants_multiple) abort
  5618. let opts = []
  5619. let cmds = []
  5620. let args = s:ArgSplit(a:string)
  5621. while !empty(args)
  5622. if args[0] =~# '^++'
  5623. call add(opts, ' ' . s:PlusEscape(remove(args, 0)))
  5624. elseif a:wants_cmd && args[0] ==# '+'
  5625. call remove(args, 0)
  5626. call add(cmds, '$')
  5627. elseif a:wants_cmd && args[0] =~# '^+'
  5628. call add(cmds, remove(args, 0)[1:-1])
  5629. else
  5630. break
  5631. endif
  5632. endwhile
  5633. if !a:wants_multiple && empty(args)
  5634. let args = ['>:']
  5635. endif
  5636. let dir = s:Dir()
  5637. let wants_cmd = a:wants_cmd
  5638. let urls = []
  5639. for arg in args
  5640. let [url, lnum] = s:OpenExpand(dir, arg, wants_cmd)
  5641. if lnum
  5642. call insert(cmds, lnum)
  5643. endif
  5644. call add(urls, url)
  5645. let wants_cmd = 0
  5646. endfor
  5647. let pre = join(opts, '')
  5648. if len(cmds) > 1
  5649. let pre .= ' +' . s:PlusEscape(join(map(cmds, '"exe ".string(v:val)'), '|'))
  5650. elseif len(cmds)
  5651. let pre .= ' +' . s:PlusEscape(cmds[0])
  5652. endif
  5653. return [a:wants_multiple ? urls : urls[0], pre]
  5654. endfunction
  5655. function! s:OpenExpand(dir, file, wants_cmd) abort
  5656. if a:file ==# '-'
  5657. let result = fugitive#Result()
  5658. if has_key(result, 'file')
  5659. let efile = result.file
  5660. else
  5661. throw 'fugitive: no previous command output'
  5662. endif
  5663. else
  5664. let efile = s:Expand(a:file)
  5665. endif
  5666. let url = s:Generate(efile, a:dir)
  5667. if a:wants_cmd && a:file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
  5668. let line = line('.')
  5669. if s:Slash(expand('%:p')) !=# s:Slash(url)
  5670. let diffcmd = 'diff'
  5671. let from = s:DirRev(@%)[1]
  5672. let to = s:DirRev(url)[1]
  5673. if empty(from) && empty(to)
  5674. let diffcmd = 'diff-files'
  5675. let args = ['--', expand('%:p'), url]
  5676. elseif empty(to)
  5677. let args = [from, '--', url]
  5678. elseif empty(from)
  5679. let args = [to, '--', expand('%:p')]
  5680. let reverse = 1
  5681. else
  5682. let args = [from, to]
  5683. endif
  5684. let [res, exec_error] = s:LinesError([a:dir, diffcmd, '-U0'] + args)
  5685. if !exec_error
  5686. call filter(res, 'v:val =~# "^@@ "')
  5687. call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
  5688. call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
  5689. if exists('reverse')
  5690. call map(res, 'v:val[2:3] + v:val[0:1]')
  5691. endif
  5692. call filter(res, 'v:val[0] < '.line('.'))
  5693. let hunk = get(res, -1, [0,0,0,0])
  5694. if hunk[0] + hunk[1] > line('.')
  5695. let line = hunk[2] + max([1 - hunk[3], 0])
  5696. else
  5697. let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
  5698. endif
  5699. endif
  5700. endif
  5701. return [url, line]
  5702. endif
  5703. return [url, 0]
  5704. endfunction
  5705. function! fugitive#DiffClose() abort
  5706. let mywinnr = winnr()
  5707. for winnr in [winnr('#')] + range(winnr('$'),1,-1)
  5708. if winnr != mywinnr && getwinvar(winnr,'&diff')
  5709. execute winnr.'wincmd w'
  5710. close
  5711. if winnr('$') > 1
  5712. wincmd p
  5713. endif
  5714. endif
  5715. endfor
  5716. diffoff!
  5717. endfunction
  5718. function! s:BlurStatus() abort
  5719. if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
  5720. let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
  5721. if len(winnrs)
  5722. exe winnrs[0].'wincmd w'
  5723. else
  5724. belowright new +setl\ bufhidden=delete
  5725. endif
  5726. if &diff
  5727. call fugitive#DiffClose()
  5728. endif
  5729. endif
  5730. endfunction
  5731. let s:bang_edits = {'split': 'Git', 'vsplit': 'vertical Git', 'tabedit': 'tab Git', 'pedit': 'Git!'}
  5732. function! fugitive#Open(cmd, bang, mods, arg, ...) abort
  5733. exe s:VersionCheck()
  5734. if a:bang
  5735. return 'echoerr ' . string(':G' . a:cmd . '! for temp buffer output has been replaced by :' . get(s:bang_edits, a:cmd, 'Git') . ' --paginate')
  5736. endif
  5737. let mods = s:Mods(a:mods)
  5738. if a:cmd ==# 'edit'
  5739. call s:BlurStatus()
  5740. endif
  5741. try
  5742. let [file, pre] = s:OpenParse(a:arg, 1, 0)
  5743. catch /^fugitive:/
  5744. return 'echoerr ' . string(v:exception)
  5745. endtry
  5746. return mods . a:cmd . pre . ' ' . s:fnameescape(file)
  5747. endfunction
  5748. function! fugitive#DropCommand(line1, count, range, bang, mods, arg, ...) abort
  5749. exe s:VersionCheck()
  5750. let mods = s:Mods(a:mods)
  5751. try
  5752. let [files, pre] = s:OpenParse(a:arg, 1, 1)
  5753. catch /^fugitive:/
  5754. return 'echoerr ' . string(v:exception)
  5755. endtry
  5756. if empty(files)
  5757. return 'drop'
  5758. endif
  5759. call s:BlurStatus()
  5760. return mods . 'drop' . ' ' . s:fnameescape(files) . substitute(pre, '^ *+', '|', '')
  5761. endfunction
  5762. function! s:ReadPrepare(line1, count, range, mods) abort
  5763. let mods = s:Mods(a:mods)
  5764. let after = a:count
  5765. if a:count < 0
  5766. let delete = 'silent 1,' . line('$') . 'delete_|'
  5767. let after = line('$')
  5768. elseif a:range == 2
  5769. let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
  5770. else
  5771. let delete = ''
  5772. endif
  5773. if foldlevel(after)
  5774. let pre = after . 'foldopen!|'
  5775. else
  5776. let pre = ''
  5777. endif
  5778. return [pre . 'keepalt ' . mods . after . 'read', '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')]
  5779. endfunction
  5780. function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, ...) abort
  5781. exe s:VersionCheck()
  5782. let [read, post] = s:ReadPrepare(a:line1, a:count, a:range, a:mods)
  5783. try
  5784. let [file, pre] = s:OpenParse(a:arg, 0, 0)
  5785. catch /^fugitive:/
  5786. return 'echoerr ' . string(v:exception)
  5787. endtry
  5788. if file =~# '^fugitive:' && a:count is# 0
  5789. return 'exe ' .string('keepalt ' . s:Mods(a:mods) . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
  5790. endif
  5791. return read . ' ' . pre . ' ' . s:fnameescape(file) . post
  5792. endfunction
  5793. function! fugitive#EditComplete(A, L, P) abort
  5794. if a:A =~# '^>'
  5795. return map(s:FilterEscape(s:CompleteHeads(s:Dir()), a:A[1:-1]), "'>' . v:val")
  5796. else
  5797. return fugitive#CompleteObject(a:A, a:L, a:P)
  5798. endif
  5799. endfunction
  5800. function! fugitive#ReadComplete(A, L, P) abort
  5801. return fugitive#EditComplete(a:A, a:L, a:P)
  5802. endfunction
  5803. " Section: :Gwrite, :Gwq
  5804. function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, ...) abort
  5805. exe s:VersionCheck()
  5806. if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG')) && empty(a:arg)
  5807. return (empty($GIT_INDEX_FILE) ? 'write|bdelete' : 'wq') . (a:bang ? '!' : '')
  5808. elseif get(b:, 'fugitive_type', '') ==# 'index' && empty(a:arg)
  5809. return 'Git commit'
  5810. elseif &buftype ==# 'nowrite' && getline(4) =~# '^[+-]\{3\} '
  5811. return 'echoerr ' . string('fugitive: :Gwrite from :Git diff has been removed in favor of :Git add --edit')
  5812. endif
  5813. let mytab = tabpagenr()
  5814. let mybufnr = bufnr('')
  5815. let args = s:ArgSplit(a:arg)
  5816. let after = ''
  5817. if get(args, 0) =~# '^+'
  5818. let after = '|' . remove(args, 0)[1:-1]
  5819. endif
  5820. try
  5821. let file = len(args) ? s:Generate(s:Expand(join(args, ' '))) : fugitive#Real(@%)
  5822. catch /^fugitive:/
  5823. return 'echoerr ' . string(v:exception)
  5824. endtry
  5825. if empty(file)
  5826. return 'echoerr '.string('fugitive: cannot determine file path')
  5827. endif
  5828. if file =~# '^fugitive:'
  5829. return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
  5830. endif
  5831. exe s:DirCheck()
  5832. let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
  5833. if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
  5834. let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
  5835. return 'echoerr v:errmsg'
  5836. endif
  5837. let treebufnr = 0
  5838. for nr in range(1,bufnr('$'))
  5839. if fnamemodify(bufname(nr),':p') ==# file
  5840. let treebufnr = nr
  5841. endif
  5842. endfor
  5843. if treebufnr > 0 && treebufnr != bufnr('')
  5844. let temp = tempname()
  5845. silent execute 'keepalt %write '.temp
  5846. for tab in [mytab] + range(1,tabpagenr('$'))
  5847. for winnr in range(1,tabpagewinnr(tab,'$'))
  5848. if tabpagebuflist(tab)[winnr-1] == treebufnr
  5849. execute 'tabnext '.tab
  5850. if winnr != winnr()
  5851. execute winnr.'wincmd w'
  5852. let restorewinnr = 1
  5853. endif
  5854. try
  5855. let lnum = line('.')
  5856. let last = line('$')
  5857. silent execute '$read '.temp
  5858. silent execute '1,'.last.'delete_'
  5859. silent write!
  5860. silent execute lnum
  5861. diffupdate
  5862. let did = 1
  5863. finally
  5864. if exists('restorewinnr')
  5865. wincmd p
  5866. endif
  5867. execute 'tabnext '.mytab
  5868. endtry
  5869. break
  5870. endif
  5871. endfor
  5872. endfor
  5873. if !exists('did')
  5874. call writefile(readfile(temp,'b'),file,'b')
  5875. endif
  5876. else
  5877. execute 'write! '.s:fnameescape(file)
  5878. endif
  5879. let message = s:ChompStderr(['add'] + (a:bang ? ['--force'] : []) + ['--', file])
  5880. if len(message)
  5881. let v:errmsg = 'fugitive: '.message
  5882. return 'echoerr v:errmsg'
  5883. endif
  5884. if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
  5885. setlocal nomodified
  5886. endif
  5887. let one = fugitive#Find(':1:'.file)
  5888. let two = fugitive#Find(':2:'.file)
  5889. let three = fugitive#Find(':3:'.file)
  5890. for nr in range(1,bufnr('$'))
  5891. let name = fnamemodify(bufname(nr), ':p')
  5892. if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
  5893. execute nr.'bdelete'
  5894. endif
  5895. endfor
  5896. unlet! restorewinnr
  5897. let zero = fugitive#Find(':0:'.file)
  5898. exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
  5899. for tab in range(1,tabpagenr('$'))
  5900. for winnr in range(1,tabpagewinnr(tab,'$'))
  5901. let bufnr = tabpagebuflist(tab)[winnr-1]
  5902. let bufname = fnamemodify(bufname(bufnr), ':p')
  5903. if bufname ==# zero && bufnr != mybufnr
  5904. execute 'tabnext '.tab
  5905. if winnr != winnr()
  5906. execute winnr.'wincmd w'
  5907. let restorewinnr = 1
  5908. endif
  5909. try
  5910. let lnum = line('.')
  5911. let last = line('$')
  5912. silent execute '$read '.s:fnameescape(file)
  5913. silent execute '1,'.last.'delete_'
  5914. silent execute lnum
  5915. setlocal nomodified
  5916. diffupdate
  5917. finally
  5918. if exists('restorewinnr')
  5919. wincmd p
  5920. endif
  5921. execute 'tabnext '.mytab
  5922. endtry
  5923. break
  5924. endif
  5925. endfor
  5926. endfor
  5927. call fugitive#DidChange()
  5928. return 'checktime' . after
  5929. endfunction
  5930. function! fugitive#WqCommand(...) abort
  5931. let bang = a:4 ? '!' : ''
  5932. if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG'))
  5933. return 'wq'.bang
  5934. endif
  5935. let result = call('fugitive#WriteCommand', a:000)
  5936. if result =~# '^\%(write\|wq\|echoerr\)'
  5937. return s:sub(result,'^write','wq')
  5938. else
  5939. return result.'|quit'.bang
  5940. endif
  5941. endfunction
  5942. " Section: :Git push, :Git fetch
  5943. function! s:CompletePush(A, L, P, ...) abort
  5944. let dir = a:0 ? a:1 : s:Dir()
  5945. let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
  5946. if empty(remote)
  5947. let matches = s:LinesError([dir, 'remote'])[0]
  5948. elseif a:A =~# ':'
  5949. let lead = matchstr(a:A, '^[^:]*:')
  5950. let matches = s:LinesError([dir, 'ls-remote', remote])[0]
  5951. call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
  5952. call map(matches, 'lead . s:sub(v:val, "^.*\t", "")')
  5953. else
  5954. let matches = s:CompleteHeads(dir)
  5955. if a:A =~# '^[\''"]\=+'
  5956. call map(matches, '"+" . v:val')
  5957. endif
  5958. endif
  5959. return s:FilterEscape(matches, a:A)
  5960. endfunction
  5961. function! fugitive#PushComplete(A, L, P, ...) abort
  5962. return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompletePush'), a:000)
  5963. endfunction
  5964. function! fugitive#FetchComplete(A, L, P, ...) abort
  5965. return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
  5966. endfunction
  5967. function! s:PushSubcommand(...) abort
  5968. return {'no_more': 1}
  5969. endfunction
  5970. function! s:FetchSubcommand(...) abort
  5971. return {'no_more': 1}
  5972. endfunction
  5973. " Section: :Gdiff
  5974. augroup fugitive_diff
  5975. autocmd!
  5976. autocmd BufWinLeave * nested
  5977. \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
  5978. \ call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
  5979. \ endif
  5980. autocmd BufWinEnter * nested
  5981. \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
  5982. \ call s:diffoff() |
  5983. \ endif
  5984. augroup END
  5985. function! s:can_diffoff(buf) abort
  5986. return getwinvar(bufwinnr(a:buf), '&diff') &&
  5987. \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
  5988. endfunction
  5989. function! fugitive#CanDiffoff(buf) abort
  5990. return s:can_diffoff(bufnr(a:buf))
  5991. endfunction
  5992. function! s:DiffModifier(count, default) abort
  5993. let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
  5994. if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
  5995. return ''
  5996. elseif &diffopt =~# 'vertical'
  5997. return 'vertical '
  5998. elseif !get(g:, 'fugitive_diffsplit_directional_fit', a:default)
  5999. return ''
  6000. elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
  6001. return ''
  6002. else
  6003. return 'vertical '
  6004. endif
  6005. endfunction
  6006. function! s:diff_window_count() abort
  6007. let c = 0
  6008. for nr in range(1,winnr('$'))
  6009. let c += getwinvar(nr,'&diff')
  6010. endfor
  6011. return c
  6012. endfunction
  6013. function! s:diffthis() abort
  6014. if !&diff
  6015. let w:fugitive_diff_restore = 1
  6016. diffthis
  6017. endif
  6018. endfunction
  6019. function! s:diffoff() abort
  6020. unlet! w:fugitive_diff_restore
  6021. diffoff
  6022. endfunction
  6023. function! s:diffoff_all(dir) abort
  6024. let curwin = winnr()
  6025. for nr in range(1,winnr('$'))
  6026. if getwinvar(nr, '&diff') && !empty(getwinvar(nr, 'fugitive_diff_restore'))
  6027. call setwinvar(nr, 'fugitive_diff_restore', '')
  6028. endif
  6029. endfor
  6030. if curwin != winnr()
  6031. execute curwin.'wincmd w'
  6032. endif
  6033. diffoff!
  6034. endfunction
  6035. function! s:IsConflicted() abort
  6036. return len(@%) && !empty(s:ChompDefault('', ['ls-files', '--unmerged', '--', expand('%:p')]))
  6037. endfunction
  6038. function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, ...) abort
  6039. exe s:VersionCheck()
  6040. let args = s:ArgSplit(a:arg)
  6041. let post = ''
  6042. let autodir = a:autodir
  6043. while get(args, 0, '') =~# '^++'
  6044. if args[0] =~? '^++novertical$'
  6045. let autodir = 0
  6046. else
  6047. return 'echoerr ' . string('fugitive: unknown option ' . args[0])
  6048. endif
  6049. call remove(args, 0)
  6050. endwhile
  6051. if get(args, 0) =~# '^+'
  6052. let post = remove(args, 0)[1:-1]
  6053. endif
  6054. if exists(':DiffGitCached') && empty(args)
  6055. return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
  6056. endif
  6057. let commit = s:DirCommitFile(@%)[1]
  6058. if a:mods =~# '\<tab\>'
  6059. let mods = substitute(a:mods, '\<tab\>', '', 'g')
  6060. let pre = 'tab split'
  6061. else
  6062. let mods = 'keepalt ' . a:mods
  6063. let pre = ''
  6064. endif
  6065. let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
  6066. if (empty(args) || args[0] =~# '^>\=:$') && a:keepfocus
  6067. exe s:DirCheck()
  6068. if commit =~# '^1\=$' && s:IsConflicted()
  6069. let parents = [s:Relative(':2:'), s:Relative(':3:')]
  6070. elseif empty(commit)
  6071. let parents = [s:Relative(':0:')]
  6072. elseif commit =~# '^\d\=$'
  6073. let parents = [s:Relative('@:')]
  6074. elseif commit =~# '^\x\x\+$'
  6075. let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
  6076. call map(parents, 's:Relative(v:val . ":")')
  6077. endif
  6078. endif
  6079. try
  6080. if exists('parents') && len(parents) > 1
  6081. exe pre
  6082. let mods = (autodir ? s:DiffModifier(len(parents) + 1, empty(args) || args[0] =~# '^>') : '') . s:Mods(mods, 'leftabove')
  6083. let nr = bufnr('')
  6084. if len(parents) > 1 && !&equalalways
  6085. let equalalways = 0
  6086. set equalalways
  6087. endif
  6088. execute mods 'split' s:fnameescape(fugitive#Find(parents[0]))
  6089. call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
  6090. let nr2 = bufnr('')
  6091. call s:diffthis()
  6092. exe back
  6093. call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
  6094. let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
  6095. for i in range(len(parents)-1, 1, -1)
  6096. execute mods 'split' s:fnameescape(fugitive#Find(parents[i]))
  6097. call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
  6098. let nrx = bufnr('')
  6099. call s:diffthis()
  6100. exe back
  6101. call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
  6102. endfor
  6103. call s:diffthis()
  6104. return post
  6105. elseif len(args)
  6106. let arg = join(args, ' ')
  6107. if arg ==# ''
  6108. return post
  6109. elseif arg ==# ':/'
  6110. exe s:DirCheck()
  6111. let file = s:Relative()
  6112. elseif arg ==# ':'
  6113. exe s:DirCheck()
  6114. let file = len(commit) ? s:Relative() : s:Relative(s:IsConflicted() ? ':1:' : ':0:')
  6115. elseif arg =~# '^:\d$'
  6116. exe s:DirCheck()
  6117. let file = s:Relative(arg . ':')
  6118. elseif arg =~# '^[~^]\d*$'
  6119. return 'echoerr ' . string('fugitive: change ' . arg . ' to !' . arg . ' to diff against ancestor')
  6120. else
  6121. try
  6122. let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
  6123. catch /^fugitive:/
  6124. return 'echoerr ' . string(v:exception)
  6125. endtry
  6126. endif
  6127. if a:keepfocus || arg =~# '^>'
  6128. let mods = s:Mods(a:mods, 'leftabove')
  6129. else
  6130. let mods = s:Mods(a:mods)
  6131. endif
  6132. elseif exists('parents')
  6133. let file = get(parents, -1, s:Relative(repeat('0', 40). ':'))
  6134. let mods = s:Mods(a:mods, 'leftabove')
  6135. elseif len(commit)
  6136. let file = s:Relative()
  6137. let mods = s:Mods(a:mods, 'rightbelow')
  6138. elseif s:IsConflicted()
  6139. let file = s:Relative(':1:')
  6140. let mods = s:Mods(a:mods, 'leftabove')
  6141. if get(g:, 'fugitive_legacy_commands', 1)
  6142. let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
  6143. endif
  6144. else
  6145. exe s:DirCheck()
  6146. let file = s:Relative(':0:')
  6147. let mods = s:Mods(a:mods, 'leftabove')
  6148. endif
  6149. let spec = s:Generate(file)
  6150. if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
  6151. let spec = s:VimSlash(spec . s:Relative('/'))
  6152. endif
  6153. exe pre
  6154. let w:fugitive_diff_restore = 1
  6155. let mods = (autodir ? s:DiffModifier(2, empty(args) || args[0] =~# '^>') : '') . mods
  6156. if &diffopt =~# 'vertical'
  6157. let diffopt = &diffopt
  6158. set diffopt-=vertical
  6159. endif
  6160. execute mods 'diffsplit' s:fnameescape(spec)
  6161. let w:fugitive_diff_restore = 1
  6162. let winnr = winnr()
  6163. if getwinvar('#', '&diff')
  6164. if a:keepfocus
  6165. exe back
  6166. endif
  6167. endif
  6168. return post
  6169. catch /^fugitive:/
  6170. return 'echoerr ' . string(v:exception)
  6171. finally
  6172. if exists('l:equalalways')
  6173. let &g:equalalways = equalalways
  6174. endif
  6175. if exists('diffopt')
  6176. let &diffopt = diffopt
  6177. endif
  6178. endtry
  6179. endfunction
  6180. " Section: :GMove, :GRemove
  6181. function! s:Move(force, rename, destination) abort
  6182. let dir = s:Dir()
  6183. exe s:DirCheck(dir)
  6184. if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
  6185. return 'echoerr ' . string('fugitive: mv not supported for this buffer')
  6186. endif
  6187. if a:rename
  6188. let default_root = expand('%:p:s?[\/]$??:h') . '/'
  6189. else
  6190. let default_root = s:Tree(dir) . '/'
  6191. endif
  6192. if a:destination =~# '^:/:\='
  6193. let destination = s:Tree(dir) . s:Expand(substitute(a:destination, '^:/:\=', '', ''))
  6194. elseif a:destination =~# '^:(top)'
  6195. let destination = s:Expand(matchstr(a:destination, ')\zs.*'))
  6196. if destination !~# '^/\|^\a\+:'
  6197. let destination = s:Tree(dir) . '/' . destination
  6198. endif
  6199. let destination = s:Tree(dir) .
  6200. elseif a:destination =~# '^:(\%(top,literal\|literal,top\))'
  6201. let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
  6202. elseif a:destination =~# '^:(literal)\.\.\=\%(/\|$\)'
  6203. let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
  6204. elseif a:destination =~# '^:(literal)'
  6205. let destination = simplify(default_root . matchstr(a:destination, ')\zs.*'))
  6206. else
  6207. let destination = s:Expand(a:destination)
  6208. if destination =~# '^\.\.\=\%(/\|$\)'
  6209. let destination = simplify(getcwd() . '/' . destination)
  6210. elseif destination !~# '^\a\+:\|^/'
  6211. let destination = default_root . destination
  6212. endif
  6213. endif
  6214. let destination = s:Slash(destination)
  6215. if isdirectory(@%)
  6216. setlocal noswapfile
  6217. endif
  6218. let exec = fugitive#Execute(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
  6219. if exec.exit_status && exec.stderr !=# ['']
  6220. return 'echoerr ' .string('fugitive: '.s:JoinChomp(exec.stderr))
  6221. endif
  6222. if isdirectory(destination)
  6223. let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
  6224. endif
  6225. let reload = '|call fugitive#DidChange(' . string(exec) . ')'
  6226. if empty(s:DirCommitFile(@%)[1])
  6227. if isdirectory(destination)
  6228. return 'keepalt edit '.s:fnameescape(destination) . reload
  6229. else
  6230. return 'keepalt saveas! '.s:fnameescape(destination) . reload
  6231. endif
  6232. else
  6233. return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
  6234. endif
  6235. endfunction
  6236. function! fugitive#RenameComplete(A,L,P) abort
  6237. if a:A =~# '^[.:]\=/'
  6238. return fugitive#CompletePath(a:A)
  6239. else
  6240. let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
  6241. return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
  6242. endif
  6243. endfunction
  6244. function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, ...) abort
  6245. return s:Move(a:bang, 0, a:arg)
  6246. endfunction
  6247. function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, ...) abort
  6248. return s:Move(a:bang, 1, a:arg)
  6249. endfunction
  6250. function! s:Remove(after, force) abort
  6251. let dir = s:Dir()
  6252. exe s:DirCheck(dir)
  6253. if len(@%) && s:DirCommitFile(@%)[1] ==# ''
  6254. let cmd = ['rm']
  6255. elseif s:DirCommitFile(@%)[1] ==# '0'
  6256. let cmd = ['rm','--cached']
  6257. else
  6258. return 'echoerr ' . string('fugitive: rm not supported for this buffer')
  6259. endif
  6260. if a:force
  6261. let cmd += ['--force']
  6262. endif
  6263. let message = s:ChompStderr(cmd + ['--', expand('%:p')], dir)
  6264. if len(message)
  6265. let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
  6266. return 'echoerr '.string(v:errmsg)
  6267. else
  6268. return a:after . (a:force ? '!' : ''). '|call fugitive#DidChange(' . string(dir) . ')'
  6269. endif
  6270. endfunction
  6271. function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, ...) abort
  6272. return s:Remove('edit', a:bang)
  6273. endfunction
  6274. function! fugitive#UnlinkCommand(line1, line2, range, bang, mods, arg, ...) abort
  6275. return s:Remove('edit', a:bang)
  6276. endfunction
  6277. function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, ...) abort
  6278. return s:Remove('bdelete', a:bang)
  6279. endfunction
  6280. " Section: :Git blame
  6281. function! s:Keywordprg() abort
  6282. let args = ' --git-dir=' . escape(FugitiveGitPath(s:GitDir()), "\\\"' ")
  6283. if has('gui_running') && !has('win32')
  6284. return s:GitShellCmd() . ' --no-pager' . args . ' log -1'
  6285. else
  6286. return s:GitShellCmd() . args . ' show'
  6287. endif
  6288. endfunction
  6289. function! s:linechars(pattern) abort
  6290. let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
  6291. if &conceallevel > 1
  6292. for col in range(1, chars)
  6293. let chars -= synconcealed(line('.'), col)[0]
  6294. endfor
  6295. endif
  6296. return chars
  6297. endfunction
  6298. function! s:BlameBufnr(...) abort
  6299. let state = s:TempState(a:0 ? a:1 : bufnr(''))
  6300. if get(state, 'filetype', '') ==# 'fugitiveblame'
  6301. return get(state, 'origin_bufnr', -1)
  6302. else
  6303. return -1
  6304. endif
  6305. endfunction
  6306. function! s:BlameCommitFileLnum(...) abort
  6307. let line = a:0 ? a:1 : getline('.')
  6308. let state = a:0 > 1 ? a:2 : s:TempState()
  6309. if get(state, 'filetype', '') !=# 'fugitiveblame'
  6310. return ['', '', 0]
  6311. endif
  6312. let commit = matchstr(line, '^\^\=[?*]*\zs\x\+')
  6313. if commit =~# '^0\+$'
  6314. let commit = ''
  6315. elseif has_key(state, 'blame_reverse_end')
  6316. let commit = get(s:LinesError([state.git_dir, 'rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end])[0], 0, '')
  6317. endif
  6318. let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
  6319. let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s*\d\+ \%((\| *\d\+)\)')
  6320. if empty(path) && lnum
  6321. let path = get(state, 'blame_file', '')
  6322. endif
  6323. return [commit, path, lnum]
  6324. endfunction
  6325. function! s:BlameLeave() abort
  6326. let bufwinnr = bufwinnr(s:BlameBufnr())
  6327. if bufwinnr > 0
  6328. let bufnr = bufnr('')
  6329. exe bufwinnr . 'wincmd w'
  6330. return bufnr . 'bdelete'
  6331. endif
  6332. return ''
  6333. endfunction
  6334. function! s:BlameQuit() abort
  6335. let cmd = s:BlameLeave()
  6336. if empty(cmd)
  6337. return 'bdelete'
  6338. elseif len(s:DirCommitFile(@%)[1])
  6339. return cmd . '|Gedit'
  6340. else
  6341. return cmd
  6342. endif
  6343. endfunction
  6344. function! fugitive#BlameComplete(A, L, P) abort
  6345. return s:CompleteSub('blame', a:A, a:L, a:P)
  6346. endfunction
  6347. function! s:BlameSubcommand(line1, count, range, bang, mods, options) abort
  6348. let dir = s:Dir(a:options)
  6349. exe s:DirCheck(dir)
  6350. let flags = copy(a:options.subcommand_args)
  6351. let i = 0
  6352. let raw = 0
  6353. let commits = []
  6354. let files = []
  6355. let ranges = []
  6356. if a:line1 > 0 && a:count > 0 && a:range != 1
  6357. call extend(ranges, ['-L', a:line1 . ',' . a:count])
  6358. endif
  6359. while i < len(flags)
  6360. let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
  6361. if len(match) && len(match[2])
  6362. call insert(flags, match[1])
  6363. let flags[i+1] = '-' . match[2]
  6364. continue
  6365. endif
  6366. let arg = flags[i]
  6367. if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
  6368. let raw = 1
  6369. elseif arg ==# '--contents' && i + 1 < len(flags)
  6370. call extend(commits, remove(flags, i, i+1))
  6371. continue
  6372. elseif arg ==# '-L' && i + 1 < len(flags)
  6373. call extend(ranges, remove(flags, i, i+1))
  6374. continue
  6375. elseif arg =~# '^--contents='
  6376. call add(commits, remove(flags, i))
  6377. continue
  6378. elseif arg =~# '^-L.'
  6379. call add(ranges, remove(flags, i))
  6380. continue
  6381. elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
  6382. let i += 1
  6383. if i == len(flags)
  6384. echohl ErrorMsg
  6385. echo s:ChompStderr([dir, 'blame', arg])
  6386. echohl NONE
  6387. return ''
  6388. endif
  6389. elseif arg ==# '--'
  6390. if i + 1 < len(flags)
  6391. call extend(files, remove(flags, i + 1, -1))
  6392. endif
  6393. call remove(flags, i)
  6394. break
  6395. elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
  6396. if index(flags, '--') >= 0
  6397. call add(commits, remove(flags, i))
  6398. continue
  6399. endif
  6400. if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
  6401. call add(commits, remove(flags, i))
  6402. continue
  6403. endif
  6404. try
  6405. let dcf = s:DirCommitFile(fugitive#Find(arg, dir))
  6406. if len(dcf[1]) && empty(dcf[2])
  6407. call add(commits, remove(flags, i))
  6408. continue
  6409. endif
  6410. catch /^fugitive:/
  6411. endtry
  6412. call add(files, remove(flags, i))
  6413. continue
  6414. endif
  6415. let i += 1
  6416. endwhile
  6417. let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./', dir))), '^\.\%(/\|$\)', '', '')
  6418. if empty(commits) && len(files) > 1
  6419. call add(commits, remove(files, 1))
  6420. endif
  6421. exe s:BlameLeave()
  6422. try
  6423. let cmd = a:options.flags + ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', a:options.subcommand, '--show-number']
  6424. call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
  6425. if a:count > 0 && empty(ranges)
  6426. let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
  6427. endif
  6428. call extend(cmd, ranges)
  6429. let tempname = tempname()
  6430. let temp = tempname . (raw ? '' : '.fugitiveblame')
  6431. if len(commits)
  6432. let cmd += commits
  6433. elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
  6434. let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
  6435. elseif empty(files) && !s:HasOpt(flags, '--reverse')
  6436. if &modified || !empty(s:DirCommitFile(@%)[1])
  6437. let cmd += ['--contents', tempname . '.in']
  6438. silent execute 'noautocmd keepalt %write ' . s:fnameescape(tempname . '.in')
  6439. let delete_in = 1
  6440. elseif &autoread
  6441. exe 'checktime ' . bufnr('')
  6442. endif
  6443. else
  6444. call fugitive#Autowrite()
  6445. endif
  6446. let basecmd = [{'git': a:options.git}, dir, '--literal-pathspecs'] + cmd + ['--'] + (len(files) ? files : [file])
  6447. let [err, exec_error] = s:StdoutToFile(temp, basecmd)
  6448. if exists('delete_in')
  6449. call delete(tempname . '.in')
  6450. endif
  6451. redraw
  6452. try
  6453. if exec_error
  6454. let lines = split(err, "\n")
  6455. if empty(lines)
  6456. let lines = readfile(temp)
  6457. endif
  6458. for i in range(len(lines))
  6459. if lines[i] =~# '^error: \|^fatal: '
  6460. echohl ErrorMsg
  6461. echon lines[i]
  6462. echohl NONE
  6463. break
  6464. else
  6465. echon lines[i]
  6466. endif
  6467. if i != len(lines) - 1
  6468. echon "\n"
  6469. endif
  6470. endfor
  6471. return ''
  6472. endif
  6473. let temp_state = {
  6474. \ 'git': a:options.git,
  6475. \ 'flags': a:options.flags,
  6476. \ 'args': [a:options.subcommand] + a:options.subcommand_args,
  6477. \ 'git_dir': s:GitDir(a:options),
  6478. \ 'cwd': s:UserCommandCwd(a:options),
  6479. \ 'filetype': (raw ? 'git' : 'fugitiveblame'),
  6480. \ 'blame_options': a:options,
  6481. \ 'blame_flags': flags,
  6482. \ 'blame_file': file}
  6483. if s:HasOpt(flags, '--reverse')
  6484. let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
  6485. endif
  6486. if a:line1 == 0 && a:count == 1
  6487. if get(a:options, 'curwin')
  6488. let edit = 'edit'
  6489. elseif a:bang
  6490. let edit = 'pedit'
  6491. else
  6492. let edit = 'split'
  6493. endif
  6494. return s:BlameCommit(s:Mods(a:mods) . edit, get(readfile(temp), 0, ''), temp_state)
  6495. elseif (a:line1 == 0 || a:range == 1) && a:count > 0
  6496. let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit', 'edit'], a:count - (a:line1 ? a:line1 : 1), 'split')
  6497. return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
  6498. else
  6499. let temp = s:Resolve(temp)
  6500. let temp_state.file = temp
  6501. call s:RunSave(temp_state)
  6502. if len(ranges + commits + files) || raw
  6503. let reload = '|call fugitive#DidChange(fugitive#Result(' . string(temp_state.file) . '))'
  6504. let mods = s:Mods(a:mods)
  6505. if a:count != 0
  6506. exe 'silent keepalt' mods get(a:options, 'curwin') ? 'edit' : 'split' s:fnameescape(temp)
  6507. elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
  6508. exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
  6509. else
  6510. return mods . 'edit ' . s:fnameescape(temp) . reload
  6511. endif
  6512. return reload[1 : -1]
  6513. endif
  6514. if a:mods =~# '\<tab\>'
  6515. silent tabedit %
  6516. endif
  6517. let bufnr = bufnr('')
  6518. let temp_state.origin_bufnr = bufnr
  6519. let restore = []
  6520. let mods = substitute(a:mods, '\<tab\>', '', 'g')
  6521. for winnr in range(winnr('$'),1,-1)
  6522. if getwinvar(winnr, '&scrollbind')
  6523. if !&l:scrollbind
  6524. call setwinvar(winnr, '&scrollbind', 0)
  6525. elseif winnr != winnr() && getwinvar(winnr, '&foldenable')
  6526. call setwinvar(winnr, '&foldenable', 0)
  6527. call add(restore, 'call setwinvar(bufwinnr('.winbufnr(winnr).'),"&foldenable",1)')
  6528. endif
  6529. endif
  6530. let win_blame_bufnr = s:BlameBufnr(winbufnr(winnr))
  6531. if getwinvar(winnr, '&scrollbind') ? win_blame_bufnr == bufnr : win_blame_bufnr > 0
  6532. execute winbufnr(winnr).'bdelete'
  6533. endif
  6534. endfor
  6535. let restore_winnr = exists('*win_getid') ? win_getid() : 'bufwinnr(' . bufnr . ')'
  6536. if !&l:scrollbind
  6537. call add(restore, 'call setwinvar(' . restore_winnr . ',"&scrollbind",0)')
  6538. endif
  6539. if &l:wrap
  6540. call add(restore, 'call setwinvar(' . restore_winnr . ',"&wrap",1)')
  6541. endif
  6542. if &l:foldenable
  6543. call add(restore, 'call setwinvar(' . restore_winnr . ',"&foldenable",1)')
  6544. endif
  6545. setlocal scrollbind nowrap nofoldenable
  6546. let top = line('w0') + &scrolloff
  6547. let current = line('.')
  6548. exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
  6549. let w:fugitive_leave = join(restore, '|')
  6550. execute top
  6551. normal! zt
  6552. execute current
  6553. setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
  6554. if exists('+relativenumber')
  6555. setlocal norelativenumber
  6556. endif
  6557. if exists('+signcolumn')
  6558. setlocal signcolumn=no
  6559. endif
  6560. execute "vertical resize ".(s:linechars('.\{-\}\s\+\d\+\ze)')+1)
  6561. redraw
  6562. syncbind
  6563. exe s:DoAutocmdChanged(temp_state)
  6564. endif
  6565. endtry
  6566. return ''
  6567. catch /^fugitive:/
  6568. return 'echoerr ' . string(v:exception)
  6569. endtry
  6570. endfunction
  6571. function! s:BlameCommit(cmd, ...) abort
  6572. let line = a:0 ? a:1 : getline('.')
  6573. let state = a:0 ? a:2 : s:TempState()
  6574. let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
  6575. let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
  6576. let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
  6577. if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
  6578. let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
  6579. return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
  6580. endif
  6581. if commit =~# '^0*$'
  6582. return 'echoerr ' . string('fugitive: no commit')
  6583. endif
  6584. if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
  6585. let path = commit . ':' . path
  6586. return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
  6587. endif
  6588. let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
  6589. if cmd =~# '^echoerr'
  6590. return cmd
  6591. endif
  6592. execute cmd
  6593. if a:cmd ==# 'pedit' || empty(path)
  6594. return ''
  6595. endif
  6596. if search('^diff .* b/\M'.escape(path,'\').'$','W')
  6597. call search('^+++')
  6598. let head = line('.')
  6599. while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
  6600. let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
  6601. let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
  6602. if lnum >= top && lnum <= top + len
  6603. let offset = lnum - top
  6604. if &scrolloff
  6605. +
  6606. normal! zt
  6607. else
  6608. normal! zt
  6609. +
  6610. endif
  6611. while offset > 0 && line('.') < line('$')
  6612. +
  6613. if getline('.') =~# '^[ ' . sigil . ']'
  6614. let offset -= 1
  6615. endif
  6616. endwhile
  6617. return 'normal! zv'
  6618. endif
  6619. endwhile
  6620. execute head
  6621. normal! zt
  6622. endif
  6623. return ''
  6624. endfunction
  6625. function! s:BlameJump(suffix, ...) abort
  6626. let suffix = a:suffix
  6627. let [commit, path, lnum] = s:BlameCommitFileLnum()
  6628. if empty(path)
  6629. return 'echoerr ' . string('fugitive: could not determine filename for blame')
  6630. endif
  6631. if commit =~# '^0*$'
  6632. let commit = '@'
  6633. let suffix = ''
  6634. endif
  6635. let offset = line('.') - line('w0')
  6636. let state = s:TempState()
  6637. let flags = get(state, 'blame_flags', [])
  6638. let blame_bufnr = s:BlameBufnr()
  6639. if blame_bufnr > 0
  6640. let bufnr = bufnr('')
  6641. let winnr = bufwinnr(blame_bufnr)
  6642. if winnr > 0
  6643. exe winnr.'wincmd w'
  6644. exe bufnr.'bdelete'
  6645. endif
  6646. execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
  6647. execute lnum
  6648. endif
  6649. let my_bufnr = bufnr('')
  6650. if blame_bufnr < 0
  6651. let blame_args = flags + [commit . suffix, '--', path]
  6652. let result = s:BlameSubcommand(0, 0, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
  6653. else
  6654. let blame_args = flags
  6655. let result = s:BlameSubcommand(-1, -1, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
  6656. endif
  6657. if bufnr('') == my_bufnr
  6658. return result
  6659. endif
  6660. execute result
  6661. execute lnum
  6662. let delta = line('.') - line('w0') - offset
  6663. if delta > 0
  6664. execute 'normal! '.delta."\<C-E>"
  6665. elseif delta < 0
  6666. execute 'normal! '.(-delta)."\<C-Y>"
  6667. endif
  6668. keepjumps syncbind
  6669. redraw
  6670. echo ':Git blame' s:fnameescape(blame_args)
  6671. return ''
  6672. endfunction
  6673. let s:hash_colors = {}
  6674. function! fugitive#BlameSyntax() abort
  6675. let conceal = has('conceal') ? ' conceal' : ''
  6676. let flags = get(s:TempState(), 'blame_flags', [])
  6677. syn spell notoplevel
  6678. syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
  6679. syn match FugitiveblameHash "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
  6680. if s:HasOpt(flags, '-b') || FugitiveConfigGet('blame.blankBoundary') =~# '^1$\|^true$'
  6681. syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
  6682. else
  6683. syn match FugitiveblameBoundary "^\^"
  6684. endif
  6685. syn match FugitiveblameScoreDebug " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
  6686. syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
  6687. syn match FugitiveblameTime "\<[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
  6688. exec 'syn match FugitiveblameLineNumber "\s[[:digit:][:space:]]\{0,' . (len(line('$'))-1). '\}\d)\@=" contained containedin=FugitiveblameAnnotation' conceal
  6689. exec 'syn match FugitiveblameOriginalFile "\s\%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-name', '-f') ? '' : conceal)
  6690. exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
  6691. exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
  6692. syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
  6693. syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
  6694. hi def link FugitiveblameBoundary Keyword
  6695. hi def link FugitiveblameHash Identifier
  6696. hi def link FugitiveblameBoundaryIgnore Ignore
  6697. hi def link FugitiveblameUncommitted Ignore
  6698. hi def link FugitiveblameScoreDebug Debug
  6699. hi def link FugitiveblameTime PreProc
  6700. hi def link FugitiveblameLineNumber Number
  6701. hi def link FugitiveblameOriginalFile String
  6702. hi def link FugitiveblameOriginalLineNumber Float
  6703. hi def link FugitiveblameShort FugitiveblameDelimiter
  6704. hi def link FugitiveblameDelimiter Delimiter
  6705. hi def link FugitiveblameNotCommittedYet Comment
  6706. if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
  6707. return
  6708. endif
  6709. let seen = {}
  6710. for lnum in range(1, line('$'))
  6711. let orig_hash = matchstr(getline(lnum), '^\^\=[*?]*\zs\x\{6\}')
  6712. let hash = orig_hash
  6713. let hash = substitute(hash, '\(\x\)\x', '\=submatch(1).printf("%x", 15-str2nr(submatch(1),16))', 'g')
  6714. let hash = substitute(hash, '\(\x\x\)', '\=printf("%02x", str2nr(submatch(1),16)*3/4+32)', 'g')
  6715. if hash ==# '' || orig_hash ==# '000000' || has_key(seen, hash)
  6716. continue
  6717. endif
  6718. let seen[hash] = 1
  6719. if &t_Co == 256
  6720. let [s, r, g, b; __] = map(matchlist(orig_hash, '\(\x\)\x\(\x\)\x\(\x\)\x'), 'str2nr(v:val,16)')
  6721. let color = 16 + (r + 1) / 3 * 36 + (g + 1) / 3 * 6 + (b + 1) / 3
  6722. if color == 16
  6723. let color = 235
  6724. elseif color == 231
  6725. let color = 255
  6726. endif
  6727. let s:hash_colors[hash] = ' ctermfg='.color
  6728. else
  6729. let s:hash_colors[hash] = ''
  6730. endif
  6731. let pattern = substitute(orig_hash, '^\(\x\)\x\(\x\)\x\(\x\)\x$', '\1\\x\2\\x\3\\x', '') . '*\>'
  6732. exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=[*?]*\)\@<='.pattern.'" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
  6733. endfor
  6734. syn match FugitiveblameUncommitted "\%(^\^\=[?*]*\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
  6735. call s:BlameRehighlight()
  6736. endfunction
  6737. function! s:BlameRehighlight() abort
  6738. for [hash, cterm] in items(s:hash_colors)
  6739. if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
  6740. exe 'hi FugitiveblameHash'.hash.' guifg=#' . hash . cterm
  6741. else
  6742. exe 'hi link FugitiveblameHash'.hash.' Identifier'
  6743. endif
  6744. endfor
  6745. endfunction
  6746. function! s:BlameMaps(is_ftplugin) abort
  6747. let ft = a:is_ftplugin
  6748. call s:Map('n', '<F1>', ':help :Git_blame<CR>', '<silent>', ft)
  6749. call s:Map('n', 'g?', ':help :Git_blame<CR>', '<silent>', ft)
  6750. call s:Map('n', 'gq', ':exe <SID>BlameQuit()<CR>', '<silent>', ft)
  6751. call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
  6752. call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
  6753. call s:Map('n', '-', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
  6754. call s:Map('n', 's', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
  6755. call s:Map('n', 'u', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
  6756. call s:Map('n', 'P', ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>', ft)
  6757. call s:Map('n', '~', ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>', ft)
  6758. call s:Map('n', 'i', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
  6759. call s:Map('n', 'o', ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>', ft)
  6760. call s:Map('n', 'O', ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>', ft)
  6761. call s:Map('n', 'p', ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>', ft)
  6762. exe s:Map('n', '.', ":<C-U> <C-R>=substitute(<SID>BlameCommitFileLnum()[0],'^$','@','')<CR><Home>", '', ft)
  6763. exe s:Map('n', '(', "-", '', ft)
  6764. exe s:Map('n', ')', "+", '', ft)
  6765. call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>', ft)
  6766. call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>', ft)
  6767. call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>', ft)
  6768. endfunction
  6769. function! fugitive#BlameFileType() abort
  6770. setlocal nomodeline
  6771. setlocal foldmethod=manual
  6772. if len(s:GitDir())
  6773. let &l:keywordprg = s:Keywordprg()
  6774. endif
  6775. let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
  6776. if exists('+concealcursor')
  6777. setlocal concealcursor=nc conceallevel=2
  6778. let b:undo_ftplugin .= ' concealcursor< conceallevel<'
  6779. endif
  6780. if &modifiable
  6781. return ''
  6782. endif
  6783. call s:BlameMaps(1)
  6784. endfunction
  6785. function! s:BlameCursorSync(bufnr, line) abort
  6786. if a:line == line('.')
  6787. return
  6788. endif
  6789. if get(s:TempState(), 'origin_bufnr') == a:bufnr || get(s:TempState(a:bufnr), 'origin_bufnr') == bufnr('')
  6790. if &startofline
  6791. execute a:line
  6792. else
  6793. let pos = getpos('.')
  6794. let pos[1] = a:line
  6795. call setpos('.', pos)
  6796. endif
  6797. endif
  6798. endfunction
  6799. augroup fugitive_blame
  6800. autocmd!
  6801. autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
  6802. autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
  6803. autocmd WinLeave * let s:cursor_for_blame = [bufnr(''), line('.')]
  6804. autocmd WinEnter * if exists('s:cursor_for_blame') | call call('s:BlameCursorSync', s:cursor_for_blame) | endif
  6805. augroup END
  6806. " Section: :GBrowse
  6807. function! s:BrowserOpen(url, mods, echo_copy) abort
  6808. let [_, main, query, anchor; __] = matchlist(a:url, '^\([^#?]*\)\(?[^#]*\)\=\(#.*\)\=')
  6809. let url = main . tr(query, ' ', '+') . anchor
  6810. let url = substitute(url, '[ <>\|"]', '\="%".printf("%02X",char2nr(submatch(0)))', 'g')
  6811. let mods = s:Mods(a:mods)
  6812. if a:echo_copy
  6813. if has('clipboard')
  6814. let @+ = url
  6815. endif
  6816. return 'echo '.string(url)
  6817. elseif exists(':Browse') == 2
  6818. return 'echo '.string(url).'|' . mods . 'Browse '.url
  6819. elseif exists(':OpenBrowser') == 2
  6820. return 'echo '.string(url).'|' . mods . 'OpenBrowser '.url
  6821. else
  6822. if !exists('g:loaded_netrw')
  6823. runtime! autoload/netrw.vim
  6824. endif
  6825. if exists('*netrw#BrowseX')
  6826. return 'echo '.string(url).'|' . mods . 'call netrw#BrowseX('.string(url).', 0)'
  6827. elseif exists('*netrw#NetrwBrowseX')
  6828. return 'echo '.string(url).'|' . mods . 'call netrw#NetrwBrowseX('.string(url).', 0)'
  6829. else
  6830. return 'echoerr ' . string('Netrw not found. Define your own :Browse to use :GBrowse')
  6831. endif
  6832. endif
  6833. endfunction
  6834. function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, ...) abort
  6835. exe s:VersionCheck()
  6836. let dir = s:Dir()
  6837. try
  6838. let arg = a:arg
  6839. if arg =~# '^++\%([Gg]it\)\=[Rr]emote='
  6840. let remote = matchstr(arg, '^++\%([Gg]it\)\=[Rr]emote=\zs\S\+')
  6841. let arg = matchstr(arg, '\s\zs\S.*')
  6842. endif
  6843. let validremote = '\.\%(git\)\=\|\.\=/.*\|\a[[:alnum:]_-]*\%(://.\{-\}\)\='
  6844. if arg ==# '-'
  6845. let remote = ''
  6846. let rev = ''
  6847. let result = fugitive#Result()
  6848. if filereadable(get(result, 'file', ''))
  6849. let rev = s:fnameescape(result.file)
  6850. else
  6851. return 'echoerr ' . string('fugitive: could not find prior :Git invocation')
  6852. endif
  6853. elseif !exists('l:remote')
  6854. let remote = matchstr(arg, '\\\@<!\%(\\\\\)*[!@]\zs\%('.validremote.'\)$')
  6855. let rev = strpart(arg, 0, len(arg) - len(remote) - (empty(remote) ? 0 : 1))
  6856. else
  6857. let rev = arg
  6858. endif
  6859. let expanded = s:Expand(rev)
  6860. if expanded =~? '^\a\a\+:[\/][\/]' && expanded !~? '^fugitive:'
  6861. return s:BrowserOpen(s:Slash(expanded), a:mods, a:bang)
  6862. endif
  6863. if !exists('l:result')
  6864. let result = s:TempState(empty(expanded) ? bufnr('') : expanded)
  6865. endif
  6866. if !get(result, 'origin_bufnr', 1) && filereadable(get(result, 'file', ''))
  6867. for line in readfile(result.file, '', 4096)
  6868. let rev = s:fnameescape(matchstr(line, '\<https\=://[^[:space:]<>]*[^[:space:]<>.,;:"''!?]'))
  6869. if len(rev)
  6870. return s:BrowserOpen(rev, a:mods, a:bang)
  6871. endif
  6872. endfor
  6873. return 'echoerr ' . string('fugitive: no URL found in output of :Git')
  6874. endif
  6875. exe s:DirCheck(dir)
  6876. let config = fugitive#Config(dir)
  6877. if empty(remote) && expanded =~# '^[^-./:^~][^:^~]*$' && !empty(FugitiveConfigGet('remote.' . expanded . '.url', config))
  6878. let remote = expanded
  6879. let expanded = ''
  6880. endif
  6881. if empty(expanded)
  6882. let bufname = &buftype =~# '^\%(nofile\|terminal\)$' ? '' : s:BufName('%')
  6883. let expanded = s:DirRev(bufname)[1]
  6884. if empty(expanded)
  6885. let expanded = fugitive#Path(bufname, ':(top)', dir)
  6886. endif
  6887. if a:count > 0 && has_key(result, 'origin_bufnr') && a:range != 2
  6888. let blame = s:BlameCommitFileLnum(getline(a:count))
  6889. if len(blame[0])
  6890. let expanded = blame[0]
  6891. endif
  6892. endif
  6893. endif
  6894. let full = s:Generate(expanded, dir)
  6895. let commit = ''
  6896. let ref = ''
  6897. let forbid_ref_as_commit = 0
  6898. if full =~# '^fugitive:'
  6899. let [dir, commit, path] = s:DirCommitFile(full)
  6900. if commit =~# '^\d\=$'
  6901. let commit = ''
  6902. let type = path =~# '^/\=$' ? 'tree' : 'blob'
  6903. else
  6904. let ref_match = matchlist(expanded, '^\(@{\@!\|[^:~^@]\+\)\(:\%(//\)\@!\|[~^@]\|$\)')
  6905. let ref = get(ref_match, 1, '')
  6906. let forbid_ref_as_commit = ref =~# '^@\=$' || ref_match[2] !~# '^:\=$'
  6907. if empty(path) && !forbid_ref_as_commit
  6908. let type = 'ref'
  6909. else
  6910. let type = s:ChompDefault(empty(path) ? 'commit': 'blob',
  6911. \ ['cat-file', '-t', commit . substitute(path, '^/', ':', '')], dir)
  6912. endif
  6913. endif
  6914. let path = path[1:-1]
  6915. elseif empty(s:Tree(dir))
  6916. let path = '.git/' . full[strlen(dir)+1:-1]
  6917. let type = ''
  6918. else
  6919. let path = fugitive#Path(full, '/')[1:-1]
  6920. if empty(path) || isdirectory(full)
  6921. let type = 'tree'
  6922. else
  6923. let type = 'blob'
  6924. endif
  6925. endif
  6926. if path =~# '^\.git/'
  6927. let ref = matchstr(path, '^.git/\zs\%(refs/[^/]\+/[^/].*\|\w*HEAD\)$')
  6928. let type = empty(ref) ? 'root': 'ref'
  6929. let path = ''
  6930. endif
  6931. if empty(ref) || ref ==# 'HEAD' || ref ==# '@'
  6932. let ref = fugitive#Head(-1, dir)
  6933. endif
  6934. if ref =~# '^\x\{40,\}$'
  6935. let ref = ''
  6936. elseif !empty(ref) && ref !~# '^refs/'
  6937. let ref = FugitiveExecute(['rev-parse', '--symbolic-full-name', ref], dir).stdout[0]
  6938. if ref !~# '^refs/'
  6939. let ref = ''
  6940. endif
  6941. endif
  6942. let merge = ''
  6943. if !empty(remote) && ref =~# '^refs/remotes/[^/]\+/[^/]\|^refs/heads/[^/]'
  6944. let merge = matchstr(ref, '^refs/\%(heads/\|remotes/[^/]\+/\)\zs.\+')
  6945. let ref = 'refs/heads/' . merge
  6946. elseif ref =~# '^refs/remotes/[^/]\+/[^/]'
  6947. let remote = matchstr(ref, '^refs/remotes/\zs[^/]\+')
  6948. let merge = matchstr(ref, '^refs/remotes/[^/]\+/\zs.\+')
  6949. let ref = 'refs/heads/' . merge
  6950. elseif ref =~# '^refs/heads/[^/]'
  6951. let merge = strpart(ref, 11)
  6952. let r = FugitiveConfigGet('branch.' . merge . '.remote', config)
  6953. let m = FugitiveConfigGet('branch.' . merge . '.merge', config)[11:-1]
  6954. if r ==# '.' && !empty(m)
  6955. let r2 = FugitiveConfigGet('branch.'.m.'.remote', config)
  6956. if r2 !~# '^\.\=$'
  6957. let r = r2
  6958. let m = FugitiveConfigGet('branch.'.m.'.merge', config)[11:-1]
  6959. endif
  6960. endif
  6961. if r !~# '^\.\=$'
  6962. let remote = r
  6963. endif
  6964. if !empty(remote)
  6965. let remote_ref = 'refs/remotes/' . remote . '/' . merge
  6966. if FugitiveConfigGet('push.default', config) ==# 'upstream' ||
  6967. \ !filereadable(FugitiveFind('.git/' . remote_ref, dir)) && empty(s:ChompDefault('', ['rev-parse', '--verify', remote_ref, '--'], dir))
  6968. let merge = m
  6969. let ref = 'refs/heads/' . merge
  6970. endif
  6971. endif
  6972. endif
  6973. if empty(remote) || remote ==# '.'
  6974. let remote = s:RemoteDefault(config)
  6975. endif
  6976. if empty(merge) || empty(remote)
  6977. let provider_ref = ref
  6978. else
  6979. let provider_ref = 'refs/remotes/' . remote . '/' . merge
  6980. endif
  6981. if forbid_ref_as_commit || a:count >= 0
  6982. let ref = ''
  6983. if type ==# 'ref'
  6984. let type = 'commit'
  6985. endif
  6986. elseif type ==# 'ref' && ref =~# '^refs/\%(heads\|tags\)/[^/]'
  6987. let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
  6988. endif
  6989. let line1 = a:count > 0 && type ==# 'blob' ? a:line1 : 0
  6990. let line2 = a:count > 0 && type ==# 'blob' ? a:count : 0
  6991. if empty(commit) && type =~# '^\%(tree\|blob\)$'
  6992. if a:count < 0
  6993. let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
  6994. elseif len(provider_ref)
  6995. let owner = s:Owner(@%, dir)
  6996. let commit = s:ChompDefault('', ['merge-base', provider_ref, empty(owner) ? '@' : owner, '--'], dir)
  6997. if line2 > 0 && empty(arg) && commit =~# '^\x\{40,\}$' && type ==# 'blob'
  6998. let blame_list = tempname()
  6999. call writefile([commit, ''], blame_list, 'b')
  7000. let blame_cmd = ['-c', 'blame.coloring=none', 'blame', '-L', line1.','.line2, '-S', blame_list, '-s', '--show-number']
  7001. if !&l:modified || has_key(result, 'origin_bufnr')
  7002. let [blame, exec_error] = s:LinesError(blame_cmd + ['./' . path], dir)
  7003. else
  7004. let blame_in = tempname()
  7005. silent exe 'noautocmd keepalt %write' blame_in
  7006. let [blame, exec_error] = s:LinesError(blame_cmd + ['--contents', blame_in, './' . path], dir)
  7007. call delete(blame_in)
  7008. endif
  7009. call delete(blame_list)
  7010. if !exec_error
  7011. let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
  7012. if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
  7013. let line1 = +matchstr(blame[0], blame_regex)
  7014. let line2 = +matchstr(blame[-1], blame_regex)
  7015. else
  7016. throw "fugitive: can't browse to unpushed change"
  7017. endif
  7018. endif
  7019. endif
  7020. endif
  7021. if empty(commit)
  7022. let commit = fugitive#RevParse(empty(ref) ? 'HEAD' : ref, dir)
  7023. endif
  7024. endif
  7025. if remote =~# ':'
  7026. let remote_url = remote
  7027. else
  7028. let remote_url = fugitive#RemoteUrl(remote, config)
  7029. endif
  7030. let raw = empty(remote_url) ? remote : remote_url
  7031. let git_dir = s:GitDir(dir)
  7032. let opts = {
  7033. \ 'git_dir': git_dir,
  7034. \ 'repo': {'git_dir': git_dir},
  7035. \ 'remote': raw,
  7036. \ 'remote_name': remote,
  7037. \ 'commit': s:UrlEncode(commit),
  7038. \ 'path': substitute(s:UrlEncode(path), '%20', ' ', 'g'),
  7039. \ 'type': type,
  7040. \ 'line1': line1,
  7041. \ 'line2': line2}
  7042. if empty(path)
  7043. if type ==# 'ref' && ref =~# '^refs/'
  7044. let opts.path = '.git/' . s:UrlEncode(ref)
  7045. let opts.type = ''
  7046. elseif type ==# 'root'
  7047. let opts.path ='.git/index'
  7048. let opts.type = ''
  7049. endif
  7050. elseif type ==# 'tree' && !empty(path)
  7051. let opts.path = s:sub(opts.path, '/\=$', '/')
  7052. endif
  7053. for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
  7054. let l:.url = call(Handler, [copy(opts)])
  7055. if type(url) == type('') && url =~# '://'
  7056. return s:BrowserOpen(url, a:mods, a:bang)
  7057. endif
  7058. endfor
  7059. throw "fugitive: no GBrowse handler installed for '".raw."'"
  7060. catch /^fugitive:/
  7061. return 'echoerr ' . string(v:exception)
  7062. endtry
  7063. endfunction
  7064. " Section: Go to file
  7065. let s:ref_header = '\%(Merge\|Rebase\|Upstream\|Pull\|Push\)'
  7066. nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
  7067. function! fugitive#MapCfile(...) abort
  7068. exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
  7069. let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
  7070. if !exists('g:fugitive_no_maps')
  7071. call s:Map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
  7072. call s:Map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
  7073. call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
  7074. call s:Map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
  7075. call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<unique>', 1)
  7076. endif
  7077. endfunction
  7078. function! s:ContainingCommit() abort
  7079. let commit = s:Owner(@%)
  7080. return empty(commit) ? '@' : commit
  7081. endfunction
  7082. function! s:SquashArgument(...) abort
  7083. if &filetype == 'fugitive'
  7084. let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze \|^' . s:ref_header . ': \zs\S\+')
  7085. elseif has_key(s:temp_files, s:cpath(expand('%:p')))
  7086. let commit = matchstr(getline('.'), '\S\@<!\x\{4,\}\>')
  7087. else
  7088. let commit = s:Owner(@%)
  7089. endif
  7090. return len(commit) && a:0 ? printf(a:1, commit) : commit
  7091. endfunction
  7092. function! s:RebaseArgument() abort
  7093. return s:SquashArgument(' %s^')
  7094. endfunction
  7095. function! s:NavigateUp(count) abort
  7096. let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
  7097. let c = a:count
  7098. while c
  7099. if rev =~# ':.*/.'
  7100. let rev = matchstr(rev, '.*\ze/.\+', '')
  7101. elseif rev =~# '.:.'
  7102. let rev = matchstr(rev, '^.[^:]*:')
  7103. elseif rev =~# '^:'
  7104. let rev = '@^{}'
  7105. elseif rev =~# ':$'
  7106. let rev = rev[0:-2]
  7107. else
  7108. return rev.'~'.c
  7109. endif
  7110. let c -= 1
  7111. endwhile
  7112. return rev
  7113. endfunction
  7114. function! s:ParseDiffHeader(str) abort
  7115. let list = matchlist(a:str, '\Cdiff --git \("\=[^/].*\|/dev/null\) \("\=[^/].*\|/dev/null\)$')
  7116. return [fugitive#Unquote(get(list, 1, '')), fugitive#Unquote(get(list, 2, ''))]
  7117. endfunction
  7118. function! s:HunkPosition(lnum) abort
  7119. let lnum = a:lnum + get({'@': 1, '\': -1}, getline(a:lnum)[0], 0)
  7120. let offsets = {' ': -1, '+': 0, '-': 0}
  7121. let sigil = getline(lnum)[0]
  7122. let line_char = sigil
  7123. while has_key(offsets, line_char)
  7124. let offsets[line_char] += 1
  7125. let lnum -= 1
  7126. let line_char = getline(lnum)[0]
  7127. endwhile
  7128. let starts = matchlist(getline(lnum), '^@@\+[ 0-9,-]* -\(\d\+\),\d\+ +\(\d\+\),')
  7129. if empty(starts)
  7130. return [0, 0, 0]
  7131. endif
  7132. return [lnum,
  7133. \ sigil ==# '+' ? 0 : starts[1] + offsets[' '] + offsets['-'],
  7134. \ sigil ==# '-' ? 0 : starts[2] + offsets[' '] + offsets['+']]
  7135. endfunction
  7136. function! s:MapMotion(lhs, rhs) abort
  7137. let maps = [
  7138. \ s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
  7139. \ s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
  7140. \ s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")]
  7141. call filter(maps, '!empty(v:val)')
  7142. return join(maps, '|')
  7143. endfunction
  7144. function! fugitive#MapJumps(...) abort
  7145. if !&modifiable
  7146. if get(b:, 'fugitive_type', '') ==# 'blob'
  7147. let blame_tail = '<C-R>=v:count ? " --reverse" : ""<CR><CR>'
  7148. exe s:Map('n', '<2-LeftMouse>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
  7149. exe s:Map('n', '<CR>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
  7150. exe s:Map('n', 'o', ':<C-U>0,1Git blame' . blame_tail, '<silent>')
  7151. exe s:Map('n', 'p', ':<C-U>0,1Git blame!' . blame_tail, '<silent>')
  7152. if has('patch-7.4.1898')
  7153. exe s:Map('n', 'gO', ':<C-U>vertical 0,1Git blame' . blame_tail, '<silent>')
  7154. exe s:Map('n', 'O', ':<C-U>tab 0,1Git blame' . blame_tail, '<silent>')
  7155. else
  7156. exe s:Map('n', 'gO', ':<C-U>0,4Git blame' . blame_tail, '<silent>')
  7157. exe s:Map('n', 'O', ':<C-U>0,5Git blame' . blame_tail, '<silent>')
  7158. endif
  7159. call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
  7160. call s:Map('n', 'dd', ":<C-U>call fugitive#DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
  7161. call s:Map('n', 'dh', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
  7162. call s:Map('n', 'ds', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
  7163. call s:Map('n', 'dv', ":<C-U>call fugitive#DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
  7164. call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
  7165. else
  7166. call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
  7167. call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
  7168. call s:Map('n', 'o', ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
  7169. call s:Map('n', 'gO', ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
  7170. call s:Map('n', 'O', ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
  7171. call s:Map('n', 'p', ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
  7172. if !exists('g:fugitive_no_maps')
  7173. call s:Map('n', '<C-P>', ':exe <SID>PreviousItem(v:count1)<Bar>echohl WarningMsg<Bar>echo "CTRL-P is deprecated in favor of ("<Bar>echohl NONE<CR>', '<unique>')
  7174. call s:Map('n', '<C-N>', ':exe <SID>NextItem(v:count1)<Bar>echohl WarningMsg<Bar>echo "CTRL-N is deprecated in favor of )"<Bar>echohl NONE<CR>', '<unique>')
  7175. endif
  7176. call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
  7177. call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
  7178. call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
  7179. call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
  7180. call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
  7181. call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
  7182. call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
  7183. call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
  7184. call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
  7185. call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
  7186. call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
  7187. call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
  7188. call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
  7189. call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
  7190. call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
  7191. call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
  7192. endif
  7193. call s:Map('n', 'S', ':<C-U>echoerr "Use gO"<CR>', '<silent><unique>')
  7194. call s:Map('n', 'dq', ":<C-U>call fugitive#DiffClose()<CR>", '<silent>')
  7195. call s:Map('n', '-', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>NavigateUp(v:count1))<Bar> if getline(1) =~# '^tree \x\{40,\}$' && empty(getline(2))<Bar>call search('^'.escape(expand('#:t'),'.*[]~\').'/\=$','wc')<Bar>endif<CR>", '<silent>')
  7196. call s:Map('n', 'P', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
  7197. call s:Map('n', '~', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
  7198. call s:Map('n', 'C', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
  7199. call s:Map('n', 'cp', ":<C-U>echoerr 'Use gC'<CR>", '<silent><unique>')
  7200. call s:Map('n', 'gC', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
  7201. call s:Map('n', 'gc', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
  7202. call s:Map('n', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
  7203. call s:Map('x', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
  7204. call s:Map('n', 'c<Space>', ':Git commit<Space>')
  7205. call s:Map('n', 'c<CR>', ':Git commit<CR>')
  7206. call s:Map('n', 'cv<Space>', ':tab Git commit -v<Space>')
  7207. call s:Map('n', 'cv<CR>', ':tab Git commit -v<CR>')
  7208. call s:Map('n', 'ca', ':<C-U>Git commit --amend<CR>', '<silent>')
  7209. call s:Map('n', 'cc', ':<C-U>Git commit<CR>', '<silent>')
  7210. call s:Map('n', 'ce', ':<C-U>Git commit --amend --no-edit<CR>', '<silent>')
  7211. call s:Map('n', 'cw', ':<C-U>Git commit --amend --only<CR>', '<silent>')
  7212. call s:Map('n', 'cva', ':<C-U>tab Git commit -v --amend<CR>', '<silent>')
  7213. call s:Map('n', 'cvc', ':<C-U>tab Git commit -v<CR>', '<silent>')
  7214. call s:Map('n', 'cRa', ':<C-U>Git commit --reset-author --amend<CR>', '<silent>')
  7215. call s:Map('n', 'cRe', ':<C-U>Git commit --reset-author --amend --no-edit<CR>', '<silent>')
  7216. call s:Map('n', 'cRw', ':<C-U>Git commit --reset-author --amend --only<CR>', '<silent>')
  7217. call s:Map('n', 'cf', ':<C-U>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>')
  7218. call s:Map('n', 'cF', ':<C-U><Bar>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>')
  7219. call s:Map('n', 'cs', ':<C-U>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>')
  7220. call s:Map('n', 'cS', ':<C-U><Bar>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>')
  7221. call s:Map('n', 'cA', ':<C-U>Git commit --edit --squash=<C-R>=<SID>SquashArgument()<CR>')
  7222. call s:Map('n', 'c?', ':<C-U>help fugitive_c<CR>', '<silent>')
  7223. call s:Map('n', 'cr<Space>', ':Git revert<Space>')
  7224. call s:Map('n', 'cr<CR>', ':Git revert<CR>')
  7225. call s:Map('n', 'crc', ':<C-U>Git revert <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>')
  7226. call s:Map('n', 'crn', ':<C-U>Git revert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>')
  7227. call s:Map('n', 'cr?', ':<C-U>help fugitive_cr<CR>', '<silent>')
  7228. call s:Map('n', 'cm<Space>', ':Git merge<Space>')
  7229. call s:Map('n', 'cm<CR>', ':Git merge<CR>')
  7230. call s:Map('n', 'cmt', ':Git mergetool')
  7231. call s:Map('n', 'cm?', ':<C-U>help fugitive_cm<CR>', '<silent>')
  7232. call s:Map('n', 'cz<Space>', ':Git stash<Space>')
  7233. call s:Map('n', 'cz<CR>', ':Git stash<CR>')
  7234. call s:Map('n', 'cza', ':<C-U>Git stash apply --quiet --index stash@{<C-R>=v:count<CR>}<CR>')
  7235. call s:Map('n', 'czA', ':<C-U>Git stash apply --quiet stash@{<C-R>=v:count<CR>}<CR>')
  7236. call s:Map('n', 'czp', ':<C-U>Git stash pop --quiet --index stash@{<C-R>=v:count<CR>}<CR>')
  7237. call s:Map('n', 'czP', ':<C-U>Git stash pop --quiet stash@{<C-R>=v:count<CR>}<CR>')
  7238. call s:Map('n', 'czs', ':<C-U>Git stash push --staged<CR>')
  7239. call s:Map('n', 'czv', ':<C-U>exe "Gedit" fugitive#RevParse("stash@{" . v:count . "}")<CR>', '<silent>')
  7240. call s:Map('n', 'czw', ':<C-U>Git stash push --keep-index<C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>')
  7241. call s:Map('n', 'czz', ':<C-U>Git stash push <C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>')
  7242. call s:Map('n', 'cz?', ':<C-U>help fugitive_cz<CR>', '<silent>')
  7243. call s:Map('n', 'co<Space>', ':Git checkout<Space>')
  7244. call s:Map('n', 'co<CR>', ':Git checkout<CR>')
  7245. call s:Map('n', 'coo', ':<C-U>Git checkout <C-R>=substitute(<SID>SquashArgument(),"^$",get(<SID>TempState(),"filetype","") ==# "git" ? expand("<cfile>") : "","")<CR> --<CR>')
  7246. call s:Map('n', 'co?', ':<C-U>help fugitive_co<CR>', '<silent>')
  7247. call s:Map('n', 'cb<Space>', ':Git branch<Space>')
  7248. call s:Map('n', 'cb<CR>', ':Git branch<CR>')
  7249. call s:Map('n', 'cb?', ':<C-U>help fugitive_cb<CR>', '<silent>')
  7250. call s:Map('n', 'r<Space>', ':Git rebase<Space>')
  7251. call s:Map('n', 'r<CR>', ':Git rebase<CR>')
  7252. call s:Map('n', 'ri', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>')
  7253. call s:Map('n', 'rf', ':<C-U>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>')
  7254. call s:Map('n', 'ru', ':<C-U>Git rebase --interactive @{upstream}<CR>', '<silent>')
  7255. call s:Map('n', 'rp', ':<C-U>Git rebase --interactive @{push}<CR>', '<silent>')
  7256. call s:Map('n', 'rw', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>', '<silent>')
  7257. call s:Map('n', 'rm', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>', '<silent>')
  7258. call s:Map('n', 'rd', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>')
  7259. call s:Map('n', 'rk', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>')
  7260. call s:Map('n', 'rx', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>')
  7261. call s:Map('n', 'rr', ':<C-U>Git rebase --continue<CR>', '<silent>')
  7262. call s:Map('n', 'rs', ':<C-U>Git rebase --skip<CR>', '<silent>')
  7263. call s:Map('n', 're', ':<C-U>Git rebase --edit-todo<CR>', '<silent>')
  7264. call s:Map('n', 'ra', ':<C-U>Git rebase --abort<CR>', '<silent>')
  7265. call s:Map('n', 'r?', ':<C-U>help fugitive_r<CR>', '<silent>')
  7266. call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
  7267. call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
  7268. call s:Map('n', 'g?', ":<C-U>help fugitive-map<CR>", '<silent>')
  7269. call s:Map('n', '<F1>', ":<C-U>help fugitive-map<CR>", '<silent>')
  7270. endif
  7271. let old_browsex = maparg('<Plug>NetrwBrowseX', 'n')
  7272. let new_browsex = substitute(old_browsex, '\Cnetrw#CheckIfRemote(\%(netrw#GX()\)\=)', '0', 'g')
  7273. let new_browsex = substitute(new_browsex, 'netrw#GX()\|expand((exists("g:netrw_gx")? g:netrw_gx : ''<cfile>''))', 'fugitive#GX()', 'g')
  7274. if new_browsex !=# old_browsex
  7275. exe 'nnoremap <silent> <buffer> <Plug>NetrwBrowseX' new_browsex
  7276. endif
  7277. endfunction
  7278. function! fugitive#GX() abort
  7279. try
  7280. let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'git' ? s:cfile() : []
  7281. if len(results) && len(results[0])
  7282. return FugitiveReal(s:Generate(results[0]))
  7283. endif
  7284. catch /^fugitive:/
  7285. endtry
  7286. return expand(get(g:, 'netrw_gx', expand('<cfile>')))
  7287. endfunction
  7288. function! s:CfilePorcelain(...) abort
  7289. let tree = s:Tree()
  7290. if empty(tree)
  7291. return ['']
  7292. endif
  7293. let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
  7294. let info = s:StageInfo()
  7295. let line = getline('.')
  7296. if len(info.sigil) && len(info.section) && len(info.paths)
  7297. if info.section ==# 'Unstaged' && info.sigil !=# '-'
  7298. return [lead . info.relative[0], info.offset, 'normal!zv']
  7299. elseif info.section ==# 'Staged' && info.sigil ==# '-'
  7300. return ['@:' . info.relative[0], info.offset, 'normal!zv']
  7301. else
  7302. return [':0:' . info.relative[0], info.offset, 'normal!zv']
  7303. endif
  7304. elseif len(info.paths)
  7305. return [lead . info.relative[0]]
  7306. elseif len(info.commit)
  7307. return [info.commit]
  7308. elseif line =~# '^' . s:ref_header . ': \|^Head: '
  7309. return [matchstr(line, ' \zs.*')]
  7310. else
  7311. return ['']
  7312. endif
  7313. endfunction
  7314. function! fugitive#PorcelainCfile() abort
  7315. let file = fugitive#Find(s:CfilePorcelain()[0])
  7316. return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
  7317. endfunction
  7318. function! s:StatusCfile(...) abort
  7319. let tree = s:Tree()
  7320. if empty(tree)
  7321. return []
  7322. endif
  7323. let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
  7324. if getline('.') =~# '^.\=\trenamed:.* -> '
  7325. return [lead . matchstr(getline('.'),' -> \zs.*')]
  7326. elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
  7327. return [lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')]
  7328. elseif getline('.') =~# '^.\=\t.'
  7329. return [lead . matchstr(getline('.'),'\t\zs.*')]
  7330. elseif getline('.') =~# ': needs merge$'
  7331. return [lead . matchstr(getline('.'),'.*\ze: needs merge$')]
  7332. elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
  7333. return ['HEAD']
  7334. elseif getline('.') =~# '^\%(. \)\=On branch '
  7335. return ['refs/heads/'.getline('.')[12:]]
  7336. elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
  7337. return [matchstr(getline('.'),"'\\zs\\S\\+\\ze'")]
  7338. else
  7339. return []
  7340. endif
  7341. endfunction
  7342. function! fugitive#MessageCfile() abort
  7343. let file = fugitive#Find(get(s:StatusCfile(), 0, ''))
  7344. return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
  7345. endfunction
  7346. function! s:BranchCfile(result) abort
  7347. return matchstr(getline('.'), '^. \zs\S\+')
  7348. endfunction
  7349. let s:diff_header_pattern = '^diff --git \%("\=[abciow12]/.*\|/dev/null\) \%("\=[abciow12]/.*\|/dev/null\)$'
  7350. function! s:cfile() abort
  7351. let temp_state = s:TempState()
  7352. let name = substitute(get(get(temp_state, 'args', []), 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
  7353. if exists('*s:' . name . 'Cfile')
  7354. let cfile = s:{name}Cfile(temp_state)
  7355. if !empty(cfile)
  7356. return type(cfile) == type('') ? [cfile] : cfile
  7357. endif
  7358. endif
  7359. if empty(FugitiveGitDir())
  7360. return []
  7361. endif
  7362. try
  7363. let myhash = s:DirRev(@%)[1]
  7364. if len(myhash)
  7365. try
  7366. let myhash = fugitive#RevParse(myhash)
  7367. catch /^fugitive:/
  7368. let myhash = ''
  7369. endtry
  7370. endif
  7371. if empty(myhash) && get(temp_state, 'filetype', '') ==# 'git'
  7372. let lnum = line('.')
  7373. while lnum > 0
  7374. if getline(lnum) =~# '^\%(commit\|tag\) \w'
  7375. let myhash = matchstr(getline(lnum),'^\w\+ \zs\S\+')
  7376. break
  7377. endif
  7378. let lnum -= 1
  7379. endwhile
  7380. endif
  7381. let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
  7382. let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
  7383. \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
  7384. if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
  7385. return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
  7386. elseif showtree
  7387. return [treebase . s:sub(getline('.'),'/$','')]
  7388. else
  7389. let dcmds = []
  7390. " Index
  7391. if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
  7392. let ref = matchstr(getline('.'),'\x\{40,\}')
  7393. let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
  7394. return [file]
  7395. endif
  7396. if getline('.') =~# '^ref: '
  7397. let ref = strpart(getline('.'),5)
  7398. elseif getline('.') =~# '^\%([|/\\_ ]*\*[|/\\_ ]*\)\=commit \x\{40,\}\>'
  7399. let ref = matchstr(getline('.'),'\x\{40,\}')
  7400. return [ref]
  7401. elseif getline('.') =~# '^parent \x\{40,\}\>'
  7402. let ref = matchstr(getline('.'),'\x\{40,\}')
  7403. let line = line('.')
  7404. let parent = 0
  7405. while getline(line) =~# '^parent '
  7406. let parent += 1
  7407. let line -= 1
  7408. endwhile
  7409. return [ref]
  7410. elseif getline('.') =~# '^tree \x\{40,\}$'
  7411. let ref = matchstr(getline('.'),'\x\{40,\}')
  7412. if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
  7413. let ref = myhash.':'
  7414. endif
  7415. return [ref]
  7416. elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
  7417. let ref = matchstr(getline('.'),'\x\{40,\}')
  7418. let type = matchstr(getline(line('.')+1),'type \zs.*')
  7419. elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
  7420. let ref = s:DirRev(@%)[1]
  7421. elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
  7422. let ref = matchstr(getline('.'),'\x\{40,\}')
  7423. echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
  7424. elseif getline('.') =~# '^[A-Z]\d*\t\S' && len(myhash)
  7425. let files = split(getline('.'), "\t")[1:-1]
  7426. let ref = 'b/' . files[-1]
  7427. if getline('.') =~# '^D'
  7428. let ref = 'a/' . files[0]
  7429. elseif getline('.') !~# '^A'
  7430. let dcmds = ['', 'Gdiffsplit! >' . myhash . '^:' . fnameescape(files[0])]
  7431. endif
  7432. elseif getline('.') =~# '^[+-]'
  7433. let [header_lnum, old_lnum, new_lnum] = s:HunkPosition(line('.'))
  7434. if new_lnum > 0
  7435. let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[1]
  7436. let dcmds = [new_lnum, 'normal!zv']
  7437. elseif old_lnum > 0
  7438. let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[0]
  7439. let dcmds = [old_lnum, 'normal!zv']
  7440. else
  7441. let ref = fugitive#Unquote(matchstr(getline('.'), '\C[+-]\{3\} \zs"\=[abciow12]/.*'))
  7442. endif
  7443. elseif getline('.') =~# '^rename from '
  7444. let ref = 'a/'.getline('.')[12:]
  7445. elseif getline('.') =~# '^rename to '
  7446. let ref = 'b/'.getline('.')[10:]
  7447. elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
  7448. let diff = getline(search(s:diff_header_pattern, 'bcnW'))
  7449. let offset = matchstr(getline('.'), '+\zs\d\+')
  7450. let [dref, ref] = s:ParseDiffHeader(diff)
  7451. let dcmd = 'Gdiffsplit! +'.offset
  7452. elseif getline('.') =~# s:diff_header_pattern
  7453. let [dref, ref] = s:ParseDiffHeader(getline('.'))
  7454. let dcmd = 'Gdiffsplit!'
  7455. elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# s:diff_header_pattern
  7456. let [dref, ref] = s:ParseDiffHeader(getline(line('.') - '.'))
  7457. let dcmd = 'Gdiffsplit!'
  7458. elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
  7459. let ref = getline('.')
  7460. elseif expand('<cword>') =~# '^\x\{7,\}\>'
  7461. return [expand('<cword>')]
  7462. else
  7463. let ref = ''
  7464. endif
  7465. let prefixes = {
  7466. \ '1': '',
  7467. \ '2': '',
  7468. \ 'b': ':0:',
  7469. \ 'i': ':0:',
  7470. \ 'o': '',
  7471. \ 'w': ''}
  7472. if len(myhash)
  7473. let prefixes.a = myhash.'^:'
  7474. let prefixes.b = myhash.':'
  7475. endif
  7476. let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
  7477. if exists('dref')
  7478. let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
  7479. endif
  7480. if ref ==# '/dev/null'
  7481. " Empty blob
  7482. let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
  7483. endif
  7484. if exists('dref')
  7485. return [ref, dcmd . ' >' . s:fnameescape(dref)] + dcmds
  7486. elseif ref != ""
  7487. return [ref] + dcmds
  7488. endif
  7489. endif
  7490. return []
  7491. endtry
  7492. endfunction
  7493. function! s:GF(mode) abort
  7494. try
  7495. let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'gitcommit' ? s:StatusCfile() : s:cfile()
  7496. catch /^fugitive:/
  7497. return 'echoerr ' . string(v:exception)
  7498. endtry
  7499. if len(results) > 1
  7500. let cmd = 'G' . a:mode .
  7501. \ (empty(results[1]) ? '' : ' +' . s:PlusEscape(results[1])) . ' ' .
  7502. \ fnameescape(results[0])
  7503. let tail = join(map(results[2:-1], '"|" . v:val'), '')
  7504. if a:mode ==# 'pedit' && len(tail)
  7505. return cmd . '|wincmd P|exe ' . string(tail[1:-1]) . '|wincmd p'
  7506. else
  7507. return cmd . tail
  7508. endif
  7509. elseif len(results) && len(results[0])
  7510. return 'G' . a:mode . ' ' . s:fnameescape(results[0])
  7511. else
  7512. return ''
  7513. endif
  7514. endfunction
  7515. function! fugitive#Cfile() abort
  7516. let pre = ''
  7517. let results = s:cfile()
  7518. if empty(results)
  7519. if !empty(s:TempState())
  7520. let cfile = s:TempDotMap()
  7521. if !empty(cfile)
  7522. return fnameescape(s:Generate(cfile))
  7523. endif
  7524. endif
  7525. let cfile = expand('<cfile>')
  7526. if &includeexpr =~# '\<v:fname\>'
  7527. sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
  7528. endif
  7529. return cfile
  7530. elseif len(results) > 1
  7531. let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
  7532. endif
  7533. return pre . fnameescape(s:Generate(results[0]))
  7534. endfunction
  7535. " Section: Statusline
  7536. function! fugitive#Statusline(...) abort
  7537. let dir = s:Dir(bufnr(''))
  7538. if empty(dir)
  7539. return ''
  7540. endif
  7541. let status = ''
  7542. let commit = s:DirCommitFile(@%)[1]
  7543. if len(commit)
  7544. let status .= ':' . commit[0:6]
  7545. endif
  7546. let status .= '('.fugitive#Head(7, dir).')'
  7547. return '[Git'.status.']'
  7548. endfunction
  7549. function! fugitive#statusline(...) abort
  7550. return fugitive#Statusline()
  7551. endfunction
  7552. " Section: Folding
  7553. function! fugitive#Foldtext() abort
  7554. if &foldmethod !=# 'syntax'
  7555. return foldtext()
  7556. endif
  7557. let line_foldstart = getline(v:foldstart)
  7558. if line_foldstart =~# '^diff '
  7559. let [add, remove] = [-1, -1]
  7560. let filename = ''
  7561. for lnum in range(v:foldstart, v:foldend)
  7562. let line = getline(lnum)
  7563. if filename ==# '' && line =~# '^[+-]\{3\} "\=[abciow12]/'
  7564. let filename = fugitive#Unquote(line[4:-1])[2:-1]
  7565. endif
  7566. if line =~# '^+'
  7567. let add += 1
  7568. elseif line =~# '^-'
  7569. let remove += 1
  7570. elseif line =~# '^Binary '
  7571. let binary = 1
  7572. endif
  7573. endfor
  7574. if filename ==# ''
  7575. let filename = fugitive#Unquote(matchstr(line_foldstart, '^diff .\{-\} \zs"\=[abciow12]/\zs.*\ze "\=[abciow12]/'))[2:-1]
  7576. endif
  7577. if filename ==# ''
  7578. let filename = line_foldstart[5:-1]
  7579. endif
  7580. if exists('binary')
  7581. return 'Binary: '.filename
  7582. else
  7583. return '+-' . v:folddashes . ' ' . (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
  7584. endif
  7585. elseif line_foldstart =~# '^@@\+ .* @@'
  7586. return '+-' . v:folddashes . ' ' . line_foldstart
  7587. elseif &filetype ==# 'gitcommit' && line_foldstart =~# '^# .*:$'
  7588. let lines = getline(v:foldstart, v:foldend)
  7589. call filter(lines, 'v:val =~# "^#\t"')
  7590. cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
  7591. cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
  7592. return line_foldstart.' '.join(lines, ', ')
  7593. endif
  7594. return foldtext()
  7595. endfunction
  7596. function! fugitive#foldtext() abort
  7597. return fugitive#Foldtext()
  7598. endfunction
  7599. " Section: End