fugitive.vim 284 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428
  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. if exists('b:git_dir') && empty(b:git_dir)
  102. unlet! b:git_dir
  103. endif
  104. return ''
  105. endif
  106. endfunction
  107. let s:worktree_error = "core.worktree is required when using an external Git dir"
  108. function! s:DirCheck(...) abort
  109. let dir = call('FugitiveGitDir', a:000)
  110. if !empty(dir) && FugitiveWorkTree(dir, 1) is# 0
  111. return 'return ' . string('echoerr "fugitive: ' . s:worktree_error . '"')
  112. elseif !empty(dir)
  113. return ''
  114. elseif empty(bufname(''))
  115. return 'return ' . string('echoerr "fugitive: working directory does not belong to a Git repository"')
  116. else
  117. return 'return ' . string('echoerr "fugitive: file does not belong to a Git repository"')
  118. endif
  119. endfunction
  120. function! s:Mods(mods, ...) abort
  121. let mods = substitute(a:mods, '\C<mods>', '', '')
  122. let mods = mods =~# '\S$' ? mods . ' ' : mods
  123. if a:0 && mods !~# '\<\d*\%(aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab\)\>'
  124. let default = a:1
  125. if default ==# 'SpanOrigin'
  126. if s:OriginBufnr() > 0 && (mods =~# '\<vertical\>' ? &winfixheight : &winfixwidth)
  127. let default = 'Edge'
  128. else
  129. let default = ''
  130. endif
  131. endif
  132. if default ==# 'Edge'
  133. if mods =~# '\<vertical\>' ? &splitright : &splitbelow
  134. let mods = 'botright ' . mods
  135. else
  136. let mods = 'topleft ' . mods
  137. endif
  138. else
  139. let mods = default . ' ' . mods
  140. endif
  141. endif
  142. return substitute(mods, '\s\+', ' ', 'g')
  143. endfunction
  144. if exists('+shellslash')
  145. let s:dir_commit_file = '\c^fugitive://\%(/[^/]\@=\)\=\([^?#]\{-1,\}\)//\%(\(\x\{40,\}\|[0-3]\)\(/[^?#]*\)\=\)\=$'
  146. function! s:Slash(path) abort
  147. return tr(a:path, '\', '/')
  148. endfunction
  149. function! s:VimSlash(path) abort
  150. return tr(a:path, '\/', &shellslash ? '//' : '\\')
  151. endfunction
  152. else
  153. let s:dir_commit_file = '\c^fugitive://\([^?#]\{-\}\)//\%(\(\x\{40,\}\|[0-3]\)\(/[^?#]*\)\=\)\=$'
  154. function! s:Slash(path) abort
  155. return a:path
  156. endfunction
  157. function! s:VimSlash(path) abort
  158. return a:path
  159. endfunction
  160. endif
  161. function! s:AbsoluteVimPath(...) abort
  162. if a:0 && type(a:1) == type('')
  163. let path = a:1
  164. else
  165. let path = bufname(a:0 && a:1 > 0 ? a:1 : '')
  166. if getbufvar(a:0 && a:1 > 0 ? a:1 : '', '&buftype') !~# '^\%(nowrite\|acwrite\)\=$'
  167. return path
  168. endif
  169. endif
  170. if s:Slash(path) =~# '^/\|^\a\+:'
  171. return path
  172. else
  173. return getcwd() . matchstr(getcwd(), '[\\/]') . path
  174. endif
  175. endfunction
  176. function! s:Resolve(path) abort
  177. let path = resolve(a:path)
  178. if has('win32')
  179. let path = s:VimSlash(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
  180. endif
  181. return path
  182. endfunction
  183. function! s:FileIgnoreCase(for_completion) abort
  184. return (exists('+fileignorecase') && &fileignorecase)
  185. \ || (a:for_completion && exists('+wildignorecase') && &wildignorecase)
  186. endfunction
  187. function! s:cpath(path, ...) abort
  188. if s:FileIgnoreCase(0)
  189. let path = s:VimSlash(tolower(a:path))
  190. else
  191. let path = s:VimSlash(a:path)
  192. endif
  193. return a:0 ? path ==# s:cpath(a:1) : path
  194. endfunction
  195. let s:quote_chars = {
  196. \ "\007": 'a', "\010": 'b', "\011": 't', "\012": 'n', "\013": 'v', "\014": 'f', "\015": 'r',
  197. \ '"': '"', '\': '\'}
  198. let s:unquote_chars = {
  199. \ 'a': "\007", 'b': "\010", 't': "\011", 'n': "\012", 'v': "\013", 'f': "\014", 'r': "\015",
  200. \ '"': '"', '\': '\'}
  201. function! s:Quote(string) abort
  202. let string = substitute(a:string, "[\001-\037\"\\\177]", '\="\\" . get(s:quote_chars, submatch(0), printf("%03o", char2nr(submatch(0))))', 'g')
  203. if string !=# a:string
  204. return '"' . string . '"'
  205. else
  206. return string
  207. endif
  208. endfunction
  209. function! fugitive#Unquote(string) abort
  210. let string = substitute(a:string, "\t*$", '', '')
  211. if string =~# '^".*"$'
  212. return substitute(string[1:-2], '\\\(\o\o\o\|.\)', '\=get(s:unquote_chars, submatch(1), iconv(nr2char("0" . submatch(1)), "utf-8", "latin1"))', 'g')
  213. else
  214. return string
  215. endif
  216. endfunction
  217. let s:executables = {}
  218. function! s:executable(binary) abort
  219. if !has_key(s:executables, a:binary)
  220. let s:executables[a:binary] = executable(a:binary)
  221. endif
  222. return s:executables[a:binary]
  223. endfunction
  224. if !exists('s:temp_scripts')
  225. let s:temp_scripts = {}
  226. endif
  227. function! s:TempScript(...) abort
  228. let body = join(a:000, "\n")
  229. if !has_key(s:temp_scripts, body)
  230. let s:temp_scripts[body] = tempname() . '.sh'
  231. endif
  232. let temp = s:temp_scripts[body]
  233. if !filereadable(temp)
  234. call writefile(['#!/bin/sh'] + a:000, temp)
  235. endif
  236. let temp = FugitiveGitPath(temp)
  237. if temp =~# '\s'
  238. let temp = '"' . temp . '"'
  239. endif
  240. return temp
  241. endfunction
  242. function! s:DoAutocmd(...) abort
  243. return join(map(copy(a:000), "'doautocmd <nomodeline>' . v:val"), '|')
  244. endfunction
  245. function! s:Map(mode, lhs, rhs, ...) abort
  246. let maps = []
  247. let flags = a:0 && type(a:1) == type('') ? a:1 : ''
  248. let defer = flags =~# '<unique>'
  249. let flags = substitute(flags, '<unique>', '', '') . (a:rhs =~# '<Plug>' ? '' : '<script>') . '<nowait>'
  250. for mode in split(a:mode, '\zs')
  251. if a:0 <= 1
  252. call add(maps, mode.'map <buffer>' . substitute(flags, '<unique>', '', '') . ' <Plug>fugitive:' . a:lhs . ' ' . a:rhs)
  253. endif
  254. let skip = 0
  255. let head = a:lhs
  256. let tail = ''
  257. let keys = get(g:, mode.'remap', {})
  258. if type(keys) == type([])
  259. continue
  260. endif
  261. while !empty(head)
  262. if has_key(keys, head)
  263. let head = keys[head]
  264. let skip = empty(head)
  265. break
  266. endif
  267. let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
  268. let head = substitute(head, '<[^<>]*>$\|.$', '', '')
  269. endwhile
  270. if !skip && (!defer || empty(mapcheck(head.tail, mode)))
  271. call add(maps, mode.'map <buffer>' . flags . ' ' . head.tail . ' ' . a:rhs)
  272. if a:0 > 1 && a:2
  273. let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
  274. \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
  275. endif
  276. endif
  277. endfor
  278. exe join(maps, '|')
  279. return ''
  280. endfunction
  281. function! fugitive#Autowrite() abort
  282. if &autowrite || &autowriteall
  283. try
  284. if &confirm
  285. let reconfirm = 1
  286. setglobal noconfirm
  287. endif
  288. silent! wall
  289. finally
  290. if exists('reconfirm')
  291. setglobal confirm
  292. endif
  293. endtry
  294. endif
  295. return ''
  296. endfunction
  297. function! fugitive#Wait(job_or_jobs, ...) abort
  298. let original = type(a:job_or_jobs) == type([]) ? copy(a:job_or_jobs) : [a:job_or_jobs]
  299. let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
  300. call filter(jobs, 'type(v:val) !=# type("")')
  301. let timeout_ms = a:0 ? a:1 : -1
  302. if exists('*jobwait')
  303. call map(copy(jobs), 'chanclose(v:val, "stdin")')
  304. call jobwait(jobs, timeout_ms)
  305. let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
  306. call filter(jobs, 'type(v:val) !=# type("")')
  307. if len(jobs)
  308. sleep 1m
  309. endif
  310. else
  311. for job in jobs
  312. if ch_status(job) ==# 'open'
  313. call ch_close_in(job)
  314. endif
  315. endfor
  316. let i = 0
  317. for job in jobs
  318. while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
  319. if i == timeout_ms
  320. break
  321. endif
  322. let i += 1
  323. sleep 1m
  324. endwhile
  325. endfor
  326. endif
  327. return a:job_or_jobs
  328. endfunction
  329. function! s:JobVimExit(dict, callback, temp, job, status) abort
  330. let a:dict.exit_status = a:status
  331. let a:dict.stderr = readfile(a:temp . '.err', 'b')
  332. call delete(a:temp . '.err')
  333. let a:dict.stdout = readfile(a:temp . '.out', 'b')
  334. call delete(a:temp . '.out')
  335. call delete(a:temp . '.in')
  336. call remove(a:dict, 'job')
  337. call call(a:callback[0], [a:dict] + a:callback[1:-1])
  338. endfunction
  339. function! s:JobNvimExit(dict, callback, job, data, type) dict abort
  340. let a:dict.stdout = self.stdout
  341. let a:dict.stderr = self.stderr
  342. let a:dict.exit_status = a:data
  343. call remove(a:dict, 'job')
  344. call call(a:callback[0], [a:dict] + a:callback[1:-1])
  345. endfunction
  346. function! s:JobExecute(argv, jopts, stdin, callback, ...) abort
  347. let dict = a:0 ? a:1 : {}
  348. let cb = len(a:callback) ? a:callback : [function('len')]
  349. if exists('*jobstart')
  350. call extend(a:jopts, {
  351. \ 'stdout_buffered': v:true,
  352. \ 'stderr_buffered': v:true,
  353. \ 'on_exit': function('s:JobNvimExit', [dict, cb])})
  354. try
  355. let dict.job = jobstart(a:argv, a:jopts)
  356. if !empty(a:stdin)
  357. call chansend(dict.job, a:stdin)
  358. endif
  359. call chanclose(dict.job, 'stdin')
  360. catch /^Vim\%((\a\+)\)\=:E475:/
  361. let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
  362. endtry
  363. elseif exists('*ch_close_in')
  364. let temp = tempname()
  365. call extend(a:jopts, {
  366. \ 'out_io': 'file',
  367. \ 'out_name': temp . '.out',
  368. \ 'err_io': 'file',
  369. \ 'err_name': temp . '.err',
  370. \ 'exit_cb': function('s:JobVimExit', [dict, cb, temp])})
  371. if a:stdin ==# ['']
  372. let a:jopts.in_io = 'null'
  373. elseif !empty(a:stdin)
  374. let a:jopts.in_io = 'file'
  375. let a:jopts.in_name = temp . '.in'
  376. call writefile(a:stdin, a:jopts.in_name, 'b')
  377. endif
  378. let dict.job = job_start(a:argv, a:jopts)
  379. if job_status(dict.job) ==# 'fail'
  380. let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
  381. unlet dict.job
  382. endif
  383. elseif &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
  384. throw 'fugitive: Vim 8 or higher required to use ' . &shell
  385. else
  386. let cmd = s:shellesc(a:argv)
  387. let outfile = tempname()
  388. try
  389. if len(a:stdin)
  390. call writefile(a:stdin, outfile . '.in', 'b')
  391. let cmd = ' (' . cmd . ' >' . outfile . ' <' . outfile . '.in) '
  392. else
  393. let cmd = ' (' . cmd . ' >' . outfile . ') '
  394. endif
  395. let dict.stderr = split(system(cmd), "\n", 1)
  396. let dict.exit_status = v:shell_error
  397. let dict.stdout = readfile(outfile, 'b')
  398. call call(cb[0], [dict] + cb[1:-1])
  399. finally
  400. call delete(outfile)
  401. call delete(outfile . '.in')
  402. endtry
  403. endif
  404. if empty(a:callback)
  405. call fugitive#Wait(dict)
  406. endif
  407. return dict
  408. endfunction
  409. function! s:add_methods(namespace, method_names) abort
  410. for name in a:method_names
  411. let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
  412. endfor
  413. endfunction
  414. " Section: Git
  415. let s:run_jobs = (exists('*ch_close_in') || exists('*jobstart')) && exists('*bufwinid')
  416. function! s:GitCmd() abort
  417. if !exists('g:fugitive_git_executable')
  418. return ['git']
  419. elseif type(g:fugitive_git_executable) == type([])
  420. return g:fugitive_git_executable
  421. else
  422. let dquote = '"\%([^"]\|""\|\\"\)*"\|'
  423. let string = g:fugitive_git_executable
  424. let list = []
  425. if string =~# '^\w\+='
  426. call add(list, '/usr/bin/env')
  427. endif
  428. while string =~# '\S'
  429. let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
  430. let string = strpart(string, len(arg))
  431. let arg = substitute(arg, '^\s\+', '', '')
  432. let arg = substitute(arg,
  433. \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\)\|' . s:expand,
  434. \ '\=submatch(0)[0] ==# "\\" ? submatch(0)[1] : submatch(0)[1:-2]', 'g')
  435. call add(list, arg)
  436. endwhile
  437. return list
  438. endif
  439. endfunction
  440. function! s:GitShellCmd() abort
  441. if !exists('g:fugitive_git_executable')
  442. return 'git'
  443. elseif type(g:fugitive_git_executable) == type([])
  444. return s:shellesc(g:fugitive_git_executable)
  445. else
  446. return g:fugitive_git_executable
  447. endif
  448. endfunction
  449. function! s:UserCommandCwd(dir) abort
  450. let tree = s:Tree(a:dir)
  451. return len(tree) ? s:VimSlash(tree) : getcwd()
  452. endfunction
  453. function! s:UserCommandList(...) abort
  454. if !fugitive#GitVersion(1, 8, 5)
  455. throw 'fugitive: Git 1.8.5 or higher required'
  456. endif
  457. if !exists('g:fugitive_git_command')
  458. let git = s:GitCmd()
  459. elseif type(g:fugitive_git_command) == type([])
  460. let git = g:fugitive_git_command
  461. else
  462. let git = split(g:fugitive_git_command, '\s\+')
  463. endif
  464. let flags = []
  465. if a:0 && type(a:1) == type({})
  466. let git = copy(get(a:1, 'git', git))
  467. let flags = get(a:1, 'flags', flags)
  468. let dir = a:1.git_dir
  469. elseif a:0
  470. let dir = s:GitDir(a:1)
  471. else
  472. let dir = ''
  473. endif
  474. if len(dir)
  475. let tree = s:Tree(dir)
  476. if empty(tree)
  477. call add(git, '--git-dir=' . FugitiveGitPath(dir))
  478. else
  479. if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
  480. call add(git, '--git-dir=' . FugitiveGitPath(dir))
  481. endif
  482. if !s:cpath(tree, getcwd())
  483. call extend(git, ['-C', FugitiveGitPath(tree)])
  484. endif
  485. endif
  486. endif
  487. return git + flags
  488. endfunction
  489. let s:git_versions = {}
  490. function! fugitive#GitVersion(...) abort
  491. let git = s:GitShellCmd()
  492. if !has_key(s:git_versions, git)
  493. let s:git_versions[git] = matchstr(get(s:JobExecute(s:GitCmd() + ['--version'], {}, [], [], {}).stdout, 0, ''), '\d[^[:space:]]\+')
  494. endif
  495. if !a:0
  496. return s:git_versions[git]
  497. endif
  498. let components = split(s:git_versions[git], '\D\+')
  499. if empty(components)
  500. return -1
  501. endif
  502. for i in range(len(a:000))
  503. if a:000[i] > +get(components, i)
  504. return 0
  505. elseif a:000[i] < +get(components, i)
  506. return 1
  507. endif
  508. endfor
  509. return a:000[i] ==# get(components, i)
  510. endfunction
  511. function! s:Dir(...) abort
  512. return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
  513. endfunction
  514. function! s:GitDir(...) abort
  515. return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
  516. endfunction
  517. function! s:InitializeBuffer(repo) abort
  518. let b:git_dir = s:GitDir(a:repo)
  519. endfunction
  520. function! s:SameRepo(one, two) abort
  521. let one = s:GitDir(a:one)
  522. return !empty(one) && one ==# s:GitDir(a:two)
  523. endfunction
  524. if exists('+shellslash')
  525. function! s:DirUrlPrefix(dir) abort
  526. let gd = s:GitDir(a:dir)
  527. return 'fugitive://' . (gd =~# '^[^/]' ? '/' : '') . s:PathUrlEncode(gd) . '//'
  528. endfunction
  529. else
  530. function! s:DirUrlPrefix(dir) abort
  531. return 'fugitive://' . s:PathUrlEncode(s:GitDir(a:dir)) . '//'
  532. endfunction
  533. endif
  534. function! s:Tree(...) abort
  535. return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
  536. endfunction
  537. function! s:HasOpt(args, ...) abort
  538. let args = a:args[0 : index(a:args, '--')]
  539. let opts = copy(a:000)
  540. if type(opts[0]) == type([])
  541. if empty(args) || index(opts[0], args[0]) == -1
  542. return 0
  543. endif
  544. call remove(opts, 0)
  545. endif
  546. for opt in opts
  547. if index(args, opt) != -1
  548. return 1
  549. endif
  550. endfor
  551. endfunction
  552. function! s:PreparePathArgs(cmd, dir, literal, explicit) abort
  553. if !a:explicit
  554. call insert(a:cmd, '--literal-pathspecs')
  555. endif
  556. let split = index(a:cmd, '--')
  557. for i in range(split < 0 ? len(a:cmd) : split)
  558. if type(a:cmd[i]) == type(0)
  559. if a:literal
  560. let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
  561. else
  562. let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
  563. endif
  564. endif
  565. endfor
  566. if split < 0
  567. return a:cmd
  568. endif
  569. for i in range(split + 1, len(a:cmd) - 1)
  570. if type(a:cmd[i]) == type(0)
  571. if a:literal
  572. let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
  573. else
  574. let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
  575. endif
  576. elseif !a:explicit
  577. let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
  578. endif
  579. endfor
  580. return a:cmd
  581. endfunction
  582. let s:git_index_file_env = {}
  583. function! s:GitIndexFileEnv() abort
  584. if $GIT_INDEX_FILE =~# '^/\|^\a:' && !has_key(s:git_index_file_env, $GIT_INDEX_FILE)
  585. let s:git_index_file_env[$GIT_INDEX_FILE] = s:Slash(FugitiveVimPath($GIT_INDEX_FILE))
  586. endif
  587. return get(s:git_index_file_env, $GIT_INDEX_FILE, '')
  588. endfunction
  589. function! s:PrepareEnv(env, dir) abort
  590. if len($GIT_INDEX_FILE) && len(s:Tree(a:dir)) && !has_key(a:env, 'GIT_INDEX_FILE')
  591. let index_dir = substitute(s:GitIndexFileEnv(), '[^/]\+$', '', '')
  592. let our_dir = fugitive#Find('.git/', a:dir)
  593. if !s:cpath(index_dir, our_dir) && !s:cpath(resolve(index_dir), our_dir)
  594. let a:env['GIT_INDEX_FILE'] = FugitiveGitPath(fugitive#Find('.git/index', a:dir))
  595. endif
  596. endif
  597. if len($GIT_WORK_TREE)
  598. let a:env['GIT_WORK_TREE'] = '.'
  599. endif
  600. endfunction
  601. let s:prepare_env = {
  602. \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
  603. \ 'core.editor': 'GIT_EDITOR',
  604. \ 'core.askpass': 'GIT_ASKPASS',
  605. \ }
  606. function! fugitive#PrepareDirEnvGitFlagsArgs(...) abort
  607. if !fugitive#GitVersion(1, 8, 5)
  608. throw 'fugitive: Git 1.8.5 or higher required'
  609. endif
  610. let git = s:GitCmd()
  611. 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')
  612. let cmd = a:1.flags + a:1.args
  613. let dir = s:Dir(a:1)
  614. if has_key(a:1, 'git')
  615. let git = a:1.git
  616. endif
  617. let env = get(a:1, 'env', {})
  618. else
  619. let list_args = []
  620. let cmd = []
  621. for l:.arg in a:000
  622. if type(arg) ==# type([])
  623. call extend(list_args, arg)
  624. else
  625. call add(cmd, arg)
  626. endif
  627. endfor
  628. call extend(cmd, list_args)
  629. let env = {}
  630. endif
  631. let autoenv = {}
  632. let explicit_pathspec_option = 0
  633. let literal_pathspecs = 1
  634. let i = 0
  635. let arg_count = 0
  636. while i < len(cmd)
  637. if type(cmd[i]) == type({})
  638. if has_key(cmd[i], 'fugitive_dir') || has_key(cmd[i], 'git_dir')
  639. let dir = s:Dir(cmd[i])
  640. endif
  641. if has_key(cmd[i], 'git')
  642. let git = cmd[i].git
  643. endif
  644. if has_key(cmd[i], 'env')
  645. call extend(env, cmd[i].env)
  646. endif
  647. call remove(cmd, i)
  648. elseif cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
  649. let dir = s:Dir(remove(cmd, i))
  650. elseif cmd[i] =~# '^--git-dir='
  651. let dir = s:Dir(remove(cmd, i)[10:-1])
  652. elseif type(cmd[i]) ==# type(0)
  653. let dir = s:Dir(remove(cmd, i))
  654. elseif cmd[i] ==# '-c' && len(cmd) > i + 1
  655. let key = matchstr(cmd[i+1], '^[^=]*')
  656. if has_key(s:prepare_env, tolower(key))
  657. let var = s:prepare_env[tolower(key)]
  658. let val = matchstr(cmd[i+1], '=\zs.*')
  659. let autoenv[var] = val
  660. endif
  661. let i += 2
  662. elseif cmd[i] =~# '^--.*pathspecs$'
  663. let literal_pathspecs = (cmd[i] ==# '--literal-pathspecs')
  664. let explicit_pathspec_option = 1
  665. let i += 1
  666. elseif cmd[i] !~# '^-'
  667. let arg_count = len(cmd) - i
  668. break
  669. else
  670. let i += 1
  671. endif
  672. endwhile
  673. if !exists('dir')
  674. let dir = s:Dir()
  675. endif
  676. call extend(autoenv, env)
  677. call s:PrepareEnv(autoenv, dir)
  678. if len($GPG_TTY) && !has_key(autoenv, 'GPG_TTY')
  679. let autoenv.GPG_TTY = ''
  680. endif
  681. call s:PreparePathArgs(cmd, dir, literal_pathspecs, explicit_pathspec_option)
  682. return [dir, env, extend(autoenv, env), git, cmd[0 : -arg_count-1], arg_count ? cmd[-arg_count : -1] : []]
  683. endfunction
  684. function! s:BuildEnvPrefix(env) abort
  685. let pre = ''
  686. let env = items(a:env)
  687. if empty(env)
  688. return ''
  689. elseif &shell =~# '\%(powershell\|pwsh\)\%(\.exe\)\=$'
  690. return join(map(env, '"$Env:" . v:val[0] . " = ''" . substitute(v:val[1], "''", "''''", "g") . "''; "'), '')
  691. elseif s:winshell()
  692. return join(map(env, '"set " . substitute(join(v:val, "="), "[&|<>^]", "^^^&", "g") . "& "'), '')
  693. else
  694. return '/usr/bin/env ' . s:shellesc(map(env, 'join(v:val, "=")')) . ' '
  695. endif
  696. endfunction
  697. function! s:JobOpts(cmd, env) abort
  698. if empty(a:env)
  699. return [a:cmd, {}]
  700. elseif has('patch-8.2.0239') ||
  701. \ has('nvim') && api_info().version.api_level - api_info().version.api_prerelease >= 7 ||
  702. \ has('patch-8.0.0902') && !has('nvim') && (!has('win32') || empty(filter(keys(a:env), 'exists("$" . v:val)')))
  703. return [a:cmd, {'env': a:env}]
  704. endif
  705. let envlist = map(items(a:env), 'join(v:val, "=")')
  706. if !has('win32')
  707. return [['/usr/bin/env'] + envlist + a:cmd, {}]
  708. else
  709. let pre = join(map(envlist, '"set " . substitute(v:val, "[&|<>^]", "^^^&", "g") . "& "'), '')
  710. if len(a:cmd) == 3 && a:cmd[0] ==# 'cmd.exe' && a:cmd[1] ==# '/c'
  711. return [a:cmd[0:1] + [pre . a:cmd[2]], {}]
  712. else
  713. return [['cmd.exe', '/c', pre . s:WinShellEsc(a:cmd)], {}]
  714. endif
  715. endif
  716. endfunction
  717. function! s:PrepareJob(opts) abort
  718. let dict = {'argv': a:opts.argv}
  719. if has_key(a:opts, 'env')
  720. let dict.env = a:opts.env
  721. endif
  722. let [argv, jopts] = s:JobOpts(a:opts.argv, get(a:opts, 'env', {}))
  723. if has_key(a:opts, 'cwd')
  724. if has('patch-8.0.0902')
  725. let jopts.cwd = a:opts.cwd
  726. let dict.cwd = a:opts.cwd
  727. else
  728. throw 'fugitive: cwd unsupported'
  729. endif
  730. endif
  731. return [argv, jopts, dict]
  732. endfunction
  733. function! fugitive#PrepareJob(...) abort
  734. if a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'argv') && !has_key(a:1, 'args')
  735. return s:PrepareJob(a:1)
  736. endif
  737. let [repo, user_env, exec_env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
  738. let dir = s:GitDir(repo)
  739. let dict = {'git': git, 'git_dir': dir, 'flags': flags, 'args': args}
  740. if len(user_env)
  741. let dict.env = user_env
  742. endif
  743. let cmd = flags + args
  744. let tree = s:Tree(repo)
  745. if empty(tree) || index(cmd, '--') == len(cmd) - 1
  746. let dict.cwd = getcwd()
  747. call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
  748. else
  749. let dict.cwd = s:VimSlash(tree)
  750. call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
  751. if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
  752. call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
  753. endif
  754. endif
  755. call extend(cmd, git, 'keep')
  756. return s:JobOpts(cmd, exec_env) + [dict]
  757. endfunction
  758. function! fugitive#Execute(...) abort
  759. let cb = copy(a:000)
  760. let cmd = []
  761. let stdin = []
  762. while len(cb) && type(cb[0]) !=# type(function('tr'))
  763. if type(cb[0]) ==# type({}) && has_key(cb[0], 'stdin')
  764. if type(cb[0].stdin) == type([])
  765. call extend(stdin, cb[0].stdin)
  766. elseif type(cb[0].stdin) == type('')
  767. call extend(stdin, readfile(cb[0].stdin, 'b'))
  768. endif
  769. if len(keys(cb[0])) == 1
  770. call remove(cb, 0)
  771. continue
  772. endif
  773. endif
  774. call add(cmd, remove(cb, 0))
  775. endwhile
  776. let [argv, jopts, dict] = call('fugitive#PrepareJob', cmd)
  777. return s:JobExecute(argv, jopts, stdin, cb, dict)
  778. endfunction
  779. function! s:BuildShell(dir, env, git, args) abort
  780. let cmd = copy(a:args)
  781. let tree = s:Tree(a:dir)
  782. let pre = s:BuildEnvPrefix(a:env)
  783. if empty(tree) || index(cmd, '--') == len(cmd) - 1
  784. call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
  785. else
  786. call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
  787. if !s:cpath(tree . '/.git', a:dir) || len($GIT_DIR)
  788. call extend(cmd, ['--git-dir=' . FugitiveGitPath(a:dir)], 'keep')
  789. endif
  790. endif
  791. return pre . join(map(a:git + cmd, 's:shellesc(v:val)'))
  792. endfunction
  793. function! s:JobNvimCallback(lines, job, data, type) abort
  794. let a:lines[-1] .= remove(a:data, 0)
  795. call extend(a:lines, a:data)
  796. endfunction
  797. function! s:SystemList(cmd) abort
  798. let exit = []
  799. if exists('*jobstart')
  800. let lines = ['']
  801. let jopts = {
  802. \ 'on_stdout': function('s:JobNvimCallback', [lines]),
  803. \ 'on_stderr': function('s:JobNvimCallback', [lines]),
  804. \ 'on_exit': { j, code, _ -> add(exit, code) }}
  805. let job = jobstart(a:cmd, jopts)
  806. call chanclose(job, 'stdin')
  807. call jobwait([job])
  808. if empty(lines[-1])
  809. call remove(lines, -1)
  810. endif
  811. return [lines, exit[0]]
  812. elseif exists('*ch_close_in')
  813. let lines = []
  814. let jopts = {
  815. \ 'out_cb': { j, str -> add(lines, str) },
  816. \ 'err_cb': { j, str -> add(lines, str) },
  817. \ 'exit_cb': { j, code -> add(exit, code) }}
  818. let job = job_start(a:cmd, jopts)
  819. call ch_close_in(job)
  820. while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
  821. sleep 1m
  822. endwhile
  823. return [lines, exit[0]]
  824. else
  825. let [output, exec_error] = s:SystemError(s:shellesc(a:cmd))
  826. let lines = split(output, "\n", 1)
  827. if empty(lines[-1])
  828. call remove(lines, -1)
  829. endif
  830. return [lines, v:shell_error]
  831. endif
  832. endfunction
  833. function! fugitive#ShellCommand(...) abort
  834. let [repo, _, env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
  835. return s:BuildShell(s:GitDir(repo), env, git, flags + args)
  836. endfunction
  837. function! s:SystemError(cmd, ...) abort
  838. let cmd = type(a:cmd) == type([]) ? s:shellesc(a:cmd) : a:cmd
  839. try
  840. if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
  841. let shellredir = &shellredir
  842. if &shell =~# 'csh'
  843. set shellredir=>&
  844. else
  845. set shellredir=>%s\ 2>&1
  846. endif
  847. endif
  848. if exists('+guioptions') && &guioptions =~# '!'
  849. let guioptions = &guioptions
  850. set guioptions-=!
  851. endif
  852. let out = call('system', [cmd] + a:000)
  853. return [out, v:shell_error]
  854. catch /^Vim\%((\a\+)\)\=:E484:/
  855. let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
  856. call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
  857. call map(opts, 'v:val."=".eval("&".v:val)')
  858. call s:throw('failed to run `' . cmd . '` with ' . join(opts, ' '))
  859. finally
  860. if exists('shellredir')
  861. let &shellredir = shellredir
  862. endif
  863. if exists('guioptions')
  864. let &guioptions = guioptions
  865. endif
  866. endtry
  867. endfunction
  868. function! s:ChompStderr(...) abort
  869. let r = call('fugitive#Execute', a:000)
  870. return !r.exit_status ? '' : len(r.stderr) > 1 ? s:JoinChomp(r.stderr) : 'unknown Git error' . string(r)
  871. endfunction
  872. function! s:ChompDefault(default, ...) abort
  873. let r = call('fugitive#Execute', a:000)
  874. return r.exit_status ? a:default : s:JoinChomp(r.stdout)
  875. endfunction
  876. function! s:LinesError(...) abort
  877. let r = call('fugitive#Execute', a:000)
  878. if empty(r.stdout[-1])
  879. call remove(r.stdout, -1)
  880. endif
  881. return [r.exit_status ? [] : r.stdout, r.exit_status]
  882. endfunction
  883. function! s:TreeChomp(...) abort
  884. let r = call('fugitive#Execute', a:000)
  885. if !r.exit_status
  886. return s:JoinChomp(r.stdout)
  887. endif
  888. throw 'fugitive: error running `' . call('fugitive#ShellCommand', a:000) . '`: ' . s:JoinChomp(r.stderr)
  889. endfunction
  890. function! s:StdoutToFile(out, cmd, ...) abort
  891. let [argv, jopts, _] = fugitive#PrepareJob(a:cmd)
  892. let exit = []
  893. if exists('*jobstart')
  894. call extend(jopts, {
  895. \ 'stdout_buffered': v:true,
  896. \ 'stderr_buffered': v:true,
  897. \ 'on_exit': { j, code, _ -> add(exit, code) }})
  898. let job = jobstart(argv, jopts)
  899. if a:0
  900. call chansend(job, a:1)
  901. endif
  902. call chanclose(job, 'stdin')
  903. call jobwait([job])
  904. if len(a:out)
  905. call writefile(jopts.stdout, a:out, 'b')
  906. endif
  907. return [join(jopts.stderr, "\n"), exit[0]]
  908. elseif exists('*ch_close_in')
  909. try
  910. let err = tempname()
  911. call extend(jopts, {
  912. \ 'out_io': len(a:out) ? 'file' : 'null',
  913. \ 'out_name': a:out,
  914. \ 'err_io': 'file',
  915. \ 'err_name': err,
  916. \ 'exit_cb': { j, code -> add(exit, code) }})
  917. let job = job_start(argv, jopts)
  918. if a:0
  919. call ch_sendraw(job, a:1)
  920. endif
  921. call ch_close_in(job)
  922. while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
  923. sleep 1m
  924. endwhile
  925. return [join(readfile(err, 'b'), "\n"), exit[0]]
  926. finally
  927. call delete(err)
  928. endtry
  929. elseif s:winshell() || &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
  930. throw 'fugitive: Vim 8 or higher required to use ' . &shell
  931. else
  932. let cmd = fugitive#ShellCommand(a:cmd)
  933. return call('s:SystemError', [' (' . cmd . ' >' . (len(a:out) ? a:out : '/dev/null') . ') '] + a:000)
  934. endif
  935. endfunction
  936. let s:head_cache = {}
  937. function! fugitive#Head(...) abort
  938. let dir = a:0 > 1 ? a:2 : s:Dir()
  939. if empty(dir)
  940. return ''
  941. endif
  942. let file = FugitiveActualDir(dir) . '/HEAD'
  943. let ftime = getftime(file)
  944. if ftime == -1
  945. return ''
  946. elseif ftime != get(s:head_cache, file, [-1])[0]
  947. let s:head_cache[file] = [ftime, readfile(file)[0]]
  948. endif
  949. let head = s:head_cache[file][1]
  950. let len = a:0 ? a:1 : 0
  951. if head =~# '^ref: '
  952. if len < 0
  953. return strpart(head, 5)
  954. else
  955. return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
  956. endif
  957. elseif head =~# '^\x\{40,\}$'
  958. return len < 0 ? head : strpart(head, 0, len)
  959. else
  960. return ''
  961. endif
  962. endfunction
  963. function! fugitive#RevParse(rev, ...) abort
  964. let hash = s:ChompDefault('', [a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
  965. if hash =~# '^\x\{40,\}$'
  966. return hash
  967. endif
  968. throw 'fugitive: failed to parse revision ' . a:rev
  969. endfunction
  970. " Section: Git config
  971. function! s:ConfigTimestamps(dir, dict) abort
  972. let files = ['/etc/gitconfig', '~/.gitconfig',
  973. \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
  974. if len(a:dir)
  975. call add(files, fugitive#Find('.git/config', a:dir))
  976. endif
  977. call extend(files, get(a:dict, 'include.path', []))
  978. return join(map(files, 'getftime(expand(v:val))'), ',')
  979. endfunction
  980. function! s:ConfigCallback(r, into) abort
  981. let dict = a:into[1]
  982. if has_key(dict, 'job')
  983. call remove(dict, 'job')
  984. endif
  985. let lines = a:r.exit_status ? [] : split(tr(join(a:r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
  986. for line in lines
  987. let key = matchstr(line, "^[^\n]*")
  988. if !has_key(dict, key)
  989. let dict[key] = []
  990. endif
  991. if len(key) ==# len(line)
  992. call add(dict[key], 1)
  993. else
  994. call add(dict[key], strpart(line, len(key) + 1))
  995. endif
  996. endfor
  997. let callbacks = remove(dict, 'callbacks')
  998. lockvar! dict
  999. let a:into[0] = s:ConfigTimestamps(dict.git_dir, dict)
  1000. for callback in callbacks
  1001. call call(callback[0], [dict] + callback[1:-1])
  1002. endfor
  1003. endfunction
  1004. let s:config_prototype = {}
  1005. let s:config = {}
  1006. function! fugitive#ExpireConfig(...) abort
  1007. if !a:0 || a:1 is# 0
  1008. let s:config = {}
  1009. else
  1010. let key = a:1 is# '' ? '_' : s:GitDir(a:0 ? a:1 : -1)
  1011. if len(key) && has_key(s:config, key)
  1012. call remove(s:config, key)
  1013. endif
  1014. endif
  1015. endfunction
  1016. function! fugitive#Config(...) abort
  1017. let name = ''
  1018. let default = get(a:, 3, '')
  1019. if a:0 && type(a:1) == type(function('tr'))
  1020. let dir = s:Dir()
  1021. let callback = a:000
  1022. elseif a:0 > 1 && type(a:2) == type(function('tr'))
  1023. if type(a:1) == type({}) && has_key(a:1, 'GetAll')
  1024. if has_key(a:1, 'callbacks')
  1025. call add(a:1.callbacks, a:000[1:-1])
  1026. else
  1027. call call(a:2, [a:1] + a:000[2:-1])
  1028. endif
  1029. return a:1
  1030. else
  1031. let dir = s:Dir(a:1)
  1032. let callback = a:000[1:-1]
  1033. endif
  1034. elseif a:0 >= 2 && type(a:2) == type({}) && has_key(a:2, 'GetAll')
  1035. return get(fugitive#ConfigGetAll(a:1, a:2), -1, default)
  1036. elseif a:0 >= 2
  1037. let dir = s:Dir(a:2)
  1038. let name = a:1
  1039. elseif a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'GetAll')
  1040. return a:1
  1041. elseif a:0 == 1 && type(a:1) == type('') && a:1 =~# '^[[:alnum:]-]\+\.'
  1042. let dir = s:Dir()
  1043. let name = a:1
  1044. elseif a:0 == 1
  1045. let dir = s:Dir(a:1)
  1046. else
  1047. let dir = s:Dir()
  1048. endif
  1049. let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
  1050. let git_dir = s:GitDir(dir)
  1051. let dir_key = len(git_dir) ? git_dir : '_'
  1052. let [ts, dict] = get(s:config, dir_key, ['new', {}])
  1053. if !has_key(dict, 'job') && ts !=# s:ConfigTimestamps(git_dir, dict)
  1054. let dict = copy(s:config_prototype)
  1055. let dict.git_dir = git_dir
  1056. let into = ['running', dict]
  1057. let dict.callbacks = []
  1058. let exec = fugitive#Execute([dir, 'config', '--list', '-z', '--'], function('s:ConfigCallback'), into)
  1059. if has_key(exec, 'job')
  1060. let dict.job = exec.job
  1061. endif
  1062. let s:config[dir_key] = into
  1063. endif
  1064. if !exists('l:callback')
  1065. call fugitive#Wait(dict)
  1066. elseif has_key(dict, 'callbacks')
  1067. call add(dict.callbacks, callback)
  1068. else
  1069. call call(callback[0], [dict] + callback[1:-1])
  1070. endif
  1071. return len(name) ? get(fugitive#ConfigGetAll(name, dict), 0, default) : dict
  1072. endfunction
  1073. function! fugitive#ConfigGetAll(name, ...) abort
  1074. if a:0 && (type(a:name) !=# type('') || a:name !~# '^[[:alnum:]-]\+\.' && type(a:1) ==# type('') && a:1 =~# '^[[:alnum:]-]\+\.')
  1075. let config = fugitive#Config(a:name)
  1076. let name = a:1
  1077. else
  1078. let config = fugitive#Config(a:0 ? a:1 : s:Dir())
  1079. let name = a:name
  1080. endif
  1081. let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
  1082. call fugitive#Wait(config)
  1083. return name =~# '\.' ? copy(get(config, name, [])) : []
  1084. endfunction
  1085. function! fugitive#ConfigGetRegexp(pattern, ...) abort
  1086. if type(a:pattern) !=# type('')
  1087. let config = fugitive#Config(a:name)
  1088. let pattern = a:0 ? a:1 : '.*'
  1089. else
  1090. let config = fugitive#Config(a:0 ? a:1 : s:Dir())
  1091. let pattern = a:pattern
  1092. endif
  1093. call fugitive#Wait(config)
  1094. let filtered = map(filter(copy(config), 'v:key =~# "\\." && v:key =~# pattern'), 'copy(v:val)')
  1095. if pattern !~# '\\\@<!\%(\\\\\)*\\z[se]'
  1096. return filtered
  1097. endif
  1098. let transformed = {}
  1099. for [k, v] in items(filtered)
  1100. let k = matchstr(k, pattern)
  1101. if len(k)
  1102. let transformed[k] = v
  1103. endif
  1104. endfor
  1105. return transformed
  1106. endfunction
  1107. function! s:config_GetAll(name) dict abort
  1108. let name = substitute(a:name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
  1109. call fugitive#Wait(self)
  1110. return name =~# '\.' ? copy(get(self, name, [])) : []
  1111. endfunction
  1112. function! s:config_Get(name, ...) dict abort
  1113. return get(self.GetAll(a:name), -1, a:0 ? a:1 : '')
  1114. endfunction
  1115. function! s:config_GetRegexp(pattern) dict abort
  1116. return fugitive#ConfigGetRegexp(self, a:pattern)
  1117. endfunction
  1118. call s:add_methods('config', ['GetAll', 'Get', 'GetRegexp'])
  1119. function! s:RemoteDefault(dir) abort
  1120. let head = FugitiveHead(0, a:dir)
  1121. let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
  1122. let i = 10
  1123. while remote ==# '.' && i > 0
  1124. let head = matchstr(FugitiveConfigGet('branch.' . head . '.merge', a:dir), 'refs/heads/\zs.*')
  1125. let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
  1126. let i -= 1
  1127. endwhile
  1128. return remote =~# '^\.\=$' ? 'origin' : remote
  1129. endfunction
  1130. function! s:SshParseHost(value) abort
  1131. let patterns = []
  1132. let negates = []
  1133. for host in split(a:value, '\s\+')
  1134. let pattern = substitute(host, '[\\^$.*~?]', '\=submatch(0) == "*" ? ".*" : submatch(0) == "?" ? "." : "\\" . submatch(0)', 'g')
  1135. if pattern[0] ==# '!'
  1136. call add(negates, '\&\%(^' . pattern[1 : -1] . '$\)\@!')
  1137. else
  1138. call add(patterns, pattern)
  1139. endif
  1140. endfor
  1141. return '^\%(' . join(patterns, '\|') . '\)$' . join(negates, '')
  1142. endfunction
  1143. function! s:SshParseConfig(into, root, file) abort
  1144. try
  1145. let lines = readfile(a:file)
  1146. catch
  1147. return a:into
  1148. endtry
  1149. let host = '^\%(.*\)$'
  1150. while !empty(lines)
  1151. let line = remove(lines, 0)
  1152. let key = tolower(matchstr(line, '^\s*\zs\w\+\ze\s'))
  1153. let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S')
  1154. if key ==# 'match'
  1155. let host = value ==# 'all' ? '^\%(.*\)$' : ''
  1156. elseif key ==# 'host'
  1157. let host = s:SshParseHost(value)
  1158. elseif key ==# 'include'
  1159. for glob in split(value)
  1160. if glob !~# '^[~/]'
  1161. let glob = a:root . glob
  1162. endif
  1163. for included in reverse(split(glob(glob), "\n"))
  1164. try
  1165. call extend(lines, readfile(included), 'keep')
  1166. catch
  1167. endtry
  1168. endfor
  1169. endfor
  1170. elseif len(key) && len(host)
  1171. call extend(a:into, {key : []}, 'keep')
  1172. call add(a:into[key], [host, value])
  1173. endif
  1174. endwhile
  1175. return a:into
  1176. endfunction
  1177. unlet! s:ssh_config
  1178. function! fugitive#SshConfig(host, ...) abort
  1179. if !exists('s:ssh_config')
  1180. let s:ssh_config = {}
  1181. for file in [expand("~/.ssh/config"), "/etc/ssh/ssh_config"]
  1182. call s:SshParseConfig(s:ssh_config, substitute(file, '\w*$', '', ''), file)
  1183. endfor
  1184. endif
  1185. let host_config = {}
  1186. for key in a:0 ? a:1 : keys(s:ssh_config)
  1187. for [host_pattern, value] in get(s:ssh_config, key, [])
  1188. if a:host =~# host_pattern
  1189. let host_config[key] = value
  1190. break
  1191. endif
  1192. endfor
  1193. endfor
  1194. return host_config
  1195. endfunction
  1196. function! fugitive#SshHostAlias(authority) abort
  1197. let [_, user, host, port; __] = matchlist(a:authority, '^\%(\([^/@]\+\)@\)\=\(.\{-\}\)\%(:\(\d\+\)\)\=$')
  1198. let c = fugitive#SshConfig(host, ['user', 'hostname', 'port'])
  1199. if empty(user)
  1200. let user = get(c, 'user', '')
  1201. endif
  1202. if empty(port)
  1203. let port = get(c, 'port', '')
  1204. endif
  1205. return (len(user) ? user . '@' : '') . get(c, 'hostname', host) . (port =~# '^\%(22\)\=$' ? '' : ':' . port)
  1206. endfunction
  1207. function! s:CurlResponse(result) abort
  1208. let a:result.headers = {}
  1209. for line in a:result.exit_status ? [] : remove(a:result, 'stdout')
  1210. let header = matchlist(line, '^\([[:alnum:]-]\+\):\s\(.\{-\}\)'. "\r\\=$")
  1211. if len(header)
  1212. let k = tolower(header[1])
  1213. if has_key(a:result.headers, k)
  1214. let a:result.headers[k] .= ', ' . header[2]
  1215. else
  1216. let a:result.headers[k] = header[2]
  1217. endif
  1218. elseif empty(line)
  1219. break
  1220. endif
  1221. endfor
  1222. endfunction
  1223. let s:remote_headers = {}
  1224. function! fugitive#RemoteHttpHeaders(remote) abort
  1225. let remote = type(a:remote) ==# type({}) ? get(a:remote, 'remote', '') : a:remote
  1226. if type(remote) !=# type('') || remote !~# '^https\=://.' || !s:executable('curl')
  1227. return {}
  1228. endif
  1229. let remote = substitute(remote, '#.*', '', '')
  1230. if !has_key(s:remote_headers, remote)
  1231. let url = remote . '/info/refs?service=git-upload-pack'
  1232. let exec = s:JobExecute(
  1233. \ ['curl', '--disable', '--silent', '--max-time', '5', '-X', 'GET', '-I',
  1234. \ url], {}, [], [function('s:CurlResponse')], {})
  1235. call fugitive#Wait(exec)
  1236. let s:remote_headers[remote] = exec.headers
  1237. endif
  1238. return s:remote_headers[remote]
  1239. endfunction
  1240. function! s:UrlParse(url) abort
  1241. let scp_authority = matchstr(a:url, '^[^:/]\+\ze:\%(//\)\@!')
  1242. if len(scp_authority) && !(has('win32') && scp_authority =~# '^\a:[\/]')
  1243. let url = {'scheme': 'ssh', 'authority': s:UrlEncode(scp_authority), 'hash': '',
  1244. \ 'path': s:UrlEncode(strpart(a:url, len(scp_authority) + 1))}
  1245. elseif empty(a:url)
  1246. let url = {'scheme': '', 'authority': '', 'path': '', 'hash': ''}
  1247. else
  1248. let match = matchlist(a:url, '^\([[:alnum:].+-]\+\)://\([^/]*\)\(/[^#]*\)\=\(#.*\)\=$')
  1249. if empty(match)
  1250. let url = {'scheme': 'file', 'authority': '', 'hash': '',
  1251. \ 'path': s:UrlEncode(a:url)}
  1252. else
  1253. let url = {'scheme': match[1], 'authority': match[2], 'hash': match[4]}
  1254. let url.path = empty(match[3]) ? '/' : match[3]
  1255. endif
  1256. endif
  1257. return url
  1258. endfunction
  1259. function! s:UrlPopulate(string, into) abort
  1260. let url = a:into
  1261. let url.protocol = substitute(url.scheme, '.\zs$', ':', '')
  1262. let url.user = fugitive#UrlDecode(matchstr(url.authority, '.\{-\}\ze@', '', ''))
  1263. let url.host = substitute(url.authority, '.\{-\}@', '', '')
  1264. let url.hostname = substitute(url.host, ':\d\+$', '', '')
  1265. let url.port = matchstr(url.host, ':\zs\d\+$', '', '')
  1266. let url.origin = substitute(url.scheme, '.\zs$', '://', '') . url.host
  1267. let url.search = matchstr(url.path, '?.*')
  1268. let url.pathname = '/' . matchstr(url.path, '^/\=\zs[^?]*')
  1269. if (url.scheme ==# 'ssh' || url.scheme ==# 'git') && url.path[0:1] ==# '/~'
  1270. let url.path = strpart(url.path, 1)
  1271. endif
  1272. if url.path =~# '^/'
  1273. let url.href = url.scheme . '://' . url.authority . url.path . url.hash
  1274. elseif url.path =~# '^\~'
  1275. let url.href = url.scheme . '://' . url.authority . '/' . url.path . url.hash
  1276. elseif url.scheme ==# 'ssh' && url.authority !~# ':'
  1277. let url.href = url.authority . ':' . url.path . url.hash
  1278. else
  1279. let url.href = a:string
  1280. endif
  1281. let url.path = fugitive#UrlDecode(matchstr(url.path, '^[^?]*'))
  1282. let url.url = matchstr(url.href, '^[^#]*')
  1283. endfunction
  1284. function! s:RemoteResolve(url, flags) abort
  1285. let remote = s:UrlParse(a:url)
  1286. if remote.scheme =~# '^https\=$' && index(a:flags, ':nohttp') < 0
  1287. let headers = fugitive#RemoteHttpHeaders(a:url)
  1288. let loc = matchstr(get(headers, 'location', ''), '^https\=://.\{-\}\ze/info/refs?')
  1289. if len(loc)
  1290. let remote = s:UrlParse(loc)
  1291. else
  1292. let remote.headers = headers
  1293. endif
  1294. elseif remote.scheme ==# 'ssh'
  1295. let remote.authority = fugitive#SshHostAlias(remote.authority)
  1296. endif
  1297. return remote
  1298. endfunction
  1299. function! s:ConfigLengthSort(i1, i2) abort
  1300. return len(a:i2[0]) - len(a:i1[0])
  1301. endfunction
  1302. function! s:RemoteCallback(config, into, flags, cb) abort
  1303. if a:into.remote_name =~# '^\.\=$'
  1304. let a:into.remote_name = s:RemoteDefault(a:config)
  1305. endif
  1306. let url = a:into.remote_name
  1307. if url ==# '.git'
  1308. let url = s:GitDir(a:config)
  1309. elseif url !~# ':\|^/\|^\a:[\/]\|^\.\.\=/'
  1310. let url = FugitiveConfigGet('remote.' . url . '.url', a:config)
  1311. endif
  1312. let instead_of = []
  1313. for [k, vs] in items(fugitive#ConfigGetRegexp('^url\.\zs.\{-\}\ze\.insteadof$', a:config))
  1314. for v in vs
  1315. call add(instead_of, [v, k])
  1316. endfor
  1317. endfor
  1318. call sort(instead_of, 's:ConfigLengthSort')
  1319. for [orig, replacement] in instead_of
  1320. if strpart(url, 0, len(orig)) ==# orig
  1321. let url = replacement . strpart(url, len(orig))
  1322. break
  1323. endif
  1324. endfor
  1325. if index(a:flags, ':noresolve') < 0
  1326. call extend(a:into, s:RemoteResolve(url, a:flags))
  1327. else
  1328. call extend(a:into, s:UrlParse(url))
  1329. endif
  1330. call s:UrlPopulate(url, a:into)
  1331. if len(a:cb)
  1332. call call(a:cb[0], [a:into] + a:cb[1:-1])
  1333. endif
  1334. endfunction
  1335. function! s:Remote(dir, remote, flags, cb) abort
  1336. let into = {'remote_name': a:remote, 'git_dir': s:GitDir(a:dir)}
  1337. let config = fugitive#Config(a:dir, function('s:RemoteCallback'), into, a:flags, a:cb)
  1338. if len(a:cb)
  1339. return config
  1340. else
  1341. call fugitive#Wait(config)
  1342. return into
  1343. endif
  1344. endfunction
  1345. function! s:RemoteParseArgs(args) abort
  1346. " Extract ':noresolve' style flags and an optional callback
  1347. let args = []
  1348. let flags = []
  1349. let cb = copy(a:args)
  1350. while len(cb)
  1351. if type(cb[0]) ==# type(function('tr'))
  1352. break
  1353. elseif len(args) > 1 || type(cb[0]) ==# type('') && cb[0] =~# '^:'
  1354. call add(flags, remove(cb, 0))
  1355. else
  1356. call add(args, remove(cb, 0))
  1357. endif
  1358. endwhile
  1359. " From the remaining 0-2 arguments, extract the remote and Git config
  1360. let remote = ''
  1361. if empty(args)
  1362. let dir_or_config = s:Dir()
  1363. elseif len(args) == 1 && type(args[0]) ==# type('') && args[0] !~# '^/\|^\a:[\\/]'
  1364. let dir_or_config = s:Dir()
  1365. let remote = args[0]
  1366. elseif len(args) == 1
  1367. let dir_or_config = args[0]
  1368. if type(args[0]) ==# type({}) && has_key(args[0], 'remote_name')
  1369. let remote = args[0].remote_name
  1370. endif
  1371. elseif type(args[1]) !=# type('') || args[1] =~# '^/\|^\a:[\\/]'
  1372. let dir_or_config = args[1]
  1373. let remote = args[0]
  1374. else
  1375. let dir_or_config = args[0]
  1376. let remote = args[1]
  1377. endif
  1378. return [dir_or_config, remote, flags, cb]
  1379. endfunction
  1380. function! fugitive#Remote(...) abort
  1381. let [dir_or_config, remote, flags, cb] = s:RemoteParseArgs(a:000)
  1382. return s:Remote(dir_or_config, remote, flags, cb)
  1383. endfunction
  1384. function! s:RemoteUrlCallback(remote, callback) abort
  1385. return call(a:callback[0], [a:remote.url] + a:callback[1:-1])
  1386. endfunction
  1387. function! fugitive#RemoteUrl(...) abort
  1388. let [dir_or_config, remote_url, flags, cb] = s:RemoteParseArgs(a:000)
  1389. if len(cb)
  1390. let cb = [function('s:RemoteUrlCallback'), cb]
  1391. endif
  1392. let remote = s:Remote(dir_or_config, remote_url, flags, cb)
  1393. return get(remote, 'url', remote_url)
  1394. endfunction
  1395. " Section: Quickfix
  1396. function! s:QuickfixGet(nr, ...) abort
  1397. if a:nr < 0
  1398. return call('getqflist', a:000)
  1399. else
  1400. return call('getloclist', [a:nr] + a:000)
  1401. endif
  1402. endfunction
  1403. function! s:QuickfixSet(nr, ...) abort
  1404. if a:nr < 0
  1405. return call('setqflist', a:000)
  1406. else
  1407. return call('setloclist', [a:nr] + a:000)
  1408. endif
  1409. endfunction
  1410. function! s:QuickfixCreate(nr, opts) abort
  1411. if has('patch-7.4.2200')
  1412. call s:QuickfixSet(a:nr, [], ' ', a:opts)
  1413. else
  1414. call s:QuickfixSet(a:nr, [], ' ')
  1415. endif
  1416. endfunction
  1417. function! s:QuickfixOpen(nr, mods) abort
  1418. let mods = substitute(s:Mods(a:mods), '\<\d*tab\>', '', '')
  1419. return mods . (a:nr < 0 ? 'c' : 'l').'open' . (mods =~# '\<vertical\>' ? ' 20' : '')
  1420. endfunction
  1421. function! s:QuickfixStream(nr, event, title, cmd, first, mods, callback, ...) abort
  1422. call s:BlurStatus()
  1423. let opts = {'title': a:title, 'context': {'items': []}}
  1424. call s:QuickfixCreate(a:nr, opts)
  1425. let event = (a:nr < 0 ? 'c' : 'l') . 'fugitive-' . a:event
  1426. exe s:DoAutocmd('QuickFixCmdPre ' . event)
  1427. let winnr = winnr()
  1428. exe s:QuickfixOpen(a:nr, a:mods)
  1429. if winnr != winnr()
  1430. wincmd p
  1431. endif
  1432. let buffer = []
  1433. let lines = s:SystemList(a:cmd)[0]
  1434. for line in lines
  1435. call extend(buffer, call(a:callback, a:000 + [line]))
  1436. if len(buffer) >= 20
  1437. let contexts = map(copy(buffer), 'get(v:val, "context", {})')
  1438. lockvar contexts
  1439. call extend(opts.context.items, contexts)
  1440. unlet contexts
  1441. call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
  1442. if a:mods !~# '\<silent\>'
  1443. redraw
  1444. endif
  1445. endif
  1446. endfor
  1447. call extend(buffer, call(a:callback, a:000 + [0]))
  1448. call extend(opts.context.items, map(copy(buffer), 'get(v:val, "context", {})'))
  1449. lockvar opts.context.items
  1450. call s:QuickfixSet(a:nr, buffer, 'a')
  1451. exe s:DoAutocmd('QuickFixCmdPost ' . event)
  1452. if a:first
  1453. let list = s:QuickfixGet(a:nr)
  1454. for index in range(len(list))
  1455. if list[index].valid
  1456. return (index+1) . (a:nr < 0 ? 'cfirst' : 'lfirst')
  1457. endif
  1458. endfor
  1459. endif
  1460. return 'exe'
  1461. endfunction
  1462. function! fugitive#Cwindow() abort
  1463. if &buftype == 'quickfix'
  1464. cwindow
  1465. else
  1466. botright cwindow
  1467. if &buftype == 'quickfix'
  1468. wincmd p
  1469. endif
  1470. endif
  1471. endfunction
  1472. " Section: Repository Object
  1473. let s:repo_prototype = {}
  1474. function! fugitive#repo(...) abort
  1475. let dir = a:0 ? s:GitDir(a:1) : (len(s:GitDir()) ? s:GitDir() : FugitiveExtractGitDir(expand('%:p')))
  1476. if dir !=# ''
  1477. return extend({'git_dir': dir, 'fugitive_dir': dir}, s:repo_prototype, 'keep')
  1478. endif
  1479. throw 'fugitive: not a Git repository'
  1480. endfunction
  1481. function! s:repo_dir(...) dict abort
  1482. if !a:0
  1483. return self.git_dir
  1484. endif
  1485. throw 'fugitive: fugitive#repo().dir("...") has been replaced by FugitiveFind(".git/...")'
  1486. endfunction
  1487. function! s:repo_tree(...) dict abort
  1488. let tree = s:Tree(self.git_dir)
  1489. if empty(tree)
  1490. throw 'fugitive: no work tree'
  1491. elseif !a:0
  1492. return tree
  1493. endif
  1494. throw 'fugitive: fugitive#repo().tree("...") has been replaced by FugitiveFind(":(top)...")'
  1495. endfunction
  1496. function! s:repo_bare() dict abort
  1497. throw 'fugitive: fugitive#repo().bare() has been replaced by !empty(FugitiveWorkTree())'
  1498. endfunction
  1499. function! s:repo_find(object) dict abort
  1500. throw 'fugitive: fugitive#repo().find(...) has been replaced by FugitiveFind(...)'
  1501. endfunction
  1502. function! s:repo_translate(rev) dict abort
  1503. throw 'fugitive: fugitive#repo().translate(...) has been replaced by FugitiveFind(...)'
  1504. endfunction
  1505. function! s:repo_head(...) dict abort
  1506. throw 'fugitive: fugitive#repo().head(...) has been replaced by FugitiveHead(...)'
  1507. endfunction
  1508. call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
  1509. function! s:repo_git_command(...) dict abort
  1510. throw 'fugitive: fugitive#repo().git_command(...) has been replaced by FugitiveShellCommand(...)'
  1511. endfunction
  1512. function! s:repo_git_chomp(...) dict abort
  1513. silent return substitute(system(fugitive#ShellCommand(a:000, self.git_dir)), '\n$', '', '')
  1514. endfunction
  1515. function! s:repo_git_chomp_in_tree(...) dict abort
  1516. return call(self.git_chomp, a:000, self)
  1517. endfunction
  1518. function! s:repo_rev_parse(rev) dict abort
  1519. throw 'fugitive: fugitive#repo().rev_parse(...) has been replaced by FugitiveExecute("rev-parse", "--verify", ...).stdout'
  1520. endfunction
  1521. call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
  1522. function! s:repo_config(name) dict abort
  1523. return FugitiveConfigGet(a:name, self.git_dir)
  1524. endfunction
  1525. call s:add_methods('repo',['config'])
  1526. " Section: File API
  1527. function! s:DirCommitFile(path) abort
  1528. let vals = matchlist(s:Slash(a:path), s:dir_commit_file)
  1529. if empty(vals)
  1530. return ['', '', '']
  1531. endif
  1532. return [s:Dir(fugitive#UrlDecode(vals[1])), vals[2], empty(vals[2]) ? '/.git/index' : fugitive#UrlDecode(vals[3])]
  1533. endfunction
  1534. function! s:DirRev(url) abort
  1535. let [dir, commit, file] = s:DirCommitFile(a:url)
  1536. return [dir, commit . file ==# '/.git/index' ? ':' : (!empty(dir) && commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
  1537. endfunction
  1538. function! fugitive#Parse(url) abort
  1539. return reverse(s:DirRev(a:url))
  1540. endfunction
  1541. let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
  1542. function! s:MergeHead(dir) abort
  1543. let dir = fugitive#Find('.git/', a:dir)
  1544. for head in s:merge_heads
  1545. if filereadable(dir . head)
  1546. return head
  1547. endif
  1548. endfor
  1549. return ''
  1550. endfunction
  1551. function! s:Owner(path, ...) abort
  1552. let dir = a:0 ? s:Dir(a:1) : s:Dir()
  1553. if empty(dir)
  1554. return ''
  1555. endif
  1556. let actualdir = fugitive#Find('.git/', dir)
  1557. let [pdir, commit, file] = s:DirCommitFile(a:path)
  1558. if s:SameRepo(dir, pdir)
  1559. if commit =~# '^\x\{40,\}$'
  1560. return commit
  1561. elseif commit ==# '2'
  1562. return '@'
  1563. elseif commit ==# '0'
  1564. return ''
  1565. endif
  1566. let merge_head = s:MergeHead(dir)
  1567. if empty(merge_head)
  1568. return ''
  1569. endif
  1570. if commit ==# '3'
  1571. return merge_head
  1572. elseif commit ==# '1'
  1573. return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
  1574. endif
  1575. endif
  1576. let path = fnamemodify(a:path, ':p')
  1577. if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
  1578. return strpart(path, len(actualdir))
  1579. endif
  1580. let refs = fugitive#Find('.git/refs', dir)
  1581. if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
  1582. return strpart(path, len(refs) - 4)
  1583. endif
  1584. return ''
  1585. endfunction
  1586. function! fugitive#Real(url) abort
  1587. if empty(a:url)
  1588. return ''
  1589. endif
  1590. let [dir, commit, file] = s:DirCommitFile(a:url)
  1591. if len(dir)
  1592. let tree = s:Tree(dir)
  1593. return s:VimSlash((len(tree) ? tree : s:GitDir(dir)) . file)
  1594. endif
  1595. let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
  1596. if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
  1597. let url = {pre}Real(a:url)
  1598. else
  1599. let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
  1600. endif
  1601. return s:VimSlash(empty(url) ? a:url : url)
  1602. endfunction
  1603. function! fugitive#Path(url, ...) abort
  1604. if empty(a:url)
  1605. return ''
  1606. endif
  1607. let repo = call('s:Dir', a:000[1:-1])
  1608. let dir_s = fugitive#Find('.git/', repo)
  1609. let tree = fugitive#Find(':/', repo)
  1610. if !a:0
  1611. return fugitive#Real(a:url)
  1612. elseif a:1 =~# '\.$'
  1613. let path = s:Slash(fugitive#Real(a:url))
  1614. let cwd = getcwd()
  1615. let lead = ''
  1616. while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
  1617. if s:cpath(cwd . '/', path[0 : len(cwd)])
  1618. if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
  1619. break
  1620. endif
  1621. return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
  1622. endif
  1623. let cwd = fnamemodify(cwd, ':h')
  1624. let lead .= '../'
  1625. endwhile
  1626. return a:1[0:-2] . path
  1627. endif
  1628. let url = a:url
  1629. let temp_state = s:TempState(url)
  1630. if has_key(temp_state, 'origin_bufnr')
  1631. let url = bufname(temp_state.origin_bufnr)
  1632. endif
  1633. let url = s:Slash(fnamemodify(url, ':p'))
  1634. if url =~# '/$' && s:Slash(a:url) !~# '/$'
  1635. let url = url[0:-2]
  1636. endif
  1637. let [argdir, commit, file] = s:DirCommitFile(url)
  1638. if !empty(argdir) && !s:SameRepo(argdir, repo)
  1639. let file = ''
  1640. elseif len(dir_s) && s:cpath(strpart(url, 0, len(dir_s)), dir_s)
  1641. let file = '/.git' . strpart(url, len(dir_s)-1)
  1642. elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
  1643. let file = url[len(tree) : -1]
  1644. elseif s:cpath(url) ==# s:cpath(tree)
  1645. let file = '/'
  1646. endif
  1647. if empty(file) && a:1 =~# '^$\|^[.:]/$'
  1648. return FugitiveGitPath(fugitive#Real(a:url))
  1649. endif
  1650. return substitute(file, '^/', '\=a:1', '')
  1651. endfunction
  1652. function! s:Relative(...) abort
  1653. return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
  1654. endfunction
  1655. function! fugitive#Find(object, ...) abort
  1656. if type(a:object) == type(0)
  1657. let name = bufname(a:object)
  1658. return s:VimSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
  1659. elseif a:object =~# '^[~$]'
  1660. let prefix = matchstr(a:object, '^[~$]\i*')
  1661. let owner = expand(prefix)
  1662. return s:VimSlash(FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix))))
  1663. endif
  1664. let rev = s:Slash(a:object)
  1665. if rev =~# '^\a\+://' && rev !~# '^fugitive:'
  1666. return rev
  1667. elseif rev =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
  1668. return s:VimSlash(a:object)
  1669. elseif rev =~# '^\.\.\=\%(/\|$\)'
  1670. return s:VimSlash(simplify(getcwd() . '/' . a:object))
  1671. endif
  1672. let dir = call('s:GitDir', a:000)
  1673. if empty(dir)
  1674. let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs\%(\.\.\=$\|\.\.\=/.*\|/.*\|\w:/.*\)')
  1675. let dir = FugitiveExtractGitDir(file)
  1676. if empty(dir)
  1677. return ''
  1678. endif
  1679. endif
  1680. let tree = s:Tree(dir)
  1681. let urlprefix = s:DirUrlPrefix(dir)
  1682. let base = len(tree) ? tree : urlprefix . '0'
  1683. if rev ==# '.git'
  1684. let f = len(tree) && len(getftype(tree . '/.git')) ? tree . '/.git' : dir
  1685. elseif rev =~# '^\.git/'
  1686. let f = strpart(rev, 5)
  1687. let fdir = simplify(FugitiveActualDir(dir) . '/')
  1688. let cdir = simplify(FugitiveCommonDir(dir) . '/')
  1689. if f =~# '^\.\./\.\.\%(/\|$\)'
  1690. let f = simplify(len(tree) ? tree . f[2:-1] : fdir . f)
  1691. elseif f =~# '^\.\.\%(/\|$\)'
  1692. let f = s:PathJoin(base, f[2:-1])
  1693. elseif cdir !=# fdir && (
  1694. \ f =~# '^\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
  1695. \ f !~# '^\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
  1696. \ getftime(fdir . f) < 0 && getftime(cdir . f) >= 0)
  1697. let f = simplify(cdir . f)
  1698. else
  1699. let f = simplify(fdir . f)
  1700. endif
  1701. elseif rev ==# ':/'
  1702. let f = tree
  1703. elseif rev =~# '^\.\%(/\|$\)'
  1704. let f = s:PathJoin(base, rev[1:-1])
  1705. elseif rev =~# '^::\%(/\|\a\+\:\)'
  1706. let f = rev[2:-1]
  1707. elseif rev =~# '^::\.\.\=\%(/\|$\)'
  1708. let f = simplify(getcwd() . '/' . rev[2:-1])
  1709. elseif rev =~# '^::'
  1710. let f = s:PathJoin(base, '/' . rev[2:-1])
  1711. elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
  1712. let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
  1713. if s:cpath(base . '/', (f . '/')[0 : len(base)])
  1714. let f = s:PathJoin(urlprefix, +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1))
  1715. else
  1716. let altdir = FugitiveExtractGitDir(f)
  1717. if len(altdir) && !s:cpath(dir, altdir)
  1718. return fugitive#Find(a:object, altdir)
  1719. endif
  1720. endif
  1721. elseif rev =~# '^:[0-3]:'
  1722. let f = s:PathJoin(urlprefix, rev[1] . '/' . rev[3:-1])
  1723. elseif rev ==# ':'
  1724. let f = urlprefix
  1725. elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
  1726. let f = matchstr(rev, ')\zs.*')
  1727. if f=~# '^\.\.\=\%(/\|$\)'
  1728. let f = simplify(getcwd() . '/' . f)
  1729. elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
  1730. let f = s:PathJoin(base, '/' . f)
  1731. endif
  1732. elseif rev =~# '^:/\@!'
  1733. let f = s:PathJoin(urlprefix, '0/' . rev[1:-1])
  1734. else
  1735. if !exists('f')
  1736. let commit = matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\|^:.*')
  1737. let file = substitute(matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\zs:.*'), '^:', '/', '')
  1738. if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
  1739. let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
  1740. if s:cpath(base . '/', (file . '/')[0 : len(base)])
  1741. let file = '/' . strpart(file, len(base) + 1)
  1742. else
  1743. let altdir = FugitiveExtractGitDir(file)
  1744. if len(altdir) && !s:cpath(dir, altdir)
  1745. return fugitive#Find(a:object, altdir)
  1746. endif
  1747. return file
  1748. endif
  1749. endif
  1750. let commits = split(commit, '\.\.\.-\@!', 1)
  1751. if len(commits) == 2
  1752. call map(commits, 'empty(v:val) ? "@" : v:val')
  1753. let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
  1754. endif
  1755. if commit !~# '^[0-9a-f]\{40,\}$\|^$'
  1756. let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit . (len(file) ? '^{}' : ''), '--']), '\<[0-9a-f]\{40,\}\>')
  1757. if empty(commit) && len(file)
  1758. let commit = repeat('0', 40)
  1759. endif
  1760. endif
  1761. if len(commit)
  1762. let f = s:PathJoin(urlprefix, commit . file)
  1763. else
  1764. let f = s:PathJoin(base, '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', ''))
  1765. endif
  1766. endif
  1767. endif
  1768. return s:VimSlash(f)
  1769. endfunction
  1770. function! s:Generate(object, ...) abort
  1771. let dir = a:0 ? a:1 : s:Dir()
  1772. let f = fugitive#Find(a:object, dir)
  1773. if !empty(f)
  1774. return f
  1775. elseif a:object ==# ':/'
  1776. return len(dir) ? s:VimSlash(s:DirUrlPrefix(dir) . '0') : '.'
  1777. endif
  1778. let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\=\zs.*')
  1779. return empty(file) ? '' : fnamemodify(s:VimSlash(file), ':p')
  1780. endfunction
  1781. function! s:DotRelative(path, ...) abort
  1782. let cwd = a:0 ? a:1 : getcwd()
  1783. let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
  1784. if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
  1785. return '.' . strpart(path, len(cwd))
  1786. endif
  1787. return a:path
  1788. endfunction
  1789. function! fugitive#Object(...) abort
  1790. let dir = a:0 > 1 ? s:Dir(a:2) : s:Dir()
  1791. let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
  1792. if !s:SameRepo(dir, fdir)
  1793. let rev = ''
  1794. endif
  1795. let tree = s:Tree(dir)
  1796. let full = a:0 ? a:1 : s:BufName('%')
  1797. let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
  1798. if empty(rev) && empty(tree)
  1799. return FugitiveGitPath(full)
  1800. elseif empty(rev)
  1801. let rev = fugitive#Path(full, './', dir)
  1802. if rev =~# '^\./.git\%(/\|$\)'
  1803. return FugitiveGitPath(full)
  1804. endif
  1805. endif
  1806. if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
  1807. return rev
  1808. else
  1809. return FugitiveGitPath(tree . rev[1:-1])
  1810. endif
  1811. endfunction
  1812. let s:var = '\%(<\%(cword\|cWORD\|cexpr\|cfile\|sfile\|slnum\|afile\|abuf\|amatch' . (has('clientserver') ? '\|client' : '') . '\)>\|%\|#<\=\d\+\|##\=\)'
  1813. let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
  1814. let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
  1815. let s:commit_expand = '!\\\@!#\=\d*\|!%'
  1816. function! s:BufName(var) abort
  1817. if a:var ==# '%'
  1818. return bufname(get(s:TempState(), 'origin_bufnr', ''))
  1819. elseif a:var =~# '^#\d*$'
  1820. let nr = get(s:TempState(+a:var[1:-1]), 'origin_bufnr', '')
  1821. return bufname(nr ? nr : +a:var[1:-1])
  1822. else
  1823. return expand(a:var)
  1824. endif
  1825. endfunction
  1826. function! s:ExpandVar(other, var, flags, esc, ...) abort
  1827. let cwd = a:0 ? a:1 : getcwd()
  1828. if a:other =~# '^\'
  1829. return a:other[1:-1]
  1830. elseif a:other =~# '^'''
  1831. return substitute(a:other[1:-2], "''", "'", "g")
  1832. elseif a:other =~# '^"'
  1833. return substitute(a:other[1:-2], '""', '"', "g")
  1834. elseif a:other =~# '^[!`]'
  1835. let buffer = s:BufName(a:other =~# '[0-9#]' ? '#' . matchstr(a:other, '\d\+') : '%')
  1836. let owner = s:Owner(buffer)
  1837. return len(owner) ? owner : '@'
  1838. elseif a:other =~# '^\~[~.]$'
  1839. return s:Slash(getcwd())
  1840. elseif len(a:other)
  1841. return expand(a:other)
  1842. elseif a:var ==# '<cfile>'
  1843. let bufnames = [expand('<cfile>')]
  1844. if get(maparg('<Plug><cfile>', 'c', 0, 1), 'expr')
  1845. try
  1846. let bufnames = [eval(maparg('<Plug><cfile>', 'c'))]
  1847. if bufnames[0] ==# "\<C-R>\<C-F>"
  1848. let bufnames = [expand('<cfile>')]
  1849. endif
  1850. catch
  1851. endtry
  1852. endif
  1853. elseif a:var =~# '^<'
  1854. let bufnames = [s:BufName(a:var)]
  1855. elseif a:var ==# '##'
  1856. let bufnames = map(argv(), 'fugitive#Real(v:val)')
  1857. else
  1858. let bufnames = [fugitive#Real(s:BufName(a:var))]
  1859. endif
  1860. let files = []
  1861. for bufname in bufnames
  1862. let flags = a:flags
  1863. let file = s:DotRelative(bufname, cwd)
  1864. while len(flags)
  1865. let flag = matchstr(flags, s:flag)
  1866. let flags = strpart(flags, len(flag))
  1867. if flag ==# ':.'
  1868. let file = s:DotRelative(fugitive#Real(file), cwd)
  1869. else
  1870. let file = fnamemodify(file, flag)
  1871. endif
  1872. endwhile
  1873. let file = s:Slash(file)
  1874. if file =~# '^fugitive://'
  1875. let [dir, commit, file_candidate] = s:DirCommitFile(file)
  1876. let tree = s:Tree(dir)
  1877. if len(tree) && len(file_candidate)
  1878. let file = (commit =~# '^.$' ? ':' : '') . commit . ':' .
  1879. \ s:DotRelative(tree . file_candidate)
  1880. elseif empty(file_candidate) && commit !~# '^.$'
  1881. let file = commit
  1882. endif
  1883. endif
  1884. call add(files, len(a:esc) ? shellescape(file) : file)
  1885. endfor
  1886. return join(files, "\1")
  1887. endfunction
  1888. if has('win32')
  1889. let s:fnameescape = " \t\n*?`%#'\"|!<"
  1890. else
  1891. let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
  1892. endif
  1893. function! s:Expand(rev, ...) abort
  1894. if a:rev =~# '^>' && s:Slash(@%) =~# '^fugitive://' && empty(s:DirCommitFile(@%)[1])
  1895. return s:Slash(@%)
  1896. elseif a:rev =~# '^>\=:[0-3]$'
  1897. let file = len(expand('%')) ? a:rev[-2:-1] . ':%' : '%'
  1898. elseif a:rev =~# '^>\%(:\=/\)\=$'
  1899. let file = '%'
  1900. elseif a:rev =~# '^>[> ]\@!' && @% !~# '^fugitive:' && s:Slash(@%) =~# '://\|^$'
  1901. let file = '%'
  1902. elseif a:rev ==# '>:'
  1903. let file = empty(s:DirCommitFile(@%)[0]) ? ':0:%' : '%'
  1904. elseif a:rev =~# '^>[> ]\@!'
  1905. let rev = (a:rev =~# '^>[~^]' ? '!' : '') . a:rev[1:-1]
  1906. let prefix = matchstr(rev, '^\%(\\.\|{[^{}]*}\|[^:]\)*')
  1907. if prefix !=# rev
  1908. let file = rev
  1909. else
  1910. let file = len(expand('%')) ? rev . ':%' : '%'
  1911. endif
  1912. elseif s:Slash(a:rev) =~# '^\a\a\+://'
  1913. let file = substitute(a:rev, '\\\@<!\%(#\a\|%\x\x\)', '\\&', 'g')
  1914. else
  1915. let file = a:rev
  1916. endif
  1917. return substitute(file,
  1918. \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
  1919. \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd()), "\1", " ")', 'g')
  1920. endfunction
  1921. function! fugitive#Expand(object) abort
  1922. return substitute(a:object,
  1923. \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
  1924. \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5)), "\1", " ")', 'g')
  1925. endfunction
  1926. function! s:SplitExpandChain(string, ...) abort
  1927. let list = []
  1928. let string = a:string
  1929. let dquote = '"\%([^"]\|""\|\\"\)*"\|'
  1930. let cwd = a:0 ? a:1 : getcwd()
  1931. while string =~# '\S'
  1932. if string =~# '^\s*|'
  1933. return [list, substitute(string, '^\s*', '', '')]
  1934. endif
  1935. let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
  1936. let string = strpart(string, len(arg))
  1937. let arg = substitute(arg, '^\s\+', '', '')
  1938. if !exists('seen_separator')
  1939. let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\%((literal)\)\=\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
  1940. \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
  1941. endif
  1942. let arg = substitute(arg,
  1943. \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~]\|^\~\w*\|\$\w\+\)\|' . s:expand,
  1944. \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
  1945. call extend(list, split(arg, "\1", 1))
  1946. if arg ==# '--'
  1947. let seen_separator = 1
  1948. endif
  1949. endwhile
  1950. return [list, '']
  1951. endfunction
  1952. let s:trees = {}
  1953. let s:indexes = {}
  1954. function! s:TreeInfo(dir, commit) abort
  1955. let key = s:GitDir(a:dir)
  1956. if a:commit =~# '^:\=[0-3]$'
  1957. let index = get(s:indexes, key, [])
  1958. let newftime = getftime(fugitive#Find('.git/index', a:dir))
  1959. if get(index, 0, -2) < newftime
  1960. let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
  1961. let s:indexes[key] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
  1962. if exec_error
  1963. return [{}, -1]
  1964. endif
  1965. for line in lines
  1966. let [info, filename] = split(line, "\t")
  1967. let [mode, sha, stage] = split(info, '\s\+')
  1968. let s:indexes[key][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
  1969. while filename =~# '/'
  1970. let filename = substitute(filename, '/[^/]*$', '', '')
  1971. let s:indexes[key][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
  1972. endwhile
  1973. endfor
  1974. endif
  1975. return [get(s:indexes[key][1], a:commit[-1:-1], {}), newftime]
  1976. elseif a:commit =~# '^\x\{40,\}$'
  1977. if !has_key(s:trees, key)
  1978. let s:trees[key] = {}
  1979. endif
  1980. if !has_key(s:trees[key], a:commit)
  1981. let ftime = s:ChompDefault('', [a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
  1982. if empty(ftime)
  1983. let s:trees[key][a:commit] = [{}, -1]
  1984. return s:trees[key][a:commit]
  1985. endif
  1986. let s:trees[key][a:commit] = [{}, +ftime]
  1987. let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
  1988. if exec_error
  1989. return s:trees[key][a:commit]
  1990. endif
  1991. for line in lines
  1992. let [info, filename] = split(line, "\t")
  1993. let [mode, type, sha, size] = split(info, '\s\+')
  1994. let s:trees[key][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
  1995. endfor
  1996. endif
  1997. return s:trees[key][a:commit]
  1998. endif
  1999. return [{}, -1]
  2000. endfunction
  2001. function! s:PathInfo(url) abort
  2002. let [dir, commit, file] = s:DirCommitFile(a:url)
  2003. if empty(dir) || !get(g:, 'fugitive_file_api', 1)
  2004. return [-1, '000000', '', '', -1]
  2005. endif
  2006. let path = substitute(file[1:-1], '/*$', '', '')
  2007. let [tree, ftime] = s:TreeInfo(dir, commit)
  2008. let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
  2009. if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
  2010. return [-1, '000000', '', '', -1]
  2011. else
  2012. return entry
  2013. endif
  2014. endfunction
  2015. function! fugitive#simplify(url) abort
  2016. let [dir, commit, file] = s:DirCommitFile(a:url)
  2017. if empty(dir)
  2018. return ''
  2019. elseif empty(commit)
  2020. return s:VimSlash(s:DirUrlPrefix(simplify(s:GitDir(dir))))
  2021. endif
  2022. if file =~# '/\.\.\%(/\|$\)'
  2023. let tree = s:Tree(dir)
  2024. if len(tree)
  2025. let path = simplify(tree . file)
  2026. if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
  2027. return s:VimSlash(path)
  2028. endif
  2029. endif
  2030. endif
  2031. return s:VimSlash(s:PathJoin(s:DirUrlPrefix(simplify(s:GitDir(dir))), commit . simplify(file)))
  2032. endfunction
  2033. function! fugitive#resolve(url) abort
  2034. let url = fugitive#simplify(a:url)
  2035. if url =~? '^fugitive:'
  2036. return url
  2037. else
  2038. return resolve(url)
  2039. endif
  2040. endfunction
  2041. function! fugitive#getftime(url) abort
  2042. return s:PathInfo(a:url)[0]
  2043. endfunction
  2044. function! fugitive#getfsize(url) abort
  2045. let entry = s:PathInfo(a:url)
  2046. if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
  2047. let dir = s:DirCommitFile(a:url)[0]
  2048. let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
  2049. endif
  2050. return entry[4]
  2051. endfunction
  2052. function! fugitive#getftype(url) abort
  2053. return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
  2054. endfunction
  2055. function! fugitive#filereadable(url) abort
  2056. return s:PathInfo(a:url)[2] ==# 'blob'
  2057. endfunction
  2058. function! fugitive#filewritable(url) abort
  2059. let [dir, commit, file] = s:DirCommitFile(a:url)
  2060. if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
  2061. return 0
  2062. endif
  2063. return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
  2064. endfunction
  2065. function! fugitive#isdirectory(url) abort
  2066. return s:PathInfo(a:url)[2] ==# 'tree'
  2067. endfunction
  2068. function! fugitive#getfperm(url) abort
  2069. let [dir, commit, file] = s:DirCommitFile(a:url)
  2070. let perm = getfperm(dir)
  2071. let fperm = s:PathInfo(a:url)[1]
  2072. if fperm ==# '040000'
  2073. let fperm = '000755'
  2074. endif
  2075. if fperm !~# '[15]'
  2076. let perm = tr(perm, 'x', '-')
  2077. endif
  2078. if fperm !~# '[45]$'
  2079. let perm = tr(perm, 'rw', '--')
  2080. endif
  2081. if commit !~# '^\d$'
  2082. let perm = tr(perm, 'w', '-')
  2083. endif
  2084. return perm ==# '---------' ? '' : perm
  2085. endfunction
  2086. function! s:UpdateIndex(dir, info) abort
  2087. let info = join(a:info[0:-2]) . "\t" . a:info[-1] . "\n"
  2088. let [error, exec_error] = s:StdoutToFile('', [a:dir, 'update-index', '--index-info'], info)
  2089. return !exec_error ? '' : len(error) ? error : 'unknown update-index error'
  2090. endfunction
  2091. function! fugitive#setfperm(url, perm) abort
  2092. let [dir, commit, file] = s:DirCommitFile(a:url)
  2093. let entry = s:PathInfo(a:url)
  2094. let perm = fugitive#getfperm(a:url)
  2095. if commit !~# '^\d$' || entry[2] !=# 'blob' ||
  2096. \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
  2097. return -2
  2098. endif
  2099. let error = s:UpdateIndex(dir, [a:perm =~# 'x' ? '000755' : '000644', entry[3], commit, file[1:-1]])
  2100. return len(error) ? -1 : 0
  2101. endfunction
  2102. if !exists('s:blobdirs')
  2103. let s:blobdirs = {}
  2104. endif
  2105. function! s:BlobTemp(url) abort
  2106. let [dir, commit, file] = s:DirCommitFile(a:url)
  2107. if empty(file)
  2108. return ''
  2109. endif
  2110. let key = s:GitDir(dir)
  2111. if !has_key(s:blobdirs, key)
  2112. let s:blobdirs[key] = tempname()
  2113. endif
  2114. let tempfile = s:blobdirs[key] . '/' . commit . file
  2115. let tempparent = fnamemodify(tempfile, ':h')
  2116. if !isdirectory(tempparent)
  2117. call mkdir(tempparent, 'p')
  2118. elseif isdirectory(tempfile)
  2119. if commit =~# '^\d$' && has('patch-7.4.1107')
  2120. call delete(tempfile, 'rf')
  2121. else
  2122. return ''
  2123. endif
  2124. endif
  2125. if commit =~# '^\d$' || !filereadable(tempfile)
  2126. let rev = s:DirRev(a:url)[1]
  2127. let blob_or_filters = fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
  2128. let exec_error = s:StdoutToFile(tempfile, [dir, 'cat-file', blob_or_filters, rev])[1]
  2129. if exec_error
  2130. call delete(tempfile)
  2131. return ''
  2132. endif
  2133. endif
  2134. return s:Resolve(tempfile)
  2135. endfunction
  2136. function! fugitive#readfile(url, ...) abort
  2137. let entry = s:PathInfo(a:url)
  2138. if entry[2] !=# 'blob'
  2139. return []
  2140. endif
  2141. let temp = s:BlobTemp(a:url)
  2142. if empty(temp)
  2143. return []
  2144. endif
  2145. return call('readfile', [temp] + a:000)
  2146. endfunction
  2147. function! fugitive#writefile(lines, url, ...) abort
  2148. let url = type(a:url) ==# type('') ? a:url : ''
  2149. let [dir, commit, file] = s:DirCommitFile(url)
  2150. let entry = s:PathInfo(url)
  2151. if commit =~# '^\d$' && entry[2] !=# 'tree'
  2152. let temp = tempname()
  2153. if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
  2154. call writefile(fugitive#readfile(url, 'b'), temp, 'b')
  2155. endif
  2156. call call('writefile', [a:lines, temp] + a:000)
  2157. let hash = s:ChompDefault('', [dir, '--literal-pathspecs', 'hash-object', '-w', FugitiveGitPath(temp)])
  2158. let mode = entry[1] !=# '000000' ? entry[1] : '100644'
  2159. if hash =~# '^\x\{40,\}$'
  2160. let error = s:UpdateIndex(dir, [mode, hash, commit, file[1:-1]])
  2161. if empty(error)
  2162. return 0
  2163. endif
  2164. endif
  2165. endif
  2166. return call('writefile', [a:lines, a:url] + a:000)
  2167. endfunction
  2168. let s:globsubs = {
  2169. \ '/**/': '/\%([^./][^/]*/\)*',
  2170. \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
  2171. \ '**/': '[^/]*\%(/[^./][^/]*\)*',
  2172. \ '**': '.*',
  2173. \ '/*': '/[^/.][^/]*',
  2174. \ '*': '[^/]*',
  2175. \ '?': '[^/]'}
  2176. function! fugitive#glob(url, ...) abort
  2177. let [repo, commit, glob] = s:DirCommitFile(a:url)
  2178. let dirglob = s:GitDir(repo)
  2179. let append = matchstr(glob, '/*$')
  2180. let glob = substitute(glob, '/*$', '', '')
  2181. let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
  2182. let results = []
  2183. for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
  2184. if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
  2185. continue
  2186. endif
  2187. let files = items(s:TreeInfo(dir, commit)[0])
  2188. if len(append)
  2189. call filter(files, 'v:val[1][2] ==# "tree"')
  2190. endif
  2191. call map(files, 'v:val[0]')
  2192. call filter(files, 'v:val =~# pattern')
  2193. let prepend = s:DirUrlPrefix(dir) . substitute(commit, '^:', '', '') . '/'
  2194. call sort(files)
  2195. call map(files, 's:VimSlash(s:PathJoin(prepend, v:val . append))')
  2196. call extend(results, files)
  2197. endfor
  2198. if a:0 > 1 && a:2
  2199. return results
  2200. else
  2201. return join(results, "\n")
  2202. endif
  2203. endfunction
  2204. function! fugitive#delete(url, ...) abort
  2205. let [dir, commit, file] = s:DirCommitFile(a:url)
  2206. if a:0 && len(a:1) || commit !~# '^\d$'
  2207. return -1
  2208. endif
  2209. let entry = s:PathInfo(a:url)
  2210. if entry[2] !=# 'blob'
  2211. return -1
  2212. endif
  2213. let error = s:UpdateIndex(dir, ['000000', '0000000000000000000000000000000000000000', commit, file[1:-1]])
  2214. return len(error) ? -1 : 0
  2215. endfunction
  2216. " Section: Completion
  2217. function! s:FilterEscape(items, ...) abort
  2218. let items = copy(a:items)
  2219. call map(items, 'fnameescape(v:val)')
  2220. if !a:0 || type(a:1) != type('')
  2221. let match = ''
  2222. else
  2223. let match = substitute(a:1, '^[+>]\|\\\@<![' . substitute(s:fnameescape, '\\', '', '') . ']', '\\&', 'g')
  2224. endif
  2225. let cmp = s:FileIgnoreCase(1) ? '==?' : '==#'
  2226. return filter(items, 'strpart(v:val, 0, strlen(match)) ' . cmp . ' match')
  2227. endfunction
  2228. function! s:GlobComplete(lead, pattern, ...) abort
  2229. if a:lead ==# '/'
  2230. return []
  2231. else
  2232. let results = glob(substitute(a:lead . a:pattern, '[\{}]', '\\&', 'g'), a:0 ? a:1 : 0, 1)
  2233. endif
  2234. call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
  2235. call map(results, 'v:val[ strlen(a:lead) : -1 ]')
  2236. return results
  2237. endfunction
  2238. function! fugitive#CompletePath(base, ...) abort
  2239. let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
  2240. let stripped = matchstr(a:base, '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\)')
  2241. let base = strpart(a:base, len(stripped))
  2242. if len(stripped) || a:0 < 4
  2243. let root = s:Tree(dir)
  2244. else
  2245. let root = a:4
  2246. endif
  2247. if root !=# '/' && len(root)
  2248. let root .= '/'
  2249. endif
  2250. if empty(stripped)
  2251. let stripped = matchstr(a:base, '^\%(:(literal)\|:\)')
  2252. let base = strpart(a:base, len(stripped))
  2253. endif
  2254. if base =~# '^\.git/' && len(dir)
  2255. let pattern = s:gsub(base[5:-1], '/', '*&').'*'
  2256. let fdir = fugitive#Find('.git/', dir)
  2257. let matches = s:GlobComplete(fdir, pattern)
  2258. let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
  2259. if len(cdir) && s:cpath(fdir) !=# s:cpath(cdir)
  2260. call extend(matches, s:GlobComplete(cdir, pattern))
  2261. endif
  2262. call s:Uniq(matches)
  2263. call map(matches, "'.git/' . v:val")
  2264. elseif base =~# '^\~/'
  2265. let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
  2266. elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/'
  2267. let matches = s:GlobComplete('', base . '*')
  2268. elseif len(root)
  2269. let matches = s:GlobComplete(root, s:gsub(base, '/', '*&').'*')
  2270. else
  2271. let matches = []
  2272. endif
  2273. call map(matches, 's:fnameescape(s:Slash(stripped . v:val))')
  2274. return matches
  2275. endfunction
  2276. function! fugitive#PathComplete(...) abort
  2277. return call('fugitive#CompletePath', a:000)
  2278. endfunction
  2279. function! s:CompleteHeads(dir) abort
  2280. if empty(a:dir)
  2281. return []
  2282. endif
  2283. let dir = fugitive#Find('.git/', a:dir)
  2284. return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
  2285. \ sort(s:LinesError([a:dir, 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes'])[0])
  2286. endfunction
  2287. function! fugitive#CompleteObject(base, ...) abort
  2288. let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
  2289. let tree = s:Tree(dir)
  2290. let cwd = getcwd()
  2291. let subdir = ''
  2292. if len(tree) && s:cpath(tree . '/', cwd[0 : len(tree)])
  2293. let subdir = strpart(cwd, len(tree) + 1) . '/'
  2294. endif
  2295. let base = s:Expand(a:base)
  2296. if a:base =~# '^!\d*$' && base !~# '^!'
  2297. return [base]
  2298. elseif base =~# '^\.\=/\|^:(' || base !~# ':'
  2299. let results = []
  2300. if base =~# '^refs/'
  2301. let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
  2302. let results += map(s:GlobComplete(cdir, base . '*'), 's:Slash(v:val)')
  2303. call map(results, 's:fnameescape(v:val)')
  2304. elseif base !~# '^\.\=/\|^:('
  2305. let heads = s:CompleteHeads(dir)
  2306. if filereadable(fugitive#Find('.git/refs/stash', dir))
  2307. let heads += ["stash"]
  2308. let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
  2309. endif
  2310. let results += s:FilterEscape(heads, fnameescape(base))
  2311. endif
  2312. let results += a:0 == 1 || a:0 >= 3 ? fugitive#CompletePath(base, 0, '', dir, a:0 >= 4 ? a:4 : tree) : fugitive#CompletePath(base)
  2313. return results
  2314. elseif base =~# '^:'
  2315. let entries = s:LinesError(['ls-files','--stage'], dir)[0]
  2316. if base =~# ':\./'
  2317. call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
  2318. endif
  2319. call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
  2320. if base !~# '^:[0-3]\%(:\|$\)'
  2321. call filter(entries,'v:val[1] == "0"')
  2322. call map(entries,'v:val[2:-1]')
  2323. endif
  2324. else
  2325. let parent = matchstr(base, '.*[:/]')
  2326. let entries = s:LinesError(['ls-tree', substitute(parent, ':\zs\./', '\=subdir', '')], dir)[0]
  2327. call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
  2328. call map(entries,'parent.s:sub(v:val,".*\t","")')
  2329. endif
  2330. return s:FilterEscape(entries, fnameescape(base))
  2331. endfunction
  2332. function! s:CompleteSub(subcommand, A, L, P, ...) abort
  2333. let pre = strpart(a:L, 0, a:P)
  2334. if pre =~# ' -- '
  2335. return fugitive#CompletePath(a:A)
  2336. elseif a:A =~# '^-' || a:A is# 0
  2337. return s:FilterEscape(split(s:ChompDefault('', [a:subcommand, '--git-completion-helper']), ' '), a:A)
  2338. elseif !a:0
  2339. return fugitive#CompleteObject(a:A, s:Dir())
  2340. elseif type(a:1) == type(function('tr'))
  2341. return call(a:1, [a:A, a:L, a:P] + (a:0 > 1 ? a:2 : []))
  2342. else
  2343. return s:FilterEscape(a:1, a:A)
  2344. endif
  2345. endfunction
  2346. function! s:CompleteRevision(A, L, P, ...) abort
  2347. return s:FilterEscape(s:CompleteHeads(a:0 ? a:1 : s:Dir()), a:A)
  2348. endfunction
  2349. function! s:CompleteRemote(A, L, P, ...) abort
  2350. let dir = a:0 ? a:1 : s:Dir()
  2351. let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
  2352. if !empty(remote)
  2353. let matches = s:LinesError([dir, 'ls-remote', remote])[0]
  2354. call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
  2355. call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
  2356. else
  2357. let matches = s:LinesError([dir, 'remote'])[0]
  2358. endif
  2359. return s:FilterEscape(matches, a:A)
  2360. endfunction
  2361. " Section: Buffer auto-commands
  2362. augroup fugitive_dummy_events
  2363. autocmd!
  2364. autocmd User Fugitive* "
  2365. autocmd BufWritePre,FileWritePre,FileWritePost * "
  2366. autocmd BufNewFile * "
  2367. autocmd QuickfixCmdPre,QuickfixCmdPost * "
  2368. augroup END
  2369. function! s:ReplaceCmd(cmd) abort
  2370. let temp = tempname()
  2371. let [err, exec_error] = s:StdoutToFile(temp, a:cmd)
  2372. if exec_error
  2373. throw 'fugitive: ' . (len(err) ? substitute(err, "\n$", '', '') : 'unknown error running ' . string(a:cmd))
  2374. endif
  2375. setlocal noswapfile
  2376. silent exe 'lockmarks keepalt noautocmd 0read ++edit' s:fnameescape(temp)
  2377. if &foldenable && foldlevel('$') > 0
  2378. set nofoldenable
  2379. silent keepjumps $delete _
  2380. set foldenable
  2381. else
  2382. silent keepjumps $delete _
  2383. endif
  2384. call delete(temp)
  2385. if s:cpath(s:AbsoluteVimPath(bufnr('$')), temp)
  2386. silent! noautocmd execute bufnr('$') . 'bwipeout'
  2387. endif
  2388. endfunction
  2389. function! s:FormatLog(dict) abort
  2390. return a:dict.commit . ' ' . a:dict.subject
  2391. endfunction
  2392. function! s:FormatRebase(dict) abort
  2393. return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
  2394. endfunction
  2395. function! s:FormatFile(dict) abort
  2396. return a:dict.status . ' ' . a:dict.filename
  2397. endfunction
  2398. function! s:Format(val) abort
  2399. if type(a:val) == type({})
  2400. return s:Format{a:val.type}(a:val)
  2401. elseif type(a:val) == type([])
  2402. return map(copy(a:val), 's:Format(v:val)')
  2403. else
  2404. return '' . a:val
  2405. endif
  2406. endfunction
  2407. function! s:AddHeader(to, key, value) abort
  2408. if empty(a:value)
  2409. return
  2410. endif
  2411. call add(a:to.lines, a:key . ':' . (len(a:value) ? ' ' . a:value : ''))
  2412. endfunction
  2413. function! s:AddSection(to, label, lines, ...) abort
  2414. let note = a:0 ? a:1 : ''
  2415. if empty(a:lines) && empty(note)
  2416. return
  2417. endif
  2418. call extend(a:to.lines, ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
  2419. endfunction
  2420. function! s:AddDiffSection(to, stat, label, files) abort
  2421. if empty(a:files)
  2422. return
  2423. endif
  2424. let diff_section = a:stat.diff[a:label]
  2425. let expanded = a:stat.expanded[a:label]
  2426. let was_expanded = get(getbufvar(a:stat.bufnr, 'fugitive_expanded', {}), a:label, {})
  2427. call extend(a:to.lines, ['', a:label . ' (' . len(a:files) . ')'])
  2428. for file in a:files
  2429. call add(a:to.lines, s:Format(file))
  2430. if has_key(was_expanded, file.filename)
  2431. let [diff, start] = s:StageInlineGetDiff(diff_section, file)
  2432. if len(diff)
  2433. let expanded[file.filename] = [start]
  2434. call extend(a:to.lines, diff)
  2435. endif
  2436. endif
  2437. endfor
  2438. endfunction
  2439. function! s:QueryLog(refspec, limit, dir) abort
  2440. let [log, exec_error] = s:LinesError(['log', '-n', '' . a:limit, '--pretty=format:%h%x09%s'] + a:refspec + ['--'], a:dir)
  2441. call map(log, 'split(v:val, "\t", 1)')
  2442. call map(log, '{"type": "Log", "commit": v:val[0], "subject": join(v:val[1 : -1], "\t")}')
  2443. let result = {'error': exec_error ? 1 : 0, 'overflow': 0, 'entries': log}
  2444. if len(log) == a:limit
  2445. call remove(log, -1)
  2446. let result.overflow = 1
  2447. endif
  2448. return result
  2449. endfunction
  2450. function! s:QueryLogRange(old, new, dir) abort
  2451. if empty(a:old) || empty(a:new)
  2452. return {'error': 2, 'overflow': 0, 'entries': []}
  2453. endif
  2454. return s:QueryLog([a:old . '..' . a:new], 256, a:dir)
  2455. endfunction
  2456. function! s:AddLogSection(to, label, log) abort
  2457. if empty(a:log.entries)
  2458. return
  2459. endif
  2460. let label = a:label . ' (' . len(a:log.entries) . (a:log.overflow ? '+' : '') . ')'
  2461. call extend(a:to.lines, ['', label] + s:Format(a:log.entries))
  2462. endfunction
  2463. let s:rebase_abbrevs = {
  2464. \ 'p': 'pick',
  2465. \ 'r': 'reword',
  2466. \ 'e': 'edit',
  2467. \ 's': 'squash',
  2468. \ 'f': 'fixup',
  2469. \ 'x': 'exec',
  2470. \ 'd': 'drop',
  2471. \ 'l': 'label',
  2472. \ 't': 'reset',
  2473. \ 'm': 'merge',
  2474. \ 'b': 'break',
  2475. \ }
  2476. function! s:MapStatus() abort
  2477. call fugitive#MapJumps()
  2478. call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
  2479. call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
  2480. call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
  2481. call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
  2482. call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
  2483. call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
  2484. call s:Map('n', 'U', ":<C-U>Git reset -q<CR>", '<silent>')
  2485. call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
  2486. call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
  2487. call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
  2488. call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
  2489. call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
  2490. call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
  2491. call s:Map('n', 'C', ":echoerr 'fugitive: C has been removed in favor of cc'<CR>", '<silent><unique>')
  2492. call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
  2493. call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
  2494. call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
  2495. call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide', line('.'),v:count)<CR>", '<silent>')
  2496. call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show', line('.'),v:count)<CR>", '<silent>')
  2497. call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
  2498. call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
  2499. call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
  2500. call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
  2501. call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
  2502. call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
  2503. call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
  2504. call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
  2505. call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
  2506. call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
  2507. call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
  2508. call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
  2509. call s:Map('n', 'p', ":<C-U>if v:count<Bar>silent exe <SID>GF('pedit')<Bar>else<Bar>echoerr 'Use = for inline diff, I for :Git add/reset --patch, 1p for :pedit'<Bar>endif<CR>", '<silent>')
  2510. call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
  2511. call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'), 1)<CR>", '<silent>')
  2512. call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"), 1)<CR>", '<silent>')
  2513. call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
  2514. call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic. Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
  2515. call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
  2516. call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
  2517. call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
  2518. call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
  2519. call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
  2520. call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
  2521. call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
  2522. call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
  2523. endfunction
  2524. function! s:StatusProcess(result, stat) abort
  2525. let stat = a:stat
  2526. let status_exec = a:stat.status
  2527. let config = a:stat.config
  2528. let dir = s:Dir(config)
  2529. try
  2530. let [staged, unstaged, untracked] = [[], [], []]
  2531. let stat.props = {}
  2532. if empty(status_exec)
  2533. let stat.branch = FugitiveHead(0, config)
  2534. elseif status_exec.exit_status
  2535. let stat.error = s:JoinChomp(status_exec.stderr)
  2536. return
  2537. elseif status_exec.args[-1] ==# '--porcelain=v2'
  2538. let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
  2539. let i = 0
  2540. while i < len(output)
  2541. let line = output[i]
  2542. let prop = matchlist(line, '# \(\S\+\) \(.*\)')
  2543. if len(prop)
  2544. let stat.props[prop[1]] = prop[2]
  2545. elseif line[0] ==# '?'
  2546. call add(untracked, {'type': 'File', 'status': line[0], 'filename': line[2:-1], 'relative': [line[2:-1]]})
  2547. elseif line[0] !=# '#'
  2548. if line[0] ==# 'u'
  2549. let file = matchstr(line, '^.\{37\} \x\{40,\} \x\{40,\} \x\{40,\} \zs.*$')
  2550. else
  2551. let file = matchstr(line, '^.\{30\} \x\{40,\} \x\{40,\} \zs.*$')
  2552. endif
  2553. if line[0] ==# '2'
  2554. let i += 1
  2555. let file = matchstr(file, ' \zs.*')
  2556. let relative = [file, output[i]]
  2557. else
  2558. let relative = [file]
  2559. endif
  2560. let filename = join(reverse(copy(relative)), ' -> ')
  2561. let sub = matchstr(line, '^[12u] .. \zs....')
  2562. if line[2] !=# '.'
  2563. call add(staged, {'type': 'File', 'status': line[2], 'filename': filename, 'relative': relative, 'submodule': sub})
  2564. endif
  2565. if line[3] !=# '.'
  2566. let sub = matchstr(line, '^[12u] .. \zs....')
  2567. call add(unstaged, {'type': 'File', 'status': get({'C':'M','M':'?','U':'?'}, matchstr(sub, 'S\.*\zs[CMU]'), line[3]), 'filename': file, 'relative': [file], 'submodule': sub})
  2568. endif
  2569. endif
  2570. let i += 1
  2571. endwhile
  2572. let stat.branch = substitute(get(stat.props, 'branch.head', '(unknown)'), '\C^(\%(detached\|unknown\))$', '', '')
  2573. else
  2574. let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
  2575. while get(output, 0, '') =~# '^\l\+:'
  2576. call remove(output, 0)
  2577. endwhile
  2578. let branch = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
  2579. if branch =~# '\.\.\.'
  2580. let stat.branch = split(branch, '\.\.\.')[0]
  2581. else
  2582. let stat.branch = branch ==# 'HEAD' ? '' : branch
  2583. endif
  2584. let i = 0
  2585. while i < len(output)
  2586. let line = output[i]
  2587. let file = line[3:-1]
  2588. let i += 1
  2589. if line[2] !=# ' '
  2590. continue
  2591. endif
  2592. if line[0:1] =~# '[RC]'
  2593. let relative = [file, output[i]]
  2594. let i += 1
  2595. else
  2596. let relative = [file]
  2597. endif
  2598. let filename = join(reverse(copy(relative)), ' -> ')
  2599. if line[0] !~# '[ ?!#]'
  2600. call add(staged, {'type': 'File', 'status': line[0], 'filename': filename, 'relative': relative, 'submodule': ''})
  2601. endif
  2602. if line[0:1] ==# '??'
  2603. call add(untracked, {'type': 'File', 'status': line[1], 'filename': filename, 'relative': relative})
  2604. elseif line[1] !~# '[ !#]'
  2605. call add(unstaged, {'type': 'File', 'status': line[1], 'filename': file, 'relative': [file], 'submodule': ''})
  2606. endif
  2607. endwhile
  2608. endif
  2609. let diff_cmd = stat.cmd + ['-c', 'diff.suppressBlankEmpty=false', '-c', 'core.quotePath=false', 'diff', '--color=never', '--no-ext-diff', '--no-prefix']
  2610. let stat.diff = {'Staged': {'stdout': ['']}, 'Unstaged': {'stdout': ['']}}
  2611. if len(staged)
  2612. let stat.diff['Staged'] = fugitive#Execute(diff_cmd + ['--cached'], function('len'))
  2613. endif
  2614. if len(unstaged)
  2615. let stat.diff['Unstaged'] = fugitive#Execute(diff_cmd + ['--'] + map(copy(unstaged), 'stat.work_tree . "/" . v:val.relative[0]'), function('len'))
  2616. endif
  2617. let [stat.staged, stat.unstaged, stat.untracked] = [staged, unstaged, untracked]
  2618. let stat.files = {'Staged': {}, 'Unstaged': {}}
  2619. for dict in staged
  2620. let stat.files['Staged'][dict.filename] = dict
  2621. endfor
  2622. for dict in unstaged
  2623. let stat.files['Unstaged'][dict.filename] = dict
  2624. endfor
  2625. let branch = stat.branch
  2626. let fetch_remote = config.Get('branch.' . branch . '.remote', 'origin')
  2627. let push_remote = config.Get('branch.' . branch . '.pushRemote',
  2628. \ config.Get('remote.pushDefault', fetch_remote))
  2629. if fetch_remote !=# '.' && empty(config.Get('remote.' . fetch_remote . '.fetch'))
  2630. let fetch_remote = ''
  2631. endif
  2632. if push_remote !=# '.' && empty(config.Get('remote.' . push_remote . '.push', config.Get('remote.' . push_remote . '.fetch')))
  2633. let push_remote = ''
  2634. endif
  2635. let stat.fetch_remote = fetch_remote
  2636. let stat.push_remote = push_remote
  2637. if empty(stat.fetch_remote) || empty(branch)
  2638. let stat.merge = ''
  2639. else
  2640. let stat.merge = config.Get('branch.' . branch . '.merge')
  2641. endif
  2642. let push_default = FugitiveConfigGet('push.default', config)
  2643. if empty(push_default)
  2644. let push_default = fugitive#GitVersion(2) ? 'simple' : 'matching'
  2645. endif
  2646. if push_default ==# 'upstream'
  2647. let stat.push = stat.merge
  2648. elseif empty(stat.push_remote) || empty(branch)
  2649. let stat.push = ''
  2650. else
  2651. let stat.push = 'refs/heads/' . branch
  2652. endif
  2653. let stat.pull_type = 'Pull'
  2654. if len(stat.merge)
  2655. let rebase = FugitiveConfigGet('branch.' . branch . '.rebase', config)
  2656. if empty(rebase)
  2657. let rebase = FugitiveConfigGet('pull.rebase', config)
  2658. endif
  2659. if rebase =~# '^\%(true\|yes\|on\|1\|interactive\|merges\|preserve\)$'
  2660. let stat.pull_type = 'Rebase'
  2661. elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
  2662. let stat.pull_type = 'Merge'
  2663. endif
  2664. endif
  2665. endtry
  2666. endfunction
  2667. function! s:StatusRender(stat) abort
  2668. try
  2669. let stat = a:stat
  2670. call fugitive#Wait(stat.running)
  2671. if has_key(stat, 'error')
  2672. return 'echoerr ' . string('fugitive: ' . stat.error)
  2673. endif
  2674. let [staged, unstaged, untracked, config] = [stat.staged, stat.unstaged, stat.untracked, stat.config]
  2675. let dir = s:Dir(config)
  2676. let pull_ref = stat.merge
  2677. if stat.fetch_remote !=# '.'
  2678. let pull_ref = substitute(pull_ref, '^refs/heads/', 'refs/remotes/' . stat.fetch_remote . '/', '')
  2679. endif
  2680. let push_ref = stat.push
  2681. if stat.push_remote !=# '.'
  2682. let push_ref = substitute(push_ref, '^refs/heads/', 'refs/remotes/' . stat.push_remote . '/', '')
  2683. endif
  2684. let push_short = substitute(push_ref, '^refs/\w\+/', '', '')
  2685. let pull_short = substitute(pull_ref, '^refs/\w\+/', '', '')
  2686. if isdirectory(fugitive#Find('.git/rebase-merge/', dir))
  2687. let rebasing_dir = fugitive#Find('.git/rebase-merge/', dir)
  2688. elseif isdirectory(fugitive#Find('.git/rebase-apply/', dir))
  2689. let rebasing_dir = fugitive#Find('.git/rebase-apply/', dir)
  2690. endif
  2691. call fugitive#Wait(stat.rev_parse)
  2692. let head = empty(stat.branch) ? stat.rev_parse.stdout[0] : stat.branch
  2693. let rebasing = []
  2694. let rebasing_head = 'detached HEAD'
  2695. if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
  2696. let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
  2697. let len = len(stat.rev_parse.stdout[0])
  2698. let lines = readfile(rebasing_dir . 'git-rebase-todo')
  2699. if getfsize(rebasing_dir . 'done') > 0
  2700. let done = readfile(rebasing_dir . 'done')
  2701. call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
  2702. let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
  2703. let lines = done + lines
  2704. endif
  2705. call reverse(lines)
  2706. for line in lines
  2707. let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
  2708. if len(match) && match[1] !~# 'exec\|merge\|label'
  2709. call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
  2710. endif
  2711. endfor
  2712. endif
  2713. let sequencing = []
  2714. if filereadable(fugitive#Find('.git/sequencer/todo', dir))
  2715. for line in reverse(readfile(fugitive#Find('.git/sequencer/todo', dir)))
  2716. let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
  2717. if len(match) && match[1] !~# 'exec\|merge\|label'
  2718. call add(sequencing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': match[2], 'subject': match[3]})
  2719. endif
  2720. endfor
  2721. elseif filereadable(fugitive#Find('.git/MERGE_MSG', dir))
  2722. if filereadable(fugitive#Find('.git/CHERRY_PICK_HEAD', dir))
  2723. let pick_head = fugitive#Execute(['rev-parse', '--short', 'CHERRY_PICK_HEAD', '--'], dir).stdout[0]
  2724. call add(sequencing, {'type': 'Rebase', 'status': 'pick', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
  2725. elseif filereadable(fugitive#Find('.git/REVERT_HEAD', dir))
  2726. let pick_head = fugitive#Execute(['rev-parse', '--short', 'REVERT_HEAD', '--'], dir).stdout[0]
  2727. call add(sequencing, {'type': 'Rebase', 'status': 'revert', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
  2728. endif
  2729. endif
  2730. let stat.expanded = {'Staged': {}, 'Unstaged': {}}
  2731. let to = {'lines': []}
  2732. call s:AddHeader(to, 'Head', head)
  2733. call s:AddHeader(to, stat.pull_type, pull_short)
  2734. if push_ref !=# pull_ref
  2735. call s:AddHeader(to, 'Push', push_short)
  2736. endif
  2737. if empty(stat.work_tree)
  2738. if get(fugitive#ConfigGetAll('core.bare', config), 0, '') !~# '^\%(false\|no|off\|0\|\)$'
  2739. call s:AddHeader(to, 'Bare', 'yes')
  2740. else
  2741. call s:AddHeader(to, 'Error', s:worktree_error)
  2742. endif
  2743. endif
  2744. if get(fugitive#ConfigGetAll('advice.statusHints', config), 0, 'true') !~# '^\%(false\|no|off\|0\|\)$'
  2745. call s:AddHeader(to, 'Help', 'g?')
  2746. endif
  2747. call s:AddSection(to, 'Rebasing ' . rebasing_head, rebasing)
  2748. call s:AddSection(to, get(get(sequencing, 0, {}), 'tous', '') ==# 'revert' ? 'Reverting' : 'Cherry Picking', sequencing)
  2749. call s:AddSection(to, 'Untracked', untracked)
  2750. call s:AddDiffSection(to, stat, 'Unstaged', unstaged)
  2751. call s:AddDiffSection(to, stat, 'Staged', staged)
  2752. let unique_push_ref = push_ref ==# pull_ref ? '' : push_ref
  2753. let unpushed_push = s:QueryLogRange(unique_push_ref, head, dir)
  2754. if get(stat.props, 'branch.ab') =~# '^+0 '
  2755. let unpushed_pull = {'error': 0, 'overflow': 0, 'entries': []}
  2756. else
  2757. let unpushed_pull = s:QueryLogRange(pull_ref, head, dir)
  2758. endif
  2759. " If the push ref is defined but nowhere to be found at the remote,
  2760. " pretend it's the same as the pull ref
  2761. if unpushed_push.error == 1
  2762. let unpushed_push = unpushed_pull
  2763. endif
  2764. call s:AddLogSection(to, 'Unpushed to ' . push_short, unpushed_push)
  2765. call s:AddLogSection(to, 'Unpushed to ' . pull_short, unpushed_pull)
  2766. if unpushed_push.error && unpushed_pull.error && empty(rebasing) &&
  2767. \ !empty(stat.push_remote . stat.fetch_remote)
  2768. call s:AddLogSection(to, 'Unpushed to *', s:QueryLog([head, '--not', '--remotes'], 256, dir))
  2769. endif
  2770. call s:AddLogSection(to, 'Unpulled from ' . push_short, s:QueryLogRange(head, unique_push_ref, dir))
  2771. if len(pull_ref) && get(stat.props, 'branch.ab') !~# ' -0$'
  2772. call s:AddLogSection(to, 'Unpulled from ' . pull_short, s:QueryLogRange(head, pull_ref, dir))
  2773. endif
  2774. let bufnr = stat.bufnr
  2775. setlocal noreadonly modifiable
  2776. if len(to.lines) < line('$')
  2777. silent keepjumps execute (len(to.lines)+1) . ',$delete_'
  2778. endif
  2779. call setline(1, to.lines)
  2780. call setbufvar(bufnr, 'fugitive_status', stat)
  2781. call setbufvar(bufnr, 'fugitive_expanded', stat.expanded)
  2782. setlocal nomodified readonly nomodifiable
  2783. return ''
  2784. finally
  2785. let b:fugitive_type = 'index'
  2786. endtry
  2787. endfunction
  2788. function! s:StatusRetrieve(bufnr, ...) abort
  2789. let amatch = s:Slash(fnamemodify(bufname(a:bufnr), ':p'))
  2790. let dir = s:Dir(a:bufnr)
  2791. let config = fugitive#Config(dir, function('len'))
  2792. let cmd = [dir]
  2793. if amatch !~# '^fugitive:' && s:cpath($GIT_INDEX_FILE !=# '' ? resolve(s:GitIndexFileEnv()) : fugitive#Find('.git/index', dir)) !=# s:cpath(amatch)
  2794. let cmd += [{'env': {'GIT_INDEX_FILE': FugitiveGitPath(amatch)}}]
  2795. endif
  2796. if fugitive#GitVersion(2, 15)
  2797. call add(cmd, '--no-optional-locks')
  2798. endif
  2799. let rev_parse_cmd = cmd + ['rev-parse', '--short', 'HEAD', '--']
  2800. let stat = {'bufnr': a:bufnr, 'reltime': reltime(), 'work_tree': s:Tree(dir), 'cmd': cmd, 'config': config}
  2801. if empty(stat.work_tree)
  2802. let stat.rev_parse = call('fugitive#Execute', [rev_parse_cmd, function('s:StatusProcess'), stat] + a:000)
  2803. let stat.status = {}
  2804. let stat.running = stat.rev_parse
  2805. else
  2806. let stat.rev_parse = fugitive#Execute(rev_parse_cmd)
  2807. let status_cmd = cmd + ['status', '-bz', fugitive#GitVersion(2, 11) ? '--porcelain=v2' : '--porcelain']
  2808. let stat.status = call('fugitive#Execute', [status_cmd, function('s:StatusProcess'), stat] + a:000)
  2809. let stat.running = stat.status
  2810. endif
  2811. return stat
  2812. endfunction
  2813. function! fugitive#BufReadStatus(cmdbang) abort
  2814. exe s:VersionCheck()
  2815. if a:cmdbang
  2816. unlet! b:fugitive_expanded
  2817. endif
  2818. let b:fugitive_type = 'index'
  2819. let stat = s:StatusRetrieve(bufnr(''))
  2820. try
  2821. let b:fugitive_loading = stat
  2822. doautocmd <nomodeline> BufReadPre
  2823. setlocal readonly nomodifiable noswapfile nomodeline buftype=nowrite
  2824. call s:MapStatus()
  2825. call s:StatusRender(stat)
  2826. doautocmd <nomodeline> BufReadPost
  2827. if &bufhidden ==# ''
  2828. setlocal bufhidden=delete
  2829. endif
  2830. if !exists('b:dispatch')
  2831. let b:dispatch = ':Git fetch --all'
  2832. endif
  2833. setlocal filetype=fugitive
  2834. return s:DoAutocmd('User FugitiveIndex')
  2835. finally
  2836. call setbufvar(stat.bufnr, 'fugitive_loading', {})
  2837. endtry
  2838. endfunction
  2839. function! fugitive#FileReadCmd(...) abort
  2840. let amatch = a:0 ? a:1 : expand('<amatch>')
  2841. let [dir, rev] = s:DirRev(amatch)
  2842. let line = a:0 > 1 ? a:2 : line("'[")
  2843. if empty(dir)
  2844. return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
  2845. endif
  2846. if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
  2847. let cmd = [dir, 'log', '--pretty=format:%B', '-1', rev, '--']
  2848. elseif rev ==# ':'
  2849. let cmd = [dir, 'status', '--short']
  2850. else
  2851. let cmd = [dir, 'cat-file', '-p', rev, '--']
  2852. endif
  2853. let temp = tempname()
  2854. let [err, exec_error] = s:StdoutToFile(temp, cmd)
  2855. if exec_error
  2856. call delete(temp)
  2857. return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
  2858. else
  2859. return 'silent keepalt ' . line . 'read ' . s:fnameescape(temp) . '|call delete(' . string(temp) . ')'
  2860. endif
  2861. endfunction
  2862. function! fugitive#FileWriteCmd(...) abort
  2863. let temp = tempname()
  2864. let amatch = a:0 ? a:1 : expand('<amatch>')
  2865. let autype = a:0 > 1 ? 'Buf' : 'File'
  2866. if exists('#' . autype . 'WritePre')
  2867. execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
  2868. endif
  2869. try
  2870. let [dir, commit, file] = s:DirCommitFile(amatch)
  2871. if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
  2872. return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
  2873. endif
  2874. silent execute "noautocmd keepalt '[,']write ".temp
  2875. let hash = s:TreeChomp([dir, '--literal-pathspecs', 'hash-object', '-w', '--', FugitiveGitPath(temp)])
  2876. let old_mode = matchstr(s:ChompDefault('', ['ls-files', '--stage', '.' . file], dir), '^\d\+')
  2877. if empty(old_mode)
  2878. let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
  2879. endif
  2880. let error = s:UpdateIndex(dir, [old_mode, hash, commit, file[1:-1]])
  2881. if empty(error)
  2882. setlocal nomodified
  2883. if exists('#' . autype . 'WritePost')
  2884. execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
  2885. endif
  2886. exe s:DoAutocmdChanged(dir)
  2887. return ''
  2888. else
  2889. return 'echoerr '.string('fugitive: '.error)
  2890. endif
  2891. catch /^fugitive:/
  2892. return 'echoerr ' . string(v:exception)
  2893. finally
  2894. call delete(temp)
  2895. endtry
  2896. endfunction
  2897. function! fugitive#BufReadCmd(...) abort
  2898. let amatch = a:0 ? a:1 : expand('<amatch>')
  2899. let [dir, rev] = s:DirRev(amatch)
  2900. if empty(dir)
  2901. return 'echo "Invalid Fugitive URL"'
  2902. endif
  2903. call s:InitializeBuffer(dir)
  2904. if rev ==# ':'
  2905. return fugitive#BufReadStatus(v:cmdbang)
  2906. endif
  2907. try
  2908. if rev =~# '^:\d$'
  2909. let b:fugitive_type = 'stage'
  2910. else
  2911. let r = fugitive#Execute([dir, 'cat-file', '-t', rev])
  2912. let b:fugitive_type = get(r.stdout, 0, '')
  2913. if r.exit_status && rev =~# '^:0'
  2914. let r = fugitive#Execute([dir, 'write-tree', '--prefix=' . rev[3:-1]])
  2915. let sha = get(r.stdout, 0, '')
  2916. let b:fugitive_type = 'tree'
  2917. endif
  2918. if r.exit_status
  2919. let error = substitute(join(r.stderr, "\n"), "\n*$", '', '')
  2920. unlet b:fugitive_type
  2921. setlocal noswapfile
  2922. if empty(&bufhidden)
  2923. setlocal bufhidden=delete
  2924. endif
  2925. if rev =~# '^:\d:'
  2926. let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
  2927. return 'doautocmd BufNewFile'
  2928. else
  2929. setlocal readonly nomodifiable
  2930. return 'doautocmd BufNewFile|echo ' . string(error)
  2931. endif
  2932. elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
  2933. return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
  2934. endif
  2935. if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
  2936. let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
  2937. endif
  2938. endif
  2939. if b:fugitive_type !=# 'blob'
  2940. setlocal nomodeline
  2941. endif
  2942. setlocal noreadonly modifiable
  2943. let pos = getpos('.')
  2944. silent keepjumps %delete_
  2945. setlocal endofline
  2946. let events = ['User FugitiveObject', 'User Fugitive' . substitute(b:fugitive_type, '^\l', '\u&', '')]
  2947. try
  2948. if b:fugitive_type !=# 'blob'
  2949. setlocal foldmarker=<<<<<<<<,>>>>>>>>
  2950. endif
  2951. exe s:DoAutocmd('BufReadPre')
  2952. if b:fugitive_type ==# 'tree'
  2953. let b:fugitive_display_format = b:fugitive_display_format % 2
  2954. if b:fugitive_display_format
  2955. call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
  2956. else
  2957. if !exists('sha')
  2958. let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
  2959. endif
  2960. call s:ReplaceCmd([dir, 'show', '--no-color', sha])
  2961. endif
  2962. elseif b:fugitive_type ==# 'tag'
  2963. let b:fugitive_display_format = b:fugitive_display_format % 2
  2964. if b:fugitive_display_format
  2965. call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
  2966. else
  2967. call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
  2968. endif
  2969. elseif b:fugitive_type ==# 'commit'
  2970. let b:fugitive_display_format = b:fugitive_display_format % 2
  2971. if b:fugitive_display_format
  2972. call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
  2973. else
  2974. 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%B', rev])
  2975. keepjumps 1
  2976. keepjumps call search('^parent ')
  2977. if getline('.') ==# 'parent '
  2978. silent lockmarks keepjumps delete_
  2979. else
  2980. silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
  2981. endif
  2982. keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
  2983. if lnum
  2984. silent lockmarks keepjumps delete_
  2985. end
  2986. silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
  2987. keepjumps 1
  2988. endif
  2989. elseif b:fugitive_type ==# 'stage'
  2990. call s:ReplaceCmd([dir, 'ls-files', '--stage'])
  2991. elseif b:fugitive_type ==# 'blob'
  2992. let blob_or_filters = rev =~# ':' && fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
  2993. call s:ReplaceCmd([dir, 'cat-file', blob_or_filters, rev])
  2994. endif
  2995. finally
  2996. keepjumps call setpos('.',pos)
  2997. setlocal nomodified noswapfile
  2998. let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
  2999. if modifiable
  3000. let events = ['User FugitiveStageBlob']
  3001. endif
  3002. let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
  3003. if empty(&bufhidden)
  3004. setlocal bufhidden=delete
  3005. endif
  3006. let &l:modifiable = modifiable
  3007. call fugitive#MapJumps()
  3008. if b:fugitive_type !=# 'blob'
  3009. call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
  3010. call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
  3011. setlocal filetype=git
  3012. endif
  3013. endtry
  3014. setlocal modifiable
  3015. return s:DoAutocmd('BufReadPost') .
  3016. \ (modifiable ? '' : '|setl nomodifiable') . '|' .
  3017. \ call('s:DoAutocmd', events)
  3018. catch /^fugitive:/
  3019. return 'echoerr ' . string(v:exception)
  3020. endtry
  3021. endfunction
  3022. function! fugitive#BufWriteCmd(...) abort
  3023. return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
  3024. endfunction
  3025. function! fugitive#SourceCmd(...) abort
  3026. let amatch = a:0 ? a:1 : expand('<amatch>')
  3027. let temp = s:BlobTemp(amatch)
  3028. if empty(temp)
  3029. return 'noautocmd source ' . s:fnameescape(amatch)
  3030. endif
  3031. if !exists('g:virtual_scriptnames')
  3032. let g:virtual_scriptnames = {}
  3033. endif
  3034. let g:virtual_scriptnames[temp] = amatch
  3035. return 'source ' . s:fnameescape(temp)
  3036. endfunction
  3037. " Section: Temp files
  3038. if !exists('s:temp_files')
  3039. let s:temp_files = {}
  3040. endif
  3041. function! s:TempState(...) abort
  3042. return get(s:temp_files, s:cpath(s:AbsoluteVimPath(a:0 ? a:1 : -1)), {})
  3043. endfunction
  3044. function! fugitive#Result(...) abort
  3045. if !a:0 && exists('g:fugitive_event')
  3046. return get(g:, 'fugitive_result', {})
  3047. elseif !a:0 || type(a:1) == type('') && a:1 =~# '^-\=$'
  3048. return get(g:, '_fugitive_last_job', {})
  3049. elseif type(a:1) == type(0)
  3050. return s:TempState(a:1)
  3051. elseif type(a:1) == type('')
  3052. return s:TempState(a:1)
  3053. elseif type(a:1) == type({}) && has_key(a:1, 'file')
  3054. return s:TempState(a:1.file)
  3055. else
  3056. return {}
  3057. endif
  3058. endfunction
  3059. function! s:TempDotMap() abort
  3060. let cfile = s:cfile()
  3061. if empty(cfile)
  3062. if getline('.') =~# '^[*+] \+\f' && col('.') < 2
  3063. return matchstr(getline('.'), '^. \+\zs\f\+')
  3064. else
  3065. return expand('<cfile>')
  3066. endif
  3067. endif
  3068. let name = fugitive#Find(cfile[0])
  3069. let [dir, commit, file] = s:DirCommitFile(name)
  3070. if len(commit) && empty(file)
  3071. return commit
  3072. elseif s:cpath(s:Tree(), getcwd())
  3073. return fugitive#Path(name, "./")
  3074. else
  3075. return fugitive#Real(name)
  3076. endif
  3077. endfunction
  3078. function! s:TempReadPre(file) abort
  3079. let key = s:cpath(s:AbsoluteVimPath(a:file))
  3080. if has_key(s:temp_files, key)
  3081. let dict = s:temp_files[key]
  3082. setlocal nomodeline
  3083. if empty(&bufhidden)
  3084. setlocal bufhidden=delete
  3085. endif
  3086. setlocal buftype=nowrite
  3087. setlocal nomodifiable
  3088. call s:InitializeBuffer(dict)
  3089. if len(dict.git_dir)
  3090. call extend(b:, {'fugitive_type': 'temp'}, 'keep')
  3091. endif
  3092. endif
  3093. return ''
  3094. endfunction
  3095. function! s:TempReadPost(file) abort
  3096. let key = s:cpath(s:AbsoluteVimPath(a:file))
  3097. if has_key(s:temp_files, key)
  3098. let dict = s:temp_files[key]
  3099. if !has_key(dict, 'job')
  3100. setlocal nobuflisted
  3101. endif
  3102. if get(dict, 'filetype', '') ==# 'git'
  3103. call fugitive#MapJumps()
  3104. call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
  3105. call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
  3106. endif
  3107. if has_key(dict, 'filetype')
  3108. if dict.filetype ==# 'man' && has('nvim')
  3109. let b:man_sect = matchstr(getline(1), '^\w\+(\zs\d\+\ze)')
  3110. endif
  3111. if !get(g:, 'did_load_ftplugin') && dict.filetype ==# 'fugitiveblame'
  3112. call s:BlameMaps(0)
  3113. endif
  3114. let &l:filetype = dict.filetype
  3115. endif
  3116. setlocal foldmarker=<<<<<<<<,>>>>>>>>
  3117. if !&modifiable
  3118. call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
  3119. endif
  3120. return 'doautocmd <nomodeline> User FugitivePager'
  3121. endif
  3122. return ''
  3123. endfunction
  3124. function! s:TempDelete(file) abort
  3125. let key = s:cpath(s:AbsoluteVimPath(a:file))
  3126. if has_key(s:temp_files, key) && !has_key(s:temp_files[key], 'job') && key !=# s:cpath(get(get(g:, '_fugitive_last_job', {}), 'file', ''))
  3127. call delete(a:file)
  3128. call remove(s:temp_files, key)
  3129. endif
  3130. return ''
  3131. endfunction
  3132. function! s:OriginBufnr(...) abort
  3133. let state = s:TempState(a:0 ? a:1 : bufnr(''))
  3134. return get(state, 'origin_bufnr', -1)
  3135. endfunction
  3136. augroup fugitive_temp
  3137. autocmd!
  3138. autocmd BufReadPre * exe s:TempReadPre( +expand('<abuf>'))
  3139. autocmd BufReadPost * exe s:TempReadPost(+expand('<abuf>'))
  3140. autocmd BufWipeout * exe s:TempDelete( +expand('<abuf>'))
  3141. augroup END
  3142. " Section: :Git
  3143. function! s:AskPassArgs(dir) abort
  3144. if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) &&
  3145. \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#ConfigGetAll('core.askpass', a:dir))
  3146. if s:executable(s:VimExecPath() . '/git-gui--askpass')
  3147. return ['-c', 'core.askPass=' . s:ExecPath()[0] . '/git-gui--askpass']
  3148. elseif s:executable('ssh-askpass')
  3149. return ['-c', 'core.askPass=ssh-askpass']
  3150. endif
  3151. endif
  3152. return []
  3153. endfunction
  3154. function! s:RunSave(state) abort
  3155. let s:temp_files[s:cpath(a:state.file)] = a:state
  3156. endfunction
  3157. function! s:RunFinished(state, ...) abort
  3158. if has_key(get(g:, '_fugitive_last_job', {}), 'file') && bufnr(g:_fugitive_last_job.file) < 0
  3159. exe s:TempDelete(remove(g:, '_fugitive_last_job').file)
  3160. endif
  3161. let g:_fugitive_last_job = a:state
  3162. let first = join(readfile(a:state.file, '', 2), "\n")
  3163. if get(a:state, 'filetype', '') ==# 'git' && first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
  3164. let a:state.filetype = 'man'
  3165. endif
  3166. if !has_key(a:state, 'capture_bufnr')
  3167. return
  3168. endif
  3169. call fugitive#DidChange(a:state)
  3170. endfunction
  3171. function! s:RunEdit(state, tmp, job) abort
  3172. if get(a:state, 'request', '') !=# 'edit'
  3173. return 0
  3174. endif
  3175. call remove(a:state, 'request')
  3176. let sentinel = a:state.file . '.edit'
  3177. let file = FugitiveVimPath(readfile(sentinel, '', 1)[0])
  3178. try
  3179. if !&equalalways && a:state.mods !~# '\<\d*tab\>' && 3 > (a:state.mods =~# '\<vert' ? winwidth(0) : winheight(0))
  3180. let noequalalways = 1
  3181. setglobal equalalways
  3182. endif
  3183. let mods = s:Mods(a:state.mods, 'SpanOrigin')
  3184. exe substitute(mods, '\<tab\>', '-tab', 'g') 'keepalt split' s:fnameescape(file)
  3185. finally
  3186. if exists('l:noequalalways')
  3187. setglobal noequalalways
  3188. endif
  3189. endtry
  3190. set bufhidden=wipe
  3191. call s:InitializeBuffer(a:state)
  3192. let bufnr = bufnr('')
  3193. let s:edit_jobs[bufnr] = [a:state, a:tmp, a:job, sentinel]
  3194. call fugitive#DidChange(a:state.git_dir)
  3195. if bufnr == bufnr('') && !exists('g:fugitive_event')
  3196. try
  3197. let g:fugitive_event = a:state.git_dir
  3198. let g:fugitive_result = a:state
  3199. exe s:DoAutocmd('User FugitiveEditor')
  3200. finally
  3201. unlet! g:fugitive_event g:fugitive_result
  3202. endtry
  3203. endif
  3204. return 1
  3205. endfunction
  3206. function! s:RunReceive(state, tmp, type, job, data, ...) abort
  3207. if a:type ==# 'err' || a:state.pty
  3208. let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
  3209. let data = a:tmp.escape . data
  3210. let escape = "\033]51;[^\007]*"
  3211. let a:tmp.escape = matchstr(data, escape . '$')
  3212. if len(a:tmp.escape)
  3213. let data = strpart(data, 0, len(data) - len(a:tmp.escape))
  3214. endif
  3215. let cmd = matchstr(data, escape . "\007")[5:-2]
  3216. let data = substitute(data, escape . "\007", '', 'g')
  3217. if cmd =~# '^fugitive:'
  3218. let a:state.request = strpart(cmd, 9)
  3219. endif
  3220. let lines = split(a:tmp.err . data, "\r\\=\n", 1)
  3221. let a:tmp.err = lines[-1]
  3222. let lines[-1] = ''
  3223. call map(lines, 'substitute(v:val, ".*\r", "", "")')
  3224. else
  3225. let lines = type(a:data) == type([]) ? a:data : split(a:data, "\n", 1)
  3226. if len(a:tmp.out)
  3227. let lines[0] = a:tmp.out . lines[0]
  3228. endif
  3229. let a:tmp.out = lines[-1]
  3230. let lines[-1] = ''
  3231. endif
  3232. call writefile(lines, a:state.file, 'ba')
  3233. if has_key(a:tmp, 'echo')
  3234. if !exists('l:data')
  3235. let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
  3236. endif
  3237. let a:tmp.echo .= data
  3238. endif
  3239. let line_count = a:tmp.line_count
  3240. let a:tmp.line_count += len(lines) - 1
  3241. if !has_key(a:state, 'capture_bufnr') || !bufloaded(a:state.capture_bufnr)
  3242. return
  3243. endif
  3244. call remove(lines, -1)
  3245. try
  3246. call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
  3247. if !line_count && len(lines) > 1000
  3248. let first = remove(lines, 0, 999)
  3249. call setbufline(a:state.capture_bufnr, 1, first)
  3250. redraw
  3251. call setbufline(a:state.capture_bufnr, 1001, lines)
  3252. else
  3253. call setbufline(a:state.capture_bufnr, line_count + 1, lines)
  3254. endif
  3255. call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
  3256. if !a:state.pager && getwinvar(bufwinid(a:state.capture_bufnr), '&previewwindow')
  3257. let winnr = bufwinnr(a:state.capture_bufnr)
  3258. if winnr > 0
  3259. let old_winnr = winnr()
  3260. exe 'noautocmd' winnr.'wincmd w'
  3261. $
  3262. exe 'noautocmd' old_winnr.'wincmd w'
  3263. endif
  3264. endif
  3265. catch
  3266. endtry
  3267. endfunction
  3268. function! s:RunExit(state, tmp, job, exit_status) abort
  3269. let a:state.exit_status = a:exit_status
  3270. if has_key(a:state, 'job')
  3271. return
  3272. endif
  3273. call s:RunFinished(a:state)
  3274. endfunction
  3275. function! s:RunClose(state, tmp, job, ...) abort
  3276. if a:0
  3277. call s:RunExit(a:state, a:tmp, a:job, a:1)
  3278. endif
  3279. let noeol = substitute(substitute(a:tmp.err, "\r$", '', ''), ".*\r", '', '') . a:tmp.out
  3280. call writefile([noeol], a:state.file, 'ba')
  3281. call remove(a:state, 'job')
  3282. if has_key(a:state, 'capture_bufnr') && bufloaded(a:state.capture_bufnr)
  3283. if len(noeol)
  3284. call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
  3285. call setbufline(a:state.capture_bufnr, a:tmp.line_count + 1, [noeol])
  3286. call setbufvar(a:state.capture_bufnr, '&eol', 0)
  3287. call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
  3288. endif
  3289. call setbufvar(a:state.capture_bufnr, '&modified', 0)
  3290. call setbufvar(a:state.capture_bufnr, '&buflisted', 0)
  3291. if a:state.filetype !=# getbufvar(a:state.capture_bufnr, '&filetype', '')
  3292. call setbufvar(a:state.capture_bufnr, '&filetype', a:state.filetype)
  3293. endif
  3294. endif
  3295. if !has_key(a:state, 'exit_status')
  3296. return
  3297. endif
  3298. call s:RunFinished(a:state)
  3299. endfunction
  3300. function! s:RunSend(job, str) abort
  3301. try
  3302. if type(a:job) == type(0)
  3303. call chansend(a:job, a:str)
  3304. else
  3305. call ch_sendraw(a:job, a:str)
  3306. endif
  3307. return len(a:str)
  3308. catch /^Vim\%((\a\+)\)\=:E90[06]:/
  3309. return 0
  3310. endtry
  3311. endfunction
  3312. function! s:RunCloseIn(job) abort
  3313. try
  3314. if type(a:job) ==# type(0)
  3315. call chanclose(a:job, 'stdin')
  3316. else
  3317. call ch_close_in(a:job)
  3318. endif
  3319. return 1
  3320. catch /^Vim\%((\a\+)\)\=:E90[06]:/
  3321. return 0
  3322. endtry
  3323. endfunction
  3324. function! s:RunEcho(tmp) abort
  3325. if !has_key(a:tmp, 'echo')
  3326. return
  3327. endif
  3328. let data = a:tmp.echo
  3329. let a:tmp.echo = matchstr(data, "[\r\n]\\+$")
  3330. if len(a:tmp.echo)
  3331. let data = strpart(data, 0, len(data) - len(a:tmp.echo))
  3332. endif
  3333. echon substitute(data, "\r\\ze\n", '', 'g')
  3334. endfunction
  3335. function! s:RunTick(job) abort
  3336. if type(a:job) == v:t_number
  3337. return jobwait([a:job], 1)[0] == -1
  3338. elseif type(a:job) == 8
  3339. let running = ch_status(a:job) !~# '^closed$\|^fail$' || job_status(a:job) ==# 'run'
  3340. sleep 1m
  3341. return running
  3342. endif
  3343. endfunction
  3344. if !exists('s:edit_jobs')
  3345. let s:edit_jobs = {}
  3346. endif
  3347. function! s:RunWait(state, tmp, job, ...) abort
  3348. if a:0 && filereadable(a:1)
  3349. call delete(a:1)
  3350. endif
  3351. try
  3352. if a:tmp.no_more && &more
  3353. let more = &more
  3354. let &more = 0
  3355. endif
  3356. while get(a:state, 'request', '') !=# 'edit' && s:RunTick(a:job)
  3357. call s:RunEcho(a:tmp)
  3358. if !get(a:tmp, 'closed_in')
  3359. let peek = getchar(1)
  3360. if peek != 0 && !(has('win32') && peek == 128)
  3361. let c = getchar()
  3362. let c = type(c) == type(0) ? nr2char(c) : c
  3363. if c ==# "\<C-D>" || c ==# "\<Esc>"
  3364. let a:tmp.closed_in = 1
  3365. let can_pedit = s:RunCloseIn(a:job) && exists('*setbufline')
  3366. for winnr in range(1, winnr('$'))
  3367. if getwinvar(winnr, '&previewwindow') && getbufvar(winbufnr(winnr), '&modified')
  3368. let can_pedit = 0
  3369. endif
  3370. endfor
  3371. if can_pedit
  3372. if has_key(a:tmp, 'echo')
  3373. call remove(a:tmp, 'echo')
  3374. endif
  3375. call writefile(['fugitive: aborting edit due to background operation.'], a:state.file . '.exit')
  3376. exe (&splitbelow ? 'botright' : 'topleft') 'silent pedit ++ff=unix' s:fnameescape(a:state.file)
  3377. let a:state.capture_bufnr = bufnr(a:state.file)
  3378. call setbufvar(a:state.capture_bufnr, '&modified', 1)
  3379. let finished = 0
  3380. redraw!
  3381. return ''
  3382. endif
  3383. else
  3384. call s:RunSend(a:job, c)
  3385. if !a:state.pty
  3386. echon c
  3387. endif
  3388. endif
  3389. endif
  3390. endif
  3391. endwhile
  3392. if !has_key(a:state, 'request') && has_key(a:state, 'job') && exists('*job_status') && job_status(a:job) ==# "dead"
  3393. throw 'fugitive: close callback did not fire; this should never happen'
  3394. endif
  3395. call s:RunEcho(a:tmp)
  3396. if has_key(a:tmp, 'echo')
  3397. let a:tmp.echo = substitute(a:tmp.echo, "^\r\\=\n", '', '')
  3398. echo
  3399. endif
  3400. let finished = !s:RunEdit(a:state, a:tmp, a:job)
  3401. finally
  3402. if exists('l:more')
  3403. let &more = more
  3404. endif
  3405. if !exists('finished')
  3406. try
  3407. if a:state.pty && !get(a:tmp, 'closed_in')
  3408. call s:RunSend(a:job, "\<C-C>")
  3409. elseif type(a:job) == type(0)
  3410. call jobstop(a:job)
  3411. else
  3412. call job_stop(a:job)
  3413. endif
  3414. catch /.*/
  3415. endtry
  3416. elseif finished
  3417. call fugitive#DidChange(a:state)
  3418. endif
  3419. endtry
  3420. return ''
  3421. endfunction
  3422. if !exists('s:resume_queue')
  3423. let s:resume_queue = []
  3424. endif
  3425. function! fugitive#Resume() abort
  3426. while len(s:resume_queue)
  3427. let enqueued = remove(s:resume_queue, 0)
  3428. if enqueued[2] isnot# ''
  3429. try
  3430. call call('s:RunWait', enqueued)
  3431. endtry
  3432. endif
  3433. endwhile
  3434. endfunction
  3435. function! s:RunBufDelete(bufnr) abort
  3436. let state = s:TempState(+a:bufnr)
  3437. if has_key(state, 'job')
  3438. try
  3439. if type(state.job) == type(0)
  3440. call jobstop(state.job)
  3441. else
  3442. call job_stop(state.job)
  3443. endif
  3444. catch
  3445. endtry
  3446. endif
  3447. if has_key(s:edit_jobs, a:bufnr) |
  3448. call add(s:resume_queue, remove(s:edit_jobs, a:bufnr))
  3449. call feedkeys("\<C-\>\<C-N>:redraw!|call delete(" . string(s:resume_queue[-1][0].file . '.edit') .
  3450. \ ")|call fugitive#Resume()|checktime\r", 'n')
  3451. endif
  3452. endfunction
  3453. augroup fugitive_job
  3454. autocmd!
  3455. autocmd BufDelete * call s:RunBufDelete(+expand('<abuf>'))
  3456. autocmd VimLeave *
  3457. \ for s:jobbuf in keys(s:edit_jobs) |
  3458. \ call writefile(['Aborting edit due to Vim exit.'], s:edit_jobs[s:jobbuf][0].file . '.exit') |
  3459. \ redraw! |
  3460. \ call call('s:RunWait', remove(s:edit_jobs, s:jobbuf)) |
  3461. \ endfor
  3462. augroup END
  3463. function! fugitive#CanPty() abort
  3464. return get(g:, 'fugitive_pty_debug_override',
  3465. \ has('unix') && !has('win32unix') && (has('patch-8.0.0744') || has('nvim')) && fugitive#GitVersion() !~# '\.windows\>')
  3466. endfunction
  3467. function! fugitive#PagerFor(argv, ...) abort
  3468. let args = a:argv
  3469. if empty(args)
  3470. return 0
  3471. elseif (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
  3472. return 1
  3473. endif
  3474. if args[0] ==# 'config' && (s:HasOpt(args, '-e', '--edit') ||
  3475. \ !s:HasOpt(args, '--list', '--get-all', '--get-regexp', '--get-urlmatch')) ||
  3476. \ args[0] =~# '^\%(tag\|branch\)$' && (
  3477. \ s:HasOpt(args, '--edit-description', '--unset-upstream', '-m', '-M', '--move', '-c', '-C', '--copy', '-d', '-D', '--delete') ||
  3478. \ len(filter(args[1:-1], 'v:val =~# "^[^-]\\|^--set-upstream-to="')) &&
  3479. \ !s:HasOpt(args, '--contains', '--no-contains', '--merged', '--no-merged', '--points-at'))
  3480. return 0
  3481. endif
  3482. let config = a:0 ? a:1 : fugitive#Config()
  3483. let value = get(fugitive#ConfigGetAll('pager.' . args[0], config), 0, -1)
  3484. if value =~# '^\%(true\|yes\|on\|1\)$'
  3485. return 1
  3486. elseif value =~# '^\%(false\|no|off\|0\|\)$'
  3487. return 0
  3488. elseif type(value) == type('')
  3489. return value
  3490. elseif args[0] =~# '^\%(branch\|config\|diff\|grep\|log\|range-diff\|shortlog\|show\|tag\|whatchanged\)$' ||
  3491. \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
  3492. \ (args[0] ==# 'reflog' && get(args, 1, '') !~# '^\%(expire\|delete\|exists\)$') ||
  3493. \ (args[0] ==# 'am' && s:HasOpt(args, '--show-current-patch'))
  3494. return 1
  3495. else
  3496. return 0
  3497. endif
  3498. endfunction
  3499. let s:disable_colors = []
  3500. for s:colortype in ['advice', 'branch', 'diff', 'grep', 'interactive', 'pager', 'push', 'remote', 'showBranch', 'status', 'transport', 'ui']
  3501. call extend(s:disable_colors, ['-c', 'color.' . s:colortype . '=false'])
  3502. endfor
  3503. unlet s:colortype
  3504. function! fugitive#Command(line1, line2, range, bang, mods, arg, ...) abort
  3505. exe s:VersionCheck()
  3506. let dir = call('s:Dir', a:000)
  3507. if len(dir)
  3508. exe s:DirCheck(dir)
  3509. endif
  3510. let config = copy(fugitive#Config(dir))
  3511. let curwin = a:arg =~# '^++curwin\>' || !a:line2
  3512. let [args, after] = s:SplitExpandChain(substitute(a:arg, '^++curwin\>\s*', '', ''), s:Tree(dir))
  3513. let flags = []
  3514. let pager = -1
  3515. let explicit_pathspec_option = 0
  3516. let did_expand_alias = 0
  3517. while len(args)
  3518. if args[0] ==# '-c' && len(args) > 1
  3519. call extend(flags, remove(args, 0, 1))
  3520. elseif args[0] =~# '^-p$\|^--paginate$'
  3521. let pager = 2
  3522. call remove(args, 0)
  3523. elseif args[0] =~# '^-P$\|^--no-pager$'
  3524. let pager = 0
  3525. call remove(args, 0)
  3526. elseif args[0] =~# '^--\%([[:lower:]-]\+-pathspecs\)$'
  3527. let explicit_pathspec_option = 1
  3528. call add(flags, remove(args, 0))
  3529. elseif args[0] =~# '^\%(--no-optional-locks\)$'
  3530. call add(flags, remove(args, 0))
  3531. elseif args[0] =~# '^-C$\|^--\%(exec-path=\|git-dir=\|work-tree=\|bare$\)'
  3532. return 'echoerr ' . string('fugitive: ' . args[0] . ' is not supported')
  3533. elseif did_expand_alias
  3534. break
  3535. else
  3536. let alias = FugitiveConfigGet('alias.' . get(args, 0, ''), config)
  3537. if get(args, 1, '') !=# '--help' && alias !~# '^$\|^!\|[\"'']' && !filereadable(s:VimExecPath() . '/git-' . args[0])
  3538. \ && !(has('win32') && filereadable(s:VimExecPath() . '/git-' . args[0] . '.exe'))
  3539. call remove(args, 0)
  3540. call extend(args, split(alias, '\s\+'), 'keep')
  3541. let did_expand_alias = 1
  3542. else
  3543. break
  3544. endif
  3545. endif
  3546. endwhile
  3547. if !explicit_pathspec_option
  3548. call insert(flags, '--no-literal-pathspecs')
  3549. endif
  3550. let no_pager = pager is# 0
  3551. if no_pager
  3552. call add(flags, '--no-pager')
  3553. endif
  3554. let env = {}
  3555. let i = 0
  3556. while i < len(flags) - 1
  3557. if flags[i] ==# '-c'
  3558. let i += 1
  3559. let config_name = tolower(matchstr(flags[i], '^[^=]\+'))
  3560. if has_key(s:prepare_env, config_name) && flags[i] =~# '=.'
  3561. let env[s:prepare_env[config_name]] = matchstr(flags[i], '=\zs.*')
  3562. endif
  3563. if flags[i] =~# '='
  3564. let config[config_name] = [matchstr(flags[i], '=\zs.*')]
  3565. else
  3566. let config[config_name] = [1]
  3567. endif
  3568. endif
  3569. let i += 1
  3570. endwhile
  3571. let options = {'git': s:UserCommandList(), 'git_dir': s:GitDir(dir), 'flags': flags, 'curwin': curwin}
  3572. if empty(args) && pager is# -1
  3573. let cmd = s:StatusCommand(a:line1, a:line2, a:range, curwin ? 0 : a:line2, a:bang, a:mods, '', '', [], options)
  3574. return (empty(cmd) ? 'exe' : cmd) . after
  3575. endif
  3576. let name = substitute(get(args, 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
  3577. if pager is# -1 && name =~# '^\a\+$' && exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
  3578. try
  3579. 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))
  3580. if type(overrides) == type('')
  3581. return 'exe ' . string(overrides) . after
  3582. endif
  3583. let args = [get(overrides, 'command', args[0])] + get(overrides, 'insert_args', []) + args[1:-1]
  3584. catch /^fugitive:/
  3585. return 'echoerr ' . string(v:exception)
  3586. endtry
  3587. else
  3588. let overrides = {}
  3589. endif
  3590. call extend(env, get(overrides, 'env', {}))
  3591. call s:PrepareEnv(env, dir)
  3592. if pager is# -1
  3593. let pager = fugitive#PagerFor(args, config)
  3594. endif
  3595. let wants_terminal = type(pager) ==# type('') ||
  3596. \ (s:HasOpt(args, ['add', 'checkout', 'commit', 'reset', 'restore', 'stage', 'stash'], '-p', '--patch') ||
  3597. \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive')) && pager is# 0
  3598. if wants_terminal
  3599. let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
  3600. let assign = len(dir) ? "|call FugitiveDetect({'git_dir':" . string(options.git_dir) . '})' : ''
  3601. let argv = s:UserCommandList(options) + args
  3602. let term_opts = len(env) ? {'env': env} : {}
  3603. if has('nvim')
  3604. call fugitive#Autowrite()
  3605. return mods . (curwin ? 'enew' : 'new') . '|call termopen(' . string(argv) . ', ' . string(term_opts) . ')' . assign . '|startinsert' . after
  3606. elseif exists('*term_start')
  3607. call fugitive#Autowrite()
  3608. if curwin
  3609. let term_opts.curwin = 1
  3610. endif
  3611. return mods . 'call term_start(' . string(argv) . ', ' . string(term_opts) . ')' . assign . after
  3612. endif
  3613. endif
  3614. let state = {
  3615. \ 'git': options.git,
  3616. \ 'flags': flags,
  3617. \ 'args': args,
  3618. \ 'git_dir': options.git_dir,
  3619. \ 'cwd': s:UserCommandCwd(dir),
  3620. \ 'filetype': 'git',
  3621. \ 'mods': s:Mods(a:mods),
  3622. \ 'file': s:Resolve(tempname())}
  3623. let allow_pty = 1
  3624. let after_edit = ''
  3625. let stream = 0
  3626. if a:bang && pager isnot# 2
  3627. let state.pager = pager
  3628. let pager = 1
  3629. let stream = exists('*setbufline')
  3630. let do_edit = substitute(s:Mods(a:mods, 'Edge'), '\<tab\>', '-tab', 'g') . 'pedit!'
  3631. elseif pager
  3632. let allow_pty = get(args, 0, '') is# 'shortlog'
  3633. if pager is# 2 && a:bang && a:line2 >= 0
  3634. let [do_edit, after_edit] = s:ReadPrepare(a:line1, a:line2, a:range, a:mods)
  3635. elseif pager is# 2 && a:bang
  3636. let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'pedit'
  3637. elseif !curwin
  3638. let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'split'
  3639. else
  3640. let do_edit = s:Mods(a:mods) . 'edit'
  3641. call s:BlurStatus()
  3642. endif
  3643. call extend(env, {'COLUMNS': '' . get(g:, 'fugitive_columns', 80)}, 'keep')
  3644. endif
  3645. if s:run_jobs
  3646. call extend(env, {'COLUMNS': '' . (&columns - 1)}, 'keep')
  3647. let state.pty = allow_pty && fugitive#CanPty()
  3648. if !state.pty
  3649. let args = s:AskPassArgs(dir) + args
  3650. endif
  3651. let tmp = {
  3652. \ 'no_more': no_pager || get(overrides, 'no_more'),
  3653. \ 'line_count': 0,
  3654. \ 'err': '',
  3655. \ 'out': '',
  3656. \ 'escape': ''}
  3657. let env.FUGITIVE = state.file
  3658. let editor = 'sh ' . s:TempScript(
  3659. \ '[ -f "$FUGITIVE.exit" ] && cat "$FUGITIVE.exit" >&2 && exit 1',
  3660. \ 'echo "$1" > "$FUGITIVE.edit"',
  3661. \ 'printf "\033]51;fugitive:edit\007" >&2',
  3662. \ 'while [ -f "$FUGITIVE.edit" -a ! -f "$FUGITIVE.exit" ]; do sleep 0.05 2>/dev/null || sleep 1; done',
  3663. \ 'exit 0')
  3664. call extend(env, {
  3665. \ 'NO_COLOR': '1',
  3666. \ 'GIT_EDITOR': editor,
  3667. \ 'GIT_SEQUENCE_EDITOR': editor,
  3668. \ 'GIT_PAGER': 'cat',
  3669. \ 'PAGER': 'cat'}, 'keep')
  3670. if s:executable('col')
  3671. let env.MANPAGER = 'col -b'
  3672. endif
  3673. if len($GPG_TTY) && !has_key(env, 'GPG_TTY')
  3674. let env.GPG_TTY = ''
  3675. let did_override_gpg_tty = 1
  3676. endif
  3677. if stream
  3678. call writefile(['fugitive: aborting edit due to background operation.'], state.file . '.exit')
  3679. elseif pager
  3680. call writefile(['fugitive: aborting edit due to use of pager.'], state.file . '.exit')
  3681. let after = '|' . do_edit . ' ' . s:fnameescape(state.file) . after_edit . after
  3682. else
  3683. let env.GIT_MERGE_AUTOEDIT = '1'
  3684. let tmp.echo = ''
  3685. endif
  3686. let args = s:disable_colors + flags + ['-c', 'advice.waitingForEditor=false'] + args
  3687. let argv = s:UserCommandList({'git': options.git, 'git_dir': options.git_dir}) + args
  3688. let [argv, jobopts] = s:JobOpts(argv, env)
  3689. call fugitive#Autowrite()
  3690. call writefile([], state.file, 'b')
  3691. call s:RunSave(state)
  3692. if has_key(tmp, 'echo')
  3693. echo ""
  3694. endif
  3695. if exists('*ch_close_in')
  3696. call extend(jobopts, {
  3697. \ 'mode': 'raw',
  3698. \ 'out_cb': function('s:RunReceive', [state, tmp, 'out']),
  3699. \ 'err_cb': function('s:RunReceive', [state, tmp, 'err']),
  3700. \ 'close_cb': function('s:RunClose', [state, tmp]),
  3701. \ 'exit_cb': function('s:RunExit', [state, tmp]),
  3702. \ })
  3703. if state.pty
  3704. let jobopts.pty = 1
  3705. endif
  3706. let job = job_start(argv, jobopts)
  3707. else
  3708. let job = jobstart(argv, extend(jobopts, {
  3709. \ 'pty': state.pty,
  3710. \ 'TERM': 'dumb',
  3711. \ 'stdout_buffered': pager,
  3712. \ 'stderr_buffered': pager,
  3713. \ 'on_stdout': function('s:RunReceive', [state, tmp, 'out']),
  3714. \ 'on_stderr': function('s:RunReceive', [state, tmp, 'err']),
  3715. \ 'on_exit': function('s:RunClose', [state, tmp]),
  3716. \ }))
  3717. endif
  3718. let state.job = job
  3719. if pager
  3720. let tmp.closed_in = 1
  3721. call s:RunCloseIn(job)
  3722. endif
  3723. if stream
  3724. exe 'silent' do_edit '++ff=unix' s:fnameescape(state.file)
  3725. let state.capture_bufnr = bufnr(state.file)
  3726. call setbufvar(state.capture_bufnr, '&modified', 1)
  3727. return (after_edit . after)[1:-1]
  3728. endif
  3729. call add(s:resume_queue, [state, tmp, job])
  3730. return 'call fugitive#Resume()|checktime' . after
  3731. elseif pager
  3732. let pre = s:BuildEnvPrefix(env)
  3733. try
  3734. if exists('+guioptions') && &guioptions =~# '!'
  3735. let guioptions = &guioptions
  3736. set guioptions-=!
  3737. endif
  3738. silent! execute '!' . escape(pre . s:shellesc(s:UserCommandList(options) + s:disable_colors + flags + ['--no-pager'] + args), '!#%') .
  3739. \ (&shell =~# 'csh' ? ' >& ' . s:shellesc(state.file) : ' > ' . s:shellesc(state.file) . ' 2>&1')
  3740. let state.exit_status = v:shell_error
  3741. finally
  3742. if exists('guioptions')
  3743. let &guioptions = guioptions
  3744. endif
  3745. endtry
  3746. redraw!
  3747. call s:RunSave(state)
  3748. call s:RunFinished(state)
  3749. return do_edit . ' ' . s:fnameescape(state.file) . after_edit .
  3750. \ '|call fugitive#DidChange(fugitive#Result(' . string(state.file) . '))' . after
  3751. elseif has('win32')
  3752. return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git on Windows')
  3753. elseif has('gui_running')
  3754. return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git in GVim')
  3755. else
  3756. if !explicit_pathspec_option && get(options.flags, 0, '') ==# '--no-literal-pathspecs'
  3757. call remove(options.flags, 0)
  3758. endif
  3759. if exists('l:did_override_gpg_tty')
  3760. call remove(env, 'GPG_TTY')
  3761. endif
  3762. let cmd = s:BuildEnvPrefix(env) . s:shellesc(s:UserCommandList(options) + args)
  3763. let after = '|call fugitive#DidChange(' . string(dir) . ')' . after
  3764. if !wants_terminal && (no_pager || index(['add', 'clean', 'reset', 'restore', 'stage'], get(args, 0, '')) >= 0 || s:HasOpt(args, ['checkout'], '-q', '--quiet', '--no-progress'))
  3765. let output = substitute(s:SystemError(cmd)[0], "\n$", '', '')
  3766. if len(output)
  3767. try
  3768. if &more && no_pager
  3769. let more = 1
  3770. set nomore
  3771. endif
  3772. echo substitute(output, "\n$", "", "")
  3773. finally
  3774. if exists('l:more')
  3775. set more
  3776. endif
  3777. endtry
  3778. endif
  3779. return 'checktime' . after
  3780. else
  3781. return 'exe ' . string('noautocmd !' . escape(cmd, '!#%')) . after
  3782. endif
  3783. endif
  3784. endfunction
  3785. let s:exec_paths = {}
  3786. function! s:ExecPath() abort
  3787. let git = s:GitShellCmd()
  3788. if !has_key(s:exec_paths, git)
  3789. let path = get(s:JobExecute(s:GitCmd() + ['--exec-path'], {}, [], [], {}).stdout, 0, '')
  3790. let s:exec_paths[git] = [path, FugitiveVimPath(path)]
  3791. endif
  3792. return s:exec_paths[git]
  3793. endfunction
  3794. function! s:VimExecPath() abort
  3795. return s:ExecPath()[1]
  3796. endfunction
  3797. let s:subcommands_before_2_5 = [
  3798. \ 'add', 'am', 'apply', 'archive', 'bisect', 'blame', 'branch', 'bundle',
  3799. \ 'checkout', 'cherry', 'cherry-pick', 'citool', 'clean', 'clone', 'commit', 'config',
  3800. \ 'describe', 'diff', 'difftool', 'fetch', 'format-patch', 'fsck',
  3801. \ 'gc', 'grep', 'gui', 'help', 'init', 'instaweb', 'log',
  3802. \ 'merge', 'mergetool', 'mv', 'notes', 'pull', 'push',
  3803. \ 'rebase', 'reflog', 'remote', 'repack', 'replace', 'request-pull', 'reset', 'revert', 'rm',
  3804. \ 'send-email', 'shortlog', 'show', 'show-branch', 'stash', 'stage', 'status', 'submodule',
  3805. \ 'tag', 'whatchanged',
  3806. \ ]
  3807. let s:path_subcommands = {}
  3808. function! s:CompletableSubcommands(dir) abort
  3809. let c_exec_path = s:cpath(s:VimExecPath())
  3810. if !has_key(s:path_subcommands, c_exec_path)
  3811. if fugitive#GitVersion(2, 18)
  3812. let [lines, exec_error] = s:LinesError([a:dir, '--list-cmds=list-mainporcelain,nohelpers,list-complete'])
  3813. call filter(lines, 'v:val =~# "^\\S\\+$"')
  3814. if !exec_error && len(lines)
  3815. let s:path_subcommands[c_exec_path] = lines
  3816. else
  3817. let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
  3818. \ ['maintenance', 'prune', 'range-diff', 'restore', 'sparse-checkout', 'switch', 'worktree']
  3819. endif
  3820. else
  3821. let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
  3822. \ (fugitive#GitVersion(2, 5) ? ['worktree'] : [])
  3823. endif
  3824. endif
  3825. let commands = copy(s:path_subcommands[c_exec_path])
  3826. for path in split($PATH, has('win32') ? ';' : ':')
  3827. if path !~# '^/\|^\a:[\\/]'
  3828. continue
  3829. endif
  3830. let cpath = s:cpath(path)
  3831. if !has_key(s:path_subcommands, cpath)
  3832. let s:path_subcommands[cpath] = filter(map(s:GlobComplete(path.'/git-', '*', 1),'substitute(v:val,"\\.exe$","","")'), 'v:val !~# "--\\|/"')
  3833. endif
  3834. call extend(commands, s:path_subcommands[cpath])
  3835. endfor
  3836. call extend(commands, keys(fugitive#ConfigGetRegexp('^alias\.\zs[^.]\+$', a:dir)))
  3837. let configured = split(FugitiveConfigGet('completion.commands', a:dir), '\s\+')
  3838. let rejected = {}
  3839. for command in configured
  3840. if command =~# '^-.'
  3841. let rejected[strpart(command, 1)] = 1
  3842. endif
  3843. endfor
  3844. call filter(configured, 'v:val !~# "^-"')
  3845. let results = filter(sort(commands + configured), '!has_key(rejected, v:val)')
  3846. if exists('*uniq')
  3847. return uniq(results)
  3848. else
  3849. let i = 1
  3850. while i < len(results)
  3851. if results[i] ==# results[i-1]
  3852. call remove(results, i)
  3853. else
  3854. let i += 1
  3855. endif
  3856. endwhile
  3857. return results
  3858. endif
  3859. endfunction
  3860. function! fugitive#Complete(lead, ...) abort
  3861. let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? s:Dir(a:3) : s:Dir()
  3862. let root = a:0 >= 4 ? a:4 : s:Tree(s:Dir())
  3863. let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
  3864. let subcmd = matchstr(pre, '\u\w*[! ] *\%(\%(++\S\+\|--\S\+-pathspecs\|-c\s\+\S\+\)\s\+\)*\zs[[:alnum:]][[:alnum:]-]*\ze ')
  3865. if empty(subcmd) && a:lead =~# '^+'
  3866. let results = ['++curwin']
  3867. elseif empty(subcmd) && a:lead =~# '^-'
  3868. let results = ['--literal-pathspecs', '--no-literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs', '--no-optional-locks']
  3869. elseif empty(subcmd)
  3870. let results = s:CompletableSubcommands(dir)
  3871. elseif a:0 ==# 2 && subcmd =~# '^\%(commit\|revert\|push\|fetch\|pull\|merge\|rebase\|bisect\)$'
  3872. let cmdline = substitute(a:1, '\u\w*\([! ] *\)' . subcmd, 'G' . subcmd, '')
  3873. let caps_subcmd = substitute(subcmd, '\%(^\|-\)\l', '\u&', 'g')
  3874. return fugitive#{caps_subcmd}Complete(a:lead, cmdline, a:2 + len(cmdline) - len(a:1), dir, root)
  3875. elseif pre =~# ' -- '
  3876. return fugitive#CompletePath(a:lead, a:1, a:2, dir, root)
  3877. elseif a:lead =~# '^-'
  3878. let results = split(s:ChompDefault('', [dir, subcmd, '--git-completion-helper']), ' ')
  3879. else
  3880. return fugitive#CompleteObject(a:lead, a:1, a:2, dir, root)
  3881. endif
  3882. return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
  3883. endfunction
  3884. function! fugitive#CompleteForWorkingDir(A, L, P, ...) abort
  3885. let path = a:0 ? a:1 : getcwd()
  3886. return fugitive#Complete(a:A, a:L, a:P, FugitiveExtractGitDir(path), path)
  3887. endfunction
  3888. " Section: :Gcd, :Glcd
  3889. function! fugitive#CdComplete(A, L, P) abort
  3890. return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
  3891. endfunction
  3892. function! fugitive#Cd(path, ...) abort
  3893. exe s:VersionCheck()
  3894. let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
  3895. if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
  3896. let dir = s:Dir()
  3897. exe s:DirCheck(dir)
  3898. let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
  3899. endif
  3900. return (a:0 && a:1 ? 'lcd ' : 'cd ') . fnameescape(s:VimSlash(path))
  3901. endfunction
  3902. " Section: :Gstatus
  3903. function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
  3904. let dir = a:0 ? s:Dir(a:1) : s:Dir()
  3905. exe s:DirCheck(dir)
  3906. try
  3907. let mods = s:Mods(a:mods, 'Edge')
  3908. let file = fugitive#Find(':', dir)
  3909. let arg = ' +setl\ foldmarker=<<<<<<<<,>>>>>>>>\|let\ w:fugitive_status=FugitiveGitDir() ' .
  3910. \ s:fnameescape(file)
  3911. for tabnr in [tabpagenr()] + (mods =~# '\<tab\>' ? range(1, tabpagenr('$')) : [])
  3912. let bufs = tabpagebuflist(tabnr)
  3913. for winnr in range(1, tabpagewinnr(tabnr, '$'))
  3914. if s:cpath(file, fnamemodify(bufname(bufs[winnr-1]), ':p'))
  3915. if tabnr == tabpagenr() && winnr == winnr()
  3916. call s:ReloadStatus()
  3917. else
  3918. call s:ExpireStatus(dir)
  3919. exe tabnr . 'tabnext'
  3920. exe winnr . 'wincmd w'
  3921. endif
  3922. let w:fugitive_status = dir
  3923. 1
  3924. return ''
  3925. endif
  3926. endfor
  3927. endfor
  3928. if a:count ==# 0
  3929. return mods . 'edit' . (a:bang ? '!' : '') . arg
  3930. elseif a:bang
  3931. return mods . 'pedit' . arg . '|wincmd P'
  3932. else
  3933. return mods . 'keepalt split' . arg
  3934. endif
  3935. catch /^fugitive:/
  3936. return 'echoerr ' . string(v:exception)
  3937. endtry
  3938. return ''
  3939. endfunction
  3940. function! s:StageJump(offset, section, ...) abort
  3941. let line = search('^\%(' . a:section . '\)', 'nw')
  3942. if !line && a:0
  3943. let line = search('^\%(' . a:1 . '\)', 'nw')
  3944. endif
  3945. if line
  3946. exe line
  3947. if a:offset
  3948. for i in range(a:offset)
  3949. call search(s:file_commit_pattern . '\|^$', 'W')
  3950. if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
  3951. call search(s:file_commit_pattern . '\|^$', 'W')
  3952. endif
  3953. if empty(getline('.'))
  3954. return ''
  3955. endif
  3956. endfor
  3957. call s:StageReveal()
  3958. else
  3959. call s:StageReveal()
  3960. +
  3961. endif
  3962. endif
  3963. return ''
  3964. endfunction
  3965. function! s:StageSeek(info, fallback) abort
  3966. let info = a:info
  3967. if empty(info.heading)
  3968. return a:fallback
  3969. endif
  3970. let line = search('^' . escape(info.heading, '^$.*[]~\') . ' (\d\++\=)$', 'wn')
  3971. if !line
  3972. for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
  3973. let line = search('^' . section, 'wn')
  3974. if line
  3975. return line + (info.index > 0 ? 1 : 0)
  3976. endif
  3977. endfor
  3978. return 1
  3979. endif
  3980. let i = 0
  3981. while len(getline(line))
  3982. let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
  3983. if len(filename) &&
  3984. \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
  3985. \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
  3986. \ filename ==# info.filename)
  3987. if info.offset < 0
  3988. return line
  3989. else
  3990. if getline(line+1) !~# '^@'
  3991. exe s:StageInline('show', line)
  3992. endif
  3993. if getline(line+1) !~# '^@'
  3994. return line
  3995. endif
  3996. let type = info.sigil ==# '-' ? '-' : '+'
  3997. let offset = -1
  3998. while offset < info.offset
  3999. let line += 1
  4000. if getline(line) =~# '^@'
  4001. let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
  4002. elseif getline(line) =~# '^[ ' . type . ']'
  4003. let offset += 1
  4004. elseif getline(line) !~# '^[ @\+-]'
  4005. return line - 1
  4006. endif
  4007. endwhile
  4008. return line
  4009. endif
  4010. endif
  4011. let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
  4012. if len(commit) && commit ==# info.commit
  4013. return line
  4014. endif
  4015. if i ==# info.index
  4016. let backup = line
  4017. endif
  4018. let i += getline(line) !~# '^[ @\+-]'
  4019. let line += 1
  4020. endwhile
  4021. return exists('backup') ? backup : line - 1
  4022. endfunction
  4023. function! s:DoAutocmdChanged(dir) abort
  4024. let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
  4025. if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
  4026. return ''
  4027. endif
  4028. try
  4029. let g:fugitive_event = dir
  4030. if type(a:dir) == type({}) && has_key(a:dir, 'args') && has_key(a:dir, 'exit_status')
  4031. let g:fugitive_result = a:dir
  4032. endif
  4033. exe s:DoAutocmd('User FugitiveChanged')
  4034. finally
  4035. unlet! g:fugitive_event g:fugitive_result
  4036. " Force statusline reload with the buffer's Git dir
  4037. if dir isnot# FugitiveGitDir()
  4038. let &l:ro = &l:ro
  4039. endif
  4040. endtry
  4041. return ''
  4042. endfunction
  4043. function! s:ReloadStatusBuffer() abort
  4044. if get(b:, 'fugitive_type', '') !=# 'index' || !empty(get(b:, 'fugitive_loading'))
  4045. return ''
  4046. endif
  4047. let original_lnum = line('.')
  4048. let info = s:StageInfo(original_lnum)
  4049. exe fugitive#BufReadStatus(0)
  4050. call setpos('.', [0, s:StageSeek(info, original_lnum), 1, 0])
  4051. return ''
  4052. endfunction
  4053. function! s:ReloadStatus() abort
  4054. call s:ExpireStatus(-1)
  4055. call s:ReloadStatusBuffer()
  4056. exe s:DoAutocmdChanged(-1)
  4057. return ''
  4058. endfunction
  4059. let s:last_time = reltime()
  4060. if !exists('s:last_times')
  4061. let s:last_times = {}
  4062. endif
  4063. function! s:ExpireStatus(bufnr) abort
  4064. if a:bufnr is# -2 || a:bufnr is# 0
  4065. let s:head_cache = {}
  4066. let s:last_time = reltime()
  4067. return ''
  4068. endif
  4069. let head_file = fugitive#Find('.git/HEAD', a:bufnr)
  4070. if !empty(head_file)
  4071. let s:last_times[s:Tree(a:bufnr) . '/'] = reltime()
  4072. if has_key(s:head_cache, head_file)
  4073. call remove(s:head_cache, head_file)
  4074. endif
  4075. endif
  4076. return ''
  4077. endfunction
  4078. function! s:ReloadWinStatus(...) abort
  4079. if get(b:, 'fugitive_type', '') !=# 'index' || !empty(get(b:, 'fugitive_loading')) || &modified
  4080. return
  4081. endif
  4082. if !exists('b:fugitive_status.reltime')
  4083. exe call('s:ReloadStatusBuffer', a:000)
  4084. return
  4085. endif
  4086. let t = b:fugitive_status.reltime
  4087. if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
  4088. \ reltimestr(reltime(get(s:last_times, s:Tree() . '/', t), t)) =~# '-\|\d\{10\}\.'
  4089. exe call('s:ReloadStatusBuffer', a:000)
  4090. endif
  4091. endfunction
  4092. function! s:ReloadTabStatus() abort
  4093. if !exists('g:fugitive_did_change_at')
  4094. return
  4095. elseif exists('t:fugitive_reloaded_at')
  4096. let time_ahead = reltime(g:fugitive_did_change_at, t:fugitive_reloaded_at)
  4097. if reltimefloat(time_ahead) >= 0
  4098. return
  4099. endif
  4100. endif
  4101. let t:fugitive_reloaded_at = reltime()
  4102. let winnr = 1
  4103. while winnr <= winnr('$')
  4104. if getbufvar(winbufnr(winnr), 'fugitive_type') ==# 'index'
  4105. if winnr != winnr()
  4106. execute 'noautocmd' winnr.'wincmd w'
  4107. let restorewinnr = 1
  4108. endif
  4109. try
  4110. call s:ReloadWinStatus()
  4111. finally
  4112. if exists('restorewinnr')
  4113. unlet restorewinnr
  4114. noautocmd wincmd p
  4115. endif
  4116. endtry
  4117. endif
  4118. let winnr += 1
  4119. endwhile
  4120. endfunction
  4121. function! fugitive#DidChange(...) abort
  4122. call s:ExpireStatus(a:0 ? a:1 : -1)
  4123. if a:0 > 1 ? a:2 : (!a:0 || a:1 isnot# 0)
  4124. let g:fugitive_did_change_at = reltime()
  4125. call s:ReloadTabStatus()
  4126. else
  4127. call s:ReloadWinStatus()
  4128. return ''
  4129. endif
  4130. exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
  4131. return ''
  4132. endfunction
  4133. function! fugitive#ReloadStatus(...) abort
  4134. return call('fugitive#DidChange', a:000)
  4135. endfunction
  4136. function! fugitive#EfmDir(...) abort
  4137. let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
  4138. let dir = substitute(dir, '%%', '%', 'g')
  4139. let dir = substitute(dir, '\\\ze[\,]', '', 'g')
  4140. return dir
  4141. endfunction
  4142. augroup fugitive_status
  4143. autocmd!
  4144. autocmd BufWritePost * call fugitive#DidChange(+expand('<abuf>'), 0)
  4145. autocmd User FileChmodPost,FileUnlinkPost call fugitive#DidChange(+expand('<abuf>'), 0)
  4146. autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#DidChange(0)
  4147. autocmd BufDelete * nested
  4148. \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
  4149. \ if !empty(FugitiveGitDir(+expand('<abuf>'))) |
  4150. \ call fugitive#DidChange(+expand('<abuf>')) |
  4151. \ else |
  4152. \ call fugitive#DidChange(0) |
  4153. \ endif |
  4154. \ endif
  4155. autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
  4156. \ call fugitive#DidChange(fugitive#EfmDir())
  4157. autocmd FocusGained *
  4158. \ if get(g:, 'fugitive_focus_gained', !has('win32')) |
  4159. \ call fugitive#DidChange(0) |
  4160. \ endif
  4161. autocmd BufEnter index,index.lock,fugitive://*//
  4162. \ call s:ReloadWinStatus()
  4163. autocmd TabEnter *
  4164. \ call s:ReloadTabStatus()
  4165. augroup END
  4166. function! s:StatusSectionFile(heading, filename) abort
  4167. return get(get(get(get(b:, 'fugitive_status', {}), 'files', {}), a:heading, {}), a:filename, {})
  4168. endfunction
  4169. function! s:StageInfo(...) abort
  4170. let lnum = a:0 ? a:1 : line('.')
  4171. let sigil = matchstr(getline(lnum), '^[ @\+-]')
  4172. let offset = -1
  4173. if len(sigil)
  4174. let [lnum, old_lnum, new_lnum] = s:HunkPosition(lnum)
  4175. let offset = sigil ==# '-' ? old_lnum : new_lnum
  4176. while getline(lnum) =~# '^[ @\+-]'
  4177. let lnum -= 1
  4178. endwhile
  4179. endif
  4180. let slnum = lnum + 1
  4181. let heading = ''
  4182. let index = 0
  4183. while len(getline(slnum - 1)) && empty(heading)
  4184. let slnum -= 1
  4185. let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
  4186. if empty(heading) && getline(slnum) !~# '^[ @\+-]'
  4187. let index += 1
  4188. endif
  4189. endwhile
  4190. let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
  4191. let file = s:StatusSectionFile(heading, text)
  4192. let relative = get(file, 'relative', len(text) ? [text] : [])
  4193. return {'section': matchstr(heading, '^\u\l\+'),
  4194. \ 'heading': heading,
  4195. \ 'sigil': sigil,
  4196. \ 'offset': offset,
  4197. \ 'filename': text,
  4198. \ 'relative': copy(relative),
  4199. \ 'paths': map(copy(relative), 's:Tree() . "/" . v:val'),
  4200. \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
  4201. \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
  4202. \ 'submodule': get(file, 'submodule', ''),
  4203. \ 'index': index}
  4204. endfunction
  4205. function! s:Selection(arg1, ...) abort
  4206. if a:arg1 ==# 'n'
  4207. let arg1 = line('.')
  4208. let arg2 = -v:count
  4209. elseif a:arg1 ==# 'v'
  4210. let arg1 = line("'<")
  4211. let arg2 = line("'>")
  4212. else
  4213. let arg1 = a:arg1
  4214. let arg2 = a:0 ? a:1 : 0
  4215. endif
  4216. let first = arg1
  4217. if arg2 < 0
  4218. let last = first - arg2 - 1
  4219. elseif arg2 > 0
  4220. let last = arg2
  4221. else
  4222. let last = first
  4223. endif
  4224. while first <= line('$') && getline(first) =~# '^$\|^[A-Z][a-z]'
  4225. let first += 1
  4226. endwhile
  4227. if first > last || &filetype !=# 'fugitive'
  4228. return []
  4229. endif
  4230. let flnum = first
  4231. while getline(flnum) =~# '^[ @\+-]'
  4232. let flnum -= 1
  4233. endwhile
  4234. let slnum = flnum + 1
  4235. let heading = ''
  4236. let index = 0
  4237. while empty(heading)
  4238. let slnum -= 1
  4239. let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
  4240. if empty(heading) && getline(slnum) !~# '^[ @\+-]'
  4241. let index += 1
  4242. endif
  4243. endwhile
  4244. let results = []
  4245. let template = {
  4246. \ 'heading': heading,
  4247. \ 'section': matchstr(heading, '^\u\l\+'),
  4248. \ 'filename': '',
  4249. \ 'relative': [],
  4250. \ 'paths': [],
  4251. \ 'commit': '',
  4252. \ 'status': '',
  4253. \ 'patch': 0,
  4254. \ 'index': index}
  4255. let line = getline(flnum)
  4256. let lnum = first - (arg1 == flnum ? 0 : 1)
  4257. let root = s:Tree() . '/'
  4258. while lnum <= last
  4259. let heading = matchstr(line, '^\u\l\+\ze.\{-\}\ze (\d\++\=)$')
  4260. if len(heading)
  4261. let template.heading = heading
  4262. let template.section = matchstr(heading, '^\u\l\+')
  4263. let template.index = 0
  4264. elseif line =~# '^[ @\+-]'
  4265. let template.index -= 1
  4266. if !results[-1].patch
  4267. let results[-1].patch = lnum
  4268. endif
  4269. let results[-1].lnum = lnum
  4270. elseif line =~# '^[A-Z?] '
  4271. let text = matchstr(line, '^[A-Z?] \zs.*')
  4272. let file = s:StatusSectionFile(template.heading, text)
  4273. let relative = get(file, 'relative', len(text) ? [text] : [])
  4274. call add(results, extend(deepcopy(template), {
  4275. \ 'lnum': lnum,
  4276. \ 'filename': text,
  4277. \ 'relative': copy(relative),
  4278. \ 'paths': map(copy(relative), 'root . v:val'),
  4279. \ 'status': matchstr(line, '^[A-Z?]'),
  4280. \ }))
  4281. elseif line =~# '^\x\x\x\+ '
  4282. call add(results, extend({
  4283. \ 'lnum': lnum,
  4284. \ 'commit': matchstr(line, '^\x\x\x\+'),
  4285. \ }, template, 'keep'))
  4286. elseif line =~# '^\l\+ \x\x\x\+ '
  4287. call add(results, extend({
  4288. \ 'lnum': lnum,
  4289. \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
  4290. \ 'status': matchstr(line, '^\l\+'),
  4291. \ }, template, 'keep'))
  4292. endif
  4293. let lnum += 1
  4294. let template.index += 1
  4295. let line = getline(lnum)
  4296. endwhile
  4297. if len(results) && results[0].patch && arg2 == 0
  4298. while getline(results[0].patch) =~# '^[ \+-]'
  4299. let results[0].patch -= 1
  4300. endwhile
  4301. while getline(results[0].lnum + 1) =~# '^[ \+-]'
  4302. let results[0].lnum += 1
  4303. endwhile
  4304. endif
  4305. return results
  4306. endfunction
  4307. function! s:StageArgs(visual) abort
  4308. let commits = []
  4309. let paths = []
  4310. for record in s:Selection(a:visual ? 'v' : 'n')
  4311. if len(record.commit)
  4312. call add(commits, record.commit)
  4313. endif
  4314. call extend(paths, record.paths)
  4315. endfor
  4316. if s:cpath(s:Tree(), getcwd())
  4317. call map(paths, 'fugitive#Path(v:val, "./")')
  4318. endif
  4319. return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
  4320. endfunction
  4321. function! s:Do(action, visual) abort
  4322. let line = getline('.')
  4323. let reload = 0
  4324. if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
  4325. let header = matchstr(line, '^\S\+\ze:')
  4326. if len(header) && exists('*s:Do' . a:action . header . 'Header')
  4327. let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
  4328. else
  4329. let section = matchstr(line, '^\S\+')
  4330. if exists('*s:Do' . a:action . section . 'Heading')
  4331. let reload = s:Do{a:action}{section}Heading(line) > 0
  4332. endif
  4333. endif
  4334. return reload ? s:ReloadStatus() : ''
  4335. endif
  4336. let selection = s:Selection(a:visual ? 'v' : 'n')
  4337. if empty(selection)
  4338. return ''
  4339. endif
  4340. call filter(selection, 'v:val.section ==# selection[0].section')
  4341. let status = 0
  4342. let err = ''
  4343. try
  4344. for record in selection
  4345. if exists('*s:Do' . a:action . record.section)
  4346. let status = s:Do{a:action}{record.section}(record)
  4347. else
  4348. continue
  4349. endif
  4350. if !status
  4351. return ''
  4352. endif
  4353. let reload = reload || (status > 0)
  4354. endfor
  4355. if status < 0
  4356. execute record.lnum + 1
  4357. endif
  4358. let success = 1
  4359. catch /^fugitive:/
  4360. return 'echoerr ' . string(v:exception)
  4361. finally
  4362. if reload
  4363. execute s:ReloadStatus()
  4364. endif
  4365. if exists('success')
  4366. call s:StageReveal()
  4367. endif
  4368. endtry
  4369. return ''
  4370. endfunction
  4371. function! s:StageReveal() abort
  4372. exe 'normal! zv'
  4373. let begin = line('.')
  4374. if getline(begin) =~# '^@'
  4375. let end = begin + 1
  4376. while getline(end) =~# '^[ \+-]'
  4377. let end += 1
  4378. endwhile
  4379. elseif getline(begin) =~# '^commit '
  4380. let end = begin
  4381. while end < line('$') && getline(end + 1) !~# '^commit '
  4382. let end += 1
  4383. endwhile
  4384. elseif getline(begin) =~# s:section_pattern
  4385. let end = begin
  4386. while len(getline(end + 1))
  4387. let end += 1
  4388. endwhile
  4389. endif
  4390. if exists('end')
  4391. while line('.') > line('w0') + &scrolloff && end > line('w$')
  4392. execute "normal! \<C-E>"
  4393. endwhile
  4394. endif
  4395. endfunction
  4396. let s:file_pattern = '^[A-Z?] .\|^diff --'
  4397. let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
  4398. let s:item_pattern = s:file_commit_pattern . '\|^@@'
  4399. function! s:NextHunk(count) abort
  4400. if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
  4401. exe s:StageInline('show')
  4402. endif
  4403. for i in range(a:count)
  4404. if &filetype ==# 'fugitive'
  4405. call search(s:file_pattern . '\|^@', 'W')
  4406. if getline('.') =~# s:file_pattern
  4407. exe s:StageInline('show')
  4408. if getline(line('.') + 1) =~# '^@'
  4409. +
  4410. endif
  4411. endif
  4412. else
  4413. call search('^@@', 'W')
  4414. endif
  4415. endfor
  4416. call s:StageReveal()
  4417. return '.'
  4418. endfunction
  4419. function! s:PreviousHunk(count) abort
  4420. normal! 0
  4421. for i in range(a:count)
  4422. if &filetype ==# 'fugitive'
  4423. if getline('.') =~# '^@' && getline(line('.') - 1) =~# s:file_pattern
  4424. -
  4425. endif
  4426. let lnum = search(s:file_pattern . '\|^@','Wbn')
  4427. call s:StageInline('show', lnum)
  4428. call search('^? .\|^@','Wb')
  4429. else
  4430. call search('^@@', 'Wb')
  4431. endif
  4432. endfor
  4433. call s:StageReveal()
  4434. return '.'
  4435. endfunction
  4436. function! s:NextFile(count) abort
  4437. for i in range(a:count)
  4438. exe s:StageInline('hide')
  4439. if !search(s:file_pattern, 'W')
  4440. break
  4441. endif
  4442. endfor
  4443. exe s:StageInline('hide')
  4444. return '.'
  4445. endfunction
  4446. function! s:PreviousFile(count) abort
  4447. exe s:StageInline('hide')
  4448. normal! 0
  4449. for i in range(a:count)
  4450. if !search(s:file_pattern, 'Wb')
  4451. break
  4452. endif
  4453. exe s:StageInline('hide')
  4454. endfor
  4455. return '.'
  4456. endfunction
  4457. function! s:NextItem(count) abort
  4458. for i in range(a:count)
  4459. if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
  4460. call search('^commit ', 'W')
  4461. endif
  4462. endfor
  4463. call s:StageReveal()
  4464. return '.'
  4465. endfunction
  4466. function! s:PreviousItem(count) abort
  4467. normal! 0
  4468. for i in range(a:count)
  4469. if !search(s:item_pattern, 'Wb') && getline('.') !~# s:item_pattern
  4470. call search('^commit ', 'Wb')
  4471. endif
  4472. endfor
  4473. call s:StageReveal()
  4474. return '.'
  4475. endfunction
  4476. let s:section_pattern = '^[A-Z][a-z][^:]*$'
  4477. let s:section_commit_pattern = s:section_pattern . '\|^commit '
  4478. function! s:NextSection(count) abort
  4479. let orig = line('.')
  4480. if getline('.') !~# '^commit '
  4481. -
  4482. endif
  4483. for i in range(a:count)
  4484. if !search(s:section_commit_pattern, 'W')
  4485. break
  4486. endif
  4487. endfor
  4488. if getline('.') =~# s:section_commit_pattern
  4489. call s:StageReveal()
  4490. return getline('.') =~# s:section_pattern ? '+' : ':'
  4491. else
  4492. return orig
  4493. endif
  4494. endfunction
  4495. function! s:PreviousSection(count) abort
  4496. let orig = line('.')
  4497. if getline('.') !~# '^commit '
  4498. -
  4499. endif
  4500. normal! 0
  4501. for i in range(a:count)
  4502. if !search(s:section_commit_pattern . '\|\%^', 'bW')
  4503. break
  4504. endif
  4505. endfor
  4506. if getline('.') =~# s:section_commit_pattern || line('.') == 1
  4507. call s:StageReveal()
  4508. return getline('.') =~# s:section_pattern ? '+' : ':'
  4509. else
  4510. return orig
  4511. endif
  4512. endfunction
  4513. function! s:NextSectionEnd(count) abort
  4514. +
  4515. if empty(getline('.'))
  4516. +
  4517. endif
  4518. for i in range(a:count)
  4519. if !search(s:section_commit_pattern, 'W')
  4520. return '$'
  4521. endif
  4522. endfor
  4523. return search('^.', 'Wb')
  4524. endfunction
  4525. function! s:PreviousSectionEnd(count) abort
  4526. let old = line('.')
  4527. for i in range(a:count)
  4528. if search(s:section_commit_pattern, 'Wb') <= 1
  4529. exe old
  4530. if i
  4531. break
  4532. else
  4533. return ''
  4534. endif
  4535. endif
  4536. let old = line('.')
  4537. endfor
  4538. return search('^.', 'Wb')
  4539. endfunction
  4540. function! s:PatchSearchExpr(reverse) abort
  4541. let line = getline('.')
  4542. if col('.') ==# 1 && line =~# '^[+-]'
  4543. if line =~# '^[+-]\{3\} '
  4544. let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
  4545. else
  4546. let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
  4547. endif
  4548. if a:reverse
  4549. return '?' . escape(pattern, '/?') . "\<CR>"
  4550. else
  4551. return '/' . escape(pattern, '/') . "\<CR>"
  4552. endif
  4553. endif
  4554. return a:reverse ? '#' : '*'
  4555. endfunction
  4556. function! s:StageInlineGetDiff(diff_section, info) abort
  4557. let diff = []
  4558. if a:info.status ==# 'U'
  4559. let diff_header = 'diff --cc ' . s:Quote(a:info.relative[0])
  4560. else
  4561. let diff_header = 'diff --git ' . s:Quote(a:info.relative[-1]) . ' ' . s:Quote(a:info.relative[0])
  4562. endif
  4563. let stdout = fugitive#Wait(a:diff_section).stdout
  4564. let start = index(stdout, diff_header)
  4565. if start == -1
  4566. return [[], -1]
  4567. endif
  4568. let index = start + 1
  4569. while get(stdout, index, '@@') !~# '^@@\|^diff '
  4570. let index += 1
  4571. endwhile
  4572. while get(stdout, index, '') =~# '^[@ \+-]'
  4573. call add(diff, stdout[index])
  4574. let index += 1
  4575. endwhile
  4576. return [diff, start]
  4577. endfunction
  4578. function! s:StageInline(mode, ...) abort
  4579. if &filetype !=# 'fugitive'
  4580. return ''
  4581. endif
  4582. let lnum1 = a:0 ? a:1 : line('.')
  4583. let lnum = lnum1 + 1
  4584. if a:0 > 1 && a:2 == 0 && lnum1 == 1
  4585. let lnum = line('$') - 1
  4586. elseif a:0 > 1 && a:2 == 0
  4587. let info = s:StageInfo(lnum - 1)
  4588. if empty(info.paths) && len(info.section)
  4589. while len(getline(lnum))
  4590. let lnum += 1
  4591. endwhile
  4592. endif
  4593. elseif a:0 > 1
  4594. let lnum += a:2 - 1
  4595. endif
  4596. while lnum > lnum1
  4597. let lnum -= 1
  4598. while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
  4599. let lnum -= 1
  4600. endwhile
  4601. let info = s:StageInfo(lnum)
  4602. let diff_section = get(get(get(b:, 'fugitive_status', {}), 'diff', {}), info.section, {})
  4603. if empty(diff_section)
  4604. continue
  4605. endif
  4606. if getline(lnum + 1) =~# '^[ @\+-]'
  4607. let lnum2 = lnum + 1
  4608. while getline(lnum2 + 1) =~# '^[ @\+-]'
  4609. let lnum2 += 1
  4610. endwhile
  4611. if a:mode !=# 'show'
  4612. setlocal modifiable noreadonly
  4613. exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
  4614. call remove(b:fugitive_expanded[info.section], info.filename)
  4615. setlocal nomodifiable readonly nomodified
  4616. endif
  4617. continue
  4618. endif
  4619. if info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
  4620. continue
  4621. endif
  4622. let [diff, start] = s:StageInlineGetDiff(diff_section, info)
  4623. if len(diff)
  4624. setlocal modifiable noreadonly
  4625. silent call append(lnum, diff)
  4626. let b:fugitive_expanded[info.section][info.filename] = [start]
  4627. setlocal nomodifiable readonly nomodified
  4628. if foldclosed(lnum+1) > 0
  4629. silent exe (lnum+1) . ',' . (lnum+len(diff)) . 'foldopen!'
  4630. endif
  4631. endif
  4632. endwhile
  4633. return lnum
  4634. endfunction
  4635. function! s:NextExpandedHunk(count) abort
  4636. for i in range(a:count)
  4637. call s:StageInline('show', line('.'), 1)
  4638. call search(s:file_pattern . '\|^@','W')
  4639. endfor
  4640. return '.'
  4641. endfunction
  4642. function! s:StageDiff(diff) abort
  4643. let lnum = line('.')
  4644. let info = s:StageInfo(lnum)
  4645. let prefix = info.offset > 0 ? '+' . info.offset : ''
  4646. if info.submodule =~# '^S'
  4647. if info.section ==# 'Staged'
  4648. return 'Git --paginate diff --no-ext-diff --submodule=log --cached -- ' . info.paths[0]
  4649. elseif info.submodule =~# '^SC'
  4650. return 'Git --paginate diff --no-ext-diff --submodule=log -- ' . info.paths[0]
  4651. else
  4652. return 'Git --paginate diff --no-ext-diff --submodule=diff -- ' . info.paths[0]
  4653. endif
  4654. elseif empty(info.paths) && info.section ==# 'Staged'
  4655. return 'Git --paginate diff --no-ext-diff --cached'
  4656. elseif empty(info.paths)
  4657. return 'Git --paginate diff --no-ext-diff'
  4658. elseif len(info.paths) > 1
  4659. execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
  4660. return 'keepalt ' . a:diff . '! @:'.s:fnameescape(info.paths[1])
  4661. elseif info.section ==# 'Staged' && info.sigil ==# '-'
  4662. execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
  4663. return 'keepalt ' . a:diff . '! :0:%'
  4664. elseif info.section ==# 'Staged'
  4665. execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
  4666. return 'keepalt ' . a:diff . '! @:%'
  4667. elseif info.sigil ==# '-'
  4668. execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
  4669. return 'keepalt ' . a:diff . '! :(top)%'
  4670. else
  4671. execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
  4672. return 'keepalt ' . a:diff . '!'
  4673. endif
  4674. endfunction
  4675. function! s:StageDiffEdit() abort
  4676. let info = s:StageInfo(line('.'))
  4677. let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
  4678. if info.section ==# 'Staged'
  4679. return 'Git --paginate diff --no-ext-diff --cached '.s:fnameescape(arg)
  4680. elseif info.status ==# '?'
  4681. call s:TreeChomp('add', '--intent-to-add', '--', arg)
  4682. return s:ReloadStatus()
  4683. else
  4684. return 'Git --paginate diff --no-ext-diff '.s:fnameescape(arg)
  4685. endif
  4686. endfunction
  4687. function! s:StageApply(info, reverse, extra) abort
  4688. if a:info.status ==# 'R'
  4689. throw 'fugitive: patching renamed file not yet supported'
  4690. endif
  4691. let cmd = ['apply', '-p0', '--recount'] + a:extra
  4692. let info = a:info
  4693. let start = info.patch
  4694. let end = info.lnum
  4695. let lines = getline(start, end)
  4696. if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
  4697. return -1
  4698. endif
  4699. while getline(end) =~# '^[-+\ ]'
  4700. let end += 1
  4701. if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . '\ ]'
  4702. call add(lines, ' ' . getline(end)[1:-1])
  4703. endif
  4704. endwhile
  4705. while start > 0 && getline(start) !~# '^@'
  4706. let start -= 1
  4707. if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
  4708. call insert(lines, ' ' . getline(start)[1:-1])
  4709. elseif getline(start) =~# '^@'
  4710. call insert(lines, getline(start))
  4711. endif
  4712. endwhile
  4713. if start == 0
  4714. throw 'fugitive: could not find hunk'
  4715. elseif getline(start) !~# '^@@ '
  4716. throw 'fugitive: cannot apply conflict hunk'
  4717. endif
  4718. let i = b:fugitive_expanded[info.section][info.filename][0]
  4719. let head = []
  4720. let diff_lines = fugitive#Wait(b:fugitive_status.diff[info.section]).stdout
  4721. while get(diff_lines, i, '@') !~# '^@'
  4722. let line = diff_lines[i]
  4723. if line ==# '--- /dev/null'
  4724. call add(head, '--- ' . get(diff_lines, i + 1, '')[4:-1])
  4725. elseif line !~# '^new file '
  4726. call add(head, line)
  4727. endif
  4728. let i += 1
  4729. endwhile
  4730. call extend(lines, head, 'keep')
  4731. let temp = tempname()
  4732. call writefile(lines, temp)
  4733. if a:reverse
  4734. call add(cmd, '--reverse')
  4735. endif
  4736. call extend(cmd, ['--', temp])
  4737. let output = s:ChompStderr(cmd)
  4738. if empty(output)
  4739. return 1
  4740. endif
  4741. call s:throw(output)
  4742. endfunction
  4743. function! s:StageDelete(lnum1, lnum2, count) abort
  4744. let restore = []
  4745. let err = ''
  4746. let did_conflict_err = 0
  4747. let reset_commit = matchstr(getline(a:lnum1), '^Un\w\+ \%(to\| from\) \zs\S\+')
  4748. try
  4749. for info in s:Selection(a:lnum1, a:lnum2)
  4750. if empty(info.paths)
  4751. if len(info.commit)
  4752. let reset_commit = info.commit . '^'
  4753. endif
  4754. continue
  4755. endif
  4756. let sub = get(s:StatusSectionFile(info.section, info.filename), 'submodule', '')
  4757. if sub =~# '^S' && info.status ==# 'M'
  4758. let undo = 'Git checkout ' . fugitive#RevParse('HEAD', FugitiveExtractGitDir(info.paths[0]))[0:10] . ' --'
  4759. elseif sub =~# '^S'
  4760. let err .= '|echoerr ' . string('fugitive: will not touch submodule ' . string(info.relative[0]))
  4761. break
  4762. elseif info.status ==# 'D'
  4763. let undo = 'GRemove'
  4764. elseif info.paths[0] =~# '/$'
  4765. let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
  4766. break
  4767. else
  4768. let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
  4769. endif
  4770. if info.patch
  4771. call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
  4772. elseif sub =~# '^S'
  4773. if info.section ==# 'Staged'
  4774. call s:TreeChomp('reset', '--', info.paths[0])
  4775. endif
  4776. call s:TreeChomp('submodule', 'update', '--', info.paths[0])
  4777. elseif info.status ==# '?'
  4778. call s:TreeChomp('clean', '-f', '--', info.paths[0])
  4779. elseif a:count == 2
  4780. if get(s:StatusSectionFile('Staged', info.filename), 'status', '') ==# 'D'
  4781. call delete(info.paths[0])
  4782. else
  4783. call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
  4784. endif
  4785. elseif a:count == 3
  4786. if get(s:StatusSectionFile('Unstaged', info.filename), 'status', '') ==# 'D'
  4787. call delete(info.paths[0])
  4788. else
  4789. call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
  4790. endif
  4791. elseif info.status =~# '[ADU]' &&
  4792. \ get(s:StatusSectionFile(info.section ==# 'Staged' ? 'Unstaged' : 'Staged', info.filename), 'status', '') =~# '[AU]'
  4793. if get(g:, 'fugitive_conflict_x', 0)
  4794. call s:TreeChomp('checkout', info.section ==# 'Unstaged' ? '--ours' : '--theirs', '--', info.paths[0])
  4795. else
  4796. if !did_conflict_err
  4797. let err .= '|echoerr "Use 2X for --ours or 3X for --theirs"'
  4798. let did_conflict_err = 1
  4799. endif
  4800. continue
  4801. endif
  4802. elseif info.status ==# 'U'
  4803. call delete(info.paths[0])
  4804. elseif info.status ==# 'A'
  4805. call s:TreeChomp('rm', '-f', '--', info.paths[0])
  4806. elseif info.section ==# 'Unstaged'
  4807. call s:TreeChomp('checkout', '--', info.paths[0])
  4808. else
  4809. call s:TreeChomp('checkout', '@', '--', info.paths[0])
  4810. endif
  4811. if len(undo)
  4812. call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
  4813. endif
  4814. endfor
  4815. catch /^fugitive:/
  4816. let err .= '|echoerr ' . string(v:exception)
  4817. endtry
  4818. if empty(restore)
  4819. if len(reset_commit) && empty(err)
  4820. call feedkeys(':Git reset ' . reset_commit)
  4821. endif
  4822. return err[1:-1]
  4823. endif
  4824. exe s:ReloadStatus()
  4825. call s:StageReveal()
  4826. return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
  4827. endfunction
  4828. function! s:StageIgnore(lnum1, lnum2, count) abort
  4829. let paths = []
  4830. for info in s:Selection(a:lnum1, a:lnum2)
  4831. call extend(paths, info.relative)
  4832. endfor
  4833. call map(paths, '"/" . v:val')
  4834. if !a:0
  4835. let dir = fugitive#Find('.git/info/')
  4836. if !isdirectory(dir)
  4837. try
  4838. call mkdir(dir)
  4839. catch
  4840. endtry
  4841. endif
  4842. endif
  4843. exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
  4844. let last = line('$')
  4845. if last == 1 && empty(getline(1))
  4846. call setline(last, paths)
  4847. else
  4848. call append(last, paths)
  4849. exe last + 1
  4850. endif
  4851. return ''
  4852. endfunction
  4853. function! s:DoToggleHeadHeader(value) abort
  4854. exe 'edit' fnameescape(fugitive#Find('.git/'))
  4855. call search('\C^index$', 'wc')
  4856. endfunction
  4857. function! s:DoToggleHelpHeader(value) abort
  4858. exe 'help fugitive-map'
  4859. endfunction
  4860. function! s:DoStagePushHeader(value) abort
  4861. let stat = get(b:, 'fugitive_status', {})
  4862. let remote = get(stat, 'push_remote', '')
  4863. let branch = substitute(get(stat, 'push', ''), '^ref/heads/', '', '')
  4864. if empty(remote) || empty(branch)
  4865. return
  4866. endif
  4867. call feedkeys(':Git push ' . remote . ' ' . branch)
  4868. endfunction
  4869. function! s:DoTogglePushHeader(value) abort
  4870. return s:DoStagePushHeader(a:value)
  4871. endfunction
  4872. function! s:DoStageUnpushedHeading(heading) abort
  4873. let stat = get(b:, 'fugitive_status', {})
  4874. let remote = get(stat, 'push_remote', '')
  4875. let push = get(stat, 'push', '')
  4876. if empty(remote) || empty(push)
  4877. return
  4878. endif
  4879. call feedkeys(':Git push ' . remote . ' ' . '@:' . push)
  4880. endfunction
  4881. function! s:DoToggleUnpushedHeading(heading) abort
  4882. return s:DoStageUnpushedHeading(a:heading)
  4883. endfunction
  4884. function! s:DoStageUnpushed(record) abort
  4885. let stat = get(b:, 'fugitive_status', {})
  4886. let remote = get(stat, 'push_remote', '')
  4887. let push = get(stat, 'push', '')
  4888. if empty(remote) || empty(push)
  4889. return
  4890. endif
  4891. call feedkeys(':Git push ' . remote . ' ' . a:record.commit . ':' . push)
  4892. endfunction
  4893. function! s:DoToggleUnpushed(record) abort
  4894. return s:DoStageUnpushed(a:record)
  4895. endfunction
  4896. function! s:DoUnstageUnpulledHeading(heading) abort
  4897. call feedkeys(':Git rebase')
  4898. endfunction
  4899. function! s:DoToggleUnpulledHeading(heading) abort
  4900. call s:DoUnstageUnpulledHeading(a:heading)
  4901. endfunction
  4902. function! s:DoUnstageUnpulled(record) abort
  4903. call feedkeys(':Git rebase ' . a:record.commit)
  4904. endfunction
  4905. function! s:DoToggleUnpulled(record) abort
  4906. call s:DoUnstageUnpulled(a:record)
  4907. endfunction
  4908. function! s:DoUnstageUnpushed(record) abort
  4909. call feedkeys(':Git -c sequence.editor=true rebase --interactive --autosquash ' . a:record.commit . '^')
  4910. endfunction
  4911. function! s:DoToggleStagedHeading(...) abort
  4912. call s:TreeChomp('reset', '-q')
  4913. return 1
  4914. endfunction
  4915. function! s:DoUnstageStagedHeading(heading) abort
  4916. return s:DoToggleStagedHeading(a:heading)
  4917. endfunction
  4918. function! s:DoToggleUnstagedHeading(...) abort
  4919. call s:TreeChomp('add', '-u')
  4920. return 1
  4921. endfunction
  4922. function! s:DoStageUnstagedHeading(heading) abort
  4923. return s:DoToggleUnstagedHeading(a:heading)
  4924. endfunction
  4925. function! s:DoToggleUntrackedHeading(...) abort
  4926. call s:TreeChomp('add', '.')
  4927. return 1
  4928. endfunction
  4929. function! s:DoStageUntrackedHeading(heading) abort
  4930. return s:DoToggleUntrackedHeading(a:heading)
  4931. endfunction
  4932. function! s:DoToggleStaged(record) abort
  4933. if a:record.patch
  4934. return s:StageApply(a:record, 1, ['--cached'])
  4935. else
  4936. call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
  4937. return 1
  4938. endif
  4939. endfunction
  4940. function! s:DoUnstageStaged(record) abort
  4941. return s:DoToggleStaged(a:record)
  4942. endfunction
  4943. function! s:DoToggleUnstaged(record) abort
  4944. if a:record.patch
  4945. return s:StageApply(a:record, 0, ['--cached'])
  4946. else
  4947. call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
  4948. return 1
  4949. endif
  4950. endfunction
  4951. function! s:DoStageUnstaged(record) abort
  4952. return s:DoToggleUnstaged(a:record)
  4953. endfunction
  4954. function! s:DoUnstageUnstaged(record) abort
  4955. if a:record.status ==# 'A'
  4956. call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
  4957. return 1
  4958. else
  4959. return -1
  4960. endif
  4961. endfunction
  4962. function! s:DoToggleUntracked(record) abort
  4963. call s:TreeChomp(['add', '--'] + a:record.paths)
  4964. return 1
  4965. endfunction
  4966. function! s:DoStageUntracked(record) abort
  4967. return s:DoToggleUntracked(a:record)
  4968. endfunction
  4969. function! s:StagePatch(lnum1, lnum2, ...) abort
  4970. let add = []
  4971. let reset = []
  4972. let intend = []
  4973. let patch_only = a:0 && a:1
  4974. for lnum in range(a:lnum1,a:lnum2)
  4975. let info = s:StageInfo(lnum)
  4976. if empty(info.paths) && info.section ==# 'Staged'
  4977. execute 'tab Git reset --patch'
  4978. break
  4979. elseif empty(info.paths) && info.section ==# 'Unstaged'
  4980. execute 'tab Git add --patch'
  4981. break
  4982. elseif empty(info.paths) && info.section ==# 'Untracked'
  4983. execute 'tab Git add --interactive'
  4984. break
  4985. elseif !patch_only && info.section ==# 'Unpushed'
  4986. if empty(info.commit)
  4987. call s:DoStageUnpushedHeading(info)
  4988. else
  4989. call s:DoStageUnpushed(info)
  4990. endif
  4991. return ''
  4992. elseif empty(info.paths)
  4993. continue
  4994. endif
  4995. execute lnum
  4996. if info.section ==# 'Staged'
  4997. let reset += info.relative
  4998. elseif info.section ==# 'Untracked'
  4999. let intend += info.paths
  5000. elseif info.status !~# '^D'
  5001. let add += info.relative
  5002. endif
  5003. endfor
  5004. try
  5005. if !empty(intend)
  5006. call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
  5007. endif
  5008. if !empty(add)
  5009. execute "tab Git add --patch -- ".join(map(add,'fnameescape(v:val)'))
  5010. endif
  5011. if !empty(reset)
  5012. execute "tab Git reset --patch -- ".join(map(reset,'fnameescape(v:val)'))
  5013. endif
  5014. catch /^fugitive:/
  5015. return 'echoerr ' . string(v:exception)
  5016. endtry
  5017. return s:ReloadStatus()
  5018. endfunction
  5019. " Section: :Git commit, :Git revert
  5020. function! s:CommitInteractive(line1, line2, range, bang, mods, options, patch) abort
  5021. 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)
  5022. let status = len(status) ? status . '|' : ''
  5023. if a:patch
  5024. return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
  5025. else
  5026. return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
  5027. endif
  5028. endfunction
  5029. function! s:CommitSubcommand(line1, line2, range, bang, mods, options) abort
  5030. let argv = copy(a:options.subcommand_args)
  5031. let i = 0
  5032. while get(argv, i, '--') !=# '--'
  5033. if argv[i] =~# '^-[apzsneiovq].'
  5034. call insert(argv, argv[i][0:1])
  5035. let argv[i+1] = '-' . argv[i+1][2:-1]
  5036. else
  5037. let i += 1
  5038. endif
  5039. endwhile
  5040. if s:HasOpt(argv, '-i', '--interactive')
  5041. return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 0)
  5042. elseif s:HasOpt(argv, '-p', '--patch')
  5043. return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 1)
  5044. else
  5045. return {}
  5046. endif
  5047. endfunction
  5048. function! s:RevertSubcommand(line1, line2, range, bang, mods, options) abort
  5049. return {'insert_args': ['--edit']}
  5050. endfunction
  5051. function! fugitive#CommitComplete(A, L, P, ...) abort
  5052. let dir = a:0 ? a:1 : s:Dir()
  5053. if a:A =~# '^--fixup=\|^--squash='
  5054. let commits = s:LinesError([dir, 'log', '--pretty=format:%s', '@{upstream}..'])[0]
  5055. let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
  5056. if pre =~# "'"
  5057. call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
  5058. call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
  5059. return commits
  5060. else
  5061. return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
  5062. endif
  5063. else
  5064. return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'), a:000)
  5065. endif
  5066. return []
  5067. endfunction
  5068. function! fugitive#RevertComplete(A, L, P, ...) abort
  5069. return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
  5070. endfunction
  5071. " Section: :Git merge, :Git rebase, :Git pull
  5072. function! fugitive#MergeComplete(A, L, P, ...) abort
  5073. return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
  5074. endfunction
  5075. function! fugitive#RebaseComplete(A, L, P, ...) abort
  5076. return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
  5077. endfunction
  5078. function! fugitive#PullComplete(A, L, P, ...) abort
  5079. return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
  5080. endfunction
  5081. function! s:MergeSubcommand(line1, line2, range, bang, mods, options) abort
  5082. if empty(a:options.subcommand_args) && (
  5083. \ filereadable(fugitive#Find('.git/MERGE_MSG', a:options)) ||
  5084. \ isdirectory(fugitive#Find('.git/rebase-apply', a:options)) ||
  5085. \ !empty(s:TreeChomp([a:options.git_dir, 'diff-files', '--diff-filter=U'])))
  5086. return 'echoerr ":Git merge for loading conflicts has been removed in favor of :Git mergetool"'
  5087. endif
  5088. return {}
  5089. endfunction
  5090. function! s:RebaseSubcommand(line1, line2, range, bang, mods, options) abort
  5091. let args = a:options.subcommand_args
  5092. if s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '-i', '--interactive')
  5093. return {'env': {'GIT_SEQUENCE_EDITOR': 'true'}, 'insert_args': ['--interactive']}
  5094. endif
  5095. return {}
  5096. endfunction
  5097. " Section: :Git bisect
  5098. function! s:CompleteBisect(A, L, P, ...) abort
  5099. let bisect_subcmd = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
  5100. if empty(bisect_subcmd)
  5101. let subcmds = ['start', 'bad', 'new', 'good', 'old', 'terms', 'skip', 'next', 'reset', 'replay', 'log', 'run']
  5102. return s:FilterEscape(subcmds, a:A)
  5103. endif
  5104. let dir = a:0 ? a:1 : s:Dir()
  5105. return fugitive#CompleteObject(a:A, dir)
  5106. endfunction
  5107. function! fugitive#BisectComplete(A, L, P, ...) abort
  5108. return s:CompleteSub('bisect', a:A, a:L, a:P, function('s:CompleteBisect'), a:000)
  5109. endfunction
  5110. " Section: :Git difftool, :Git mergetool
  5111. function! s:ToolItems(state, from, to, offsets, text, ...) abort
  5112. let items = []
  5113. for i in range(len(a:state.diff))
  5114. let diff = a:state.diff[i]
  5115. let path = (i == len(a:state.diff) - 1) ? a:to : a:from
  5116. if empty(path)
  5117. return []
  5118. endif
  5119. let item = {
  5120. \ 'valid': a:0 ? a:1 : 1,
  5121. \ 'filename': diff.filename . s:VimSlash(path),
  5122. \ 'lnum': matchstr(get(a:offsets, i), '\d\+'),
  5123. \ 'text': a:text}
  5124. if len(get(diff, 'module', ''))
  5125. let item.module = diff.module . path
  5126. endif
  5127. call add(items, item)
  5128. endfor
  5129. if get(a:offsets, 0, '') isnot# 'none'
  5130. let items[-1].context = {'diff': items[0:-2]}
  5131. endif
  5132. return [items[-1]]
  5133. endfunction
  5134. function! s:ToolToFrom(str) abort
  5135. if a:str =~# ' => '
  5136. let str = a:str =~# '{.* => .*}' ? a:str : '{' . a:str . '}'
  5137. return [substitute(str, '{.* => \(.*\)}', '\1', ''),
  5138. \ substitute(str, '{\(.*\) => .*}', '\1', '')]
  5139. else
  5140. return [a:str, a:str]
  5141. endif
  5142. endfunction
  5143. function! s:ToolParse(state, line) abort
  5144. if type(a:line) !=# type('') || a:state.mode ==# 'hunk' && a:line =~# '^[ +-]'
  5145. return []
  5146. elseif a:line =~# '^diff '
  5147. let a:state.mode = 'diffhead'
  5148. let a:state.from = ''
  5149. let a:state.to = ''
  5150. elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- [^/]'
  5151. let a:state.from = a:line[4:-1]
  5152. let a:state.to = a:state.from
  5153. elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ [^/]'
  5154. let a:state.to = a:line[4:-1]
  5155. if empty(get(a:state, 'from', ''))
  5156. let a:state.from = a:state.to
  5157. endif
  5158. elseif a:line[0] ==# '@'
  5159. let a:state.mode = 'hunk'
  5160. if has_key(a:state, 'from')
  5161. let offsets = split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' ')
  5162. return s:ToolItems(a:state, a:state.from, a:state.to, offsets, matchstr(a:line, ' @@\+ \zs.*'))
  5163. endif
  5164. elseif a:line =~# '^\* Unmerged path .'
  5165. let file = a:line[16:-1]
  5166. return s:ToolItems(a:state, file, file, [], '')
  5167. elseif a:line =~# '^[A-Z]\d*\t.\|^:.*\t.'
  5168. " --raw, --name-status
  5169. let [status; files] = split(a:line, "\t")
  5170. return s:ToolItems(a:state, files[0], files[-1], [], a:state.name_only ? '' : status)
  5171. elseif a:line =~# '^ \S.* |'
  5172. " --stat
  5173. let [_, to, changes; __] = matchlist(a:line, '^ \(.\{-\}\) \+|\zs \(.*\)$')
  5174. let [to, from] = s:ToolToFrom(to)
  5175. return s:ToolItems(a:state, from, to, [], changes)
  5176. elseif a:line =~# '^ *\([0-9.]\+%\) .'
  5177. " --dirstat
  5178. let [_, changes, to; __] = matchlist(a:line, '^ *\([0-9.]\+%\) \(.*\)')
  5179. return s:ToolItems(a:state, to, to, [], changes)
  5180. elseif a:line =~# '^\(\d\+\|-\)\t\(\d\+\|-\)\t.'
  5181. " --numstat
  5182. let [_, add, remove, to; __] = matchlist(a:line, '^\(\d\+\|-\)\t\(\d\+\|-\)\t\(.*\)')
  5183. let [to, from] = s:ToolToFrom(to)
  5184. return s:ToolItems(a:state, from, to, [], add ==# '-' ? 'Binary file' : '+' . add . ' -' . remove, add !=# '-')
  5185. elseif a:line =~# '^\f\+:\d\+: \D'
  5186. " --check
  5187. let [_, to, line, text; __] = matchlist(a:line, '^\(\f\+\):\(\d\+\):\s*\(.*\)$')
  5188. return s:ToolItems(a:state, to, to, ['none', line], text)
  5189. elseif a:state.mode !=# 'diffhead' && a:state.mode !=# 'hunk' && len(a:line) || a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
  5190. return [{'text': a:line}]
  5191. endif
  5192. return []
  5193. endfunction
  5194. function! s:ToolStream(line1, line2, range, bang, mods, options, args, state) abort
  5195. let i = 0
  5196. let argv = copy(a:args)
  5197. let prompt = 1
  5198. let state = a:state
  5199. while i < len(argv)
  5200. let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
  5201. if len(match) && len(match[2])
  5202. call insert(argv, match[1])
  5203. let argv[i+1] = '-' . match[2]
  5204. continue
  5205. endif
  5206. let arg = argv[i]
  5207. if arg =~# '^-t$\|^--tool=\|^--tool-help$\|^--help$'
  5208. return {}
  5209. elseif arg =~# '^-y$\|^--no-prompt$'
  5210. let prompt = 0
  5211. call remove(argv, i)
  5212. continue
  5213. elseif arg ==# '--prompt'
  5214. let prompt = 1
  5215. call remove(argv, i)
  5216. continue
  5217. elseif arg =~# '^--\%(no-\)\=\(symlinks\|trust-exit-code\|gui\)$'
  5218. call remove(argv, i)
  5219. continue
  5220. elseif arg ==# '--'
  5221. break
  5222. endif
  5223. let i += 1
  5224. endwhile
  5225. call fugitive#Autowrite()
  5226. let a:state.mode = 'init'
  5227. let a:state.from = ''
  5228. let a:state.to = ''
  5229. let exec = s:UserCommandList({'git': a:options.git, 'git_dir': a:options.git_dir}) + ['-c', 'diff.context=0']
  5230. let exec += a:options.flags + ['--no-pager', 'diff', '--no-ext-diff', '--no-color', '--no-prefix'] + argv
  5231. if prompt
  5232. let title = ':Git ' . s:fnameescape(a:options.flags + [a:options.subcommand] + a:options.subcommand_args)
  5233. 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)
  5234. else
  5235. let filename = ''
  5236. let cmd = []
  5237. let tabnr = tabpagenr() + 1
  5238. for line in s:SystemList(exec)[0]
  5239. for item in s:ToolParse(a:state, line)
  5240. if len(get(item, 'filename', '')) && item.filename != filename
  5241. call add(cmd, 'tabedit ' . s:fnameescape(item.filename))
  5242. for i in reverse(range(len(get(get(item, 'context', {}), 'diff', []))))
  5243. call add(cmd, (i ? 'rightbelow' : 'leftabove') . ' vertical Gdiffsplit! ' . s:fnameescape(item.context.diff[i].filename))
  5244. endfor
  5245. call add(cmd, 'wincmd =')
  5246. let filename = item.filename
  5247. endif
  5248. endfor
  5249. endfor
  5250. return join(cmd, '|') . (empty(cmd) ? '' : '|' . tabnr . 'tabnext')
  5251. endif
  5252. endfunction
  5253. function! s:MergetoolSubcommand(line1, line2, range, bang, mods, options) abort
  5254. let dir = a:options.git_dir
  5255. exe s:DirCheck(dir)
  5256. let i = 0
  5257. let prompt = 1
  5258. let cmd = ['diff', '--diff-filter=U']
  5259. let state = {'name_only': 0}
  5260. let state.diff = [{'prefix': ':2:', 'module': ':2:'}, {'prefix': ':3:', 'module': ':3:'}, {'prefix': ':(top)'}]
  5261. call map(state.diff, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
  5262. return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, ['--diff-filter=U'] + a:options.subcommand_args, state)
  5263. endfunction
  5264. function! s:DifftoolSubcommand(line1, line2, range, bang, mods, options) abort
  5265. let dir = s:Dir(a:options)
  5266. exe s:DirCheck(dir)
  5267. let i = 0
  5268. let argv = copy(a:options.subcommand_args)
  5269. let commits = []
  5270. let cached = 0
  5271. let reverse = 1
  5272. let prompt = 1
  5273. let state = {'name_only': 0}
  5274. let merge_base_against = {}
  5275. let dash = (index(argv, '--') > i ? ['--'] : [])
  5276. while i < len(argv)
  5277. let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
  5278. if len(match) && len(match[2])
  5279. call insert(argv, match[1])
  5280. let argv[i+1] = '-' . match[2]
  5281. continue
  5282. endif
  5283. let arg = argv[i]
  5284. if arg ==# '--cached'
  5285. let cached = 1
  5286. elseif arg ==# '-R'
  5287. let reverse = 1
  5288. elseif arg ==# '--name-only'
  5289. let state.name_only = 1
  5290. let argv[0] = '--name-status'
  5291. elseif arg ==# '--'
  5292. break
  5293. elseif arg !~# '^-\|^\.\.\=\%(/\|$\)'
  5294. let parsed = s:LinesError(['rev-parse', '--revs-only', substitute(arg, ':.*', '', '')] + dash)[0]
  5295. call map(parsed, '{"uninteresting": v:val =~# "^\\^", "prefix": substitute(v:val, "^\\^", "", "") . ":"}')
  5296. let merge_base_against = {}
  5297. if arg =~# '\.\.\.' && len(parsed) > 2
  5298. let display = map(split(arg, '\.\.\.', 1), 'empty(v:val) ? "@" : v:val')
  5299. if len(display) == 2
  5300. let parsed[0].module = display[1] . ':'
  5301. let parsed[1].module = display[0] . ':'
  5302. endif
  5303. let parsed[2].module = arg . ':'
  5304. if empty(commits)
  5305. let merge_base_against = parsed[0]
  5306. let parsed = [parsed[2]]
  5307. endif
  5308. elseif arg =~# '\.\.' && len(parsed) == 2
  5309. let display = map(split(arg, '\.\.', 1), 'empty(v:val) ? "@" : v:val')
  5310. if len(display) == 2
  5311. let parsed[0].module = display[0] . ':'
  5312. let parsed[1].module = display[1] . ':'
  5313. endif
  5314. elseif len(parsed) == 1
  5315. let parsed[0].module = arg . ':'
  5316. endif
  5317. call extend(commits, parsed)
  5318. endif
  5319. let i += 1
  5320. endwhile
  5321. if len(merge_base_against)
  5322. call add(commits, merge_base_against)
  5323. endif
  5324. let commits = filter(copy(commits), 'v:val.uninteresting') + filter(commits, '!v:val.uninteresting')
  5325. if cached
  5326. if empty(commits)
  5327. call add(commits, {'prefix': '@:', 'module': '@:'})
  5328. endif
  5329. call add(commits, {'prefix': ':0:', 'module': ':0:'})
  5330. elseif len(commits) < 2
  5331. call add(commits, {'prefix': ':(top)'})
  5332. if len(commits) < 2
  5333. call insert(commits, {'prefix': ':0:', 'module': ':0:'})
  5334. endif
  5335. endif
  5336. if reverse
  5337. let commits = [commits[-1]] + repeat([commits[0]], len(commits) - 1)
  5338. call reverse(commits)
  5339. endif
  5340. if len(commits) > 2
  5341. call add(commits, remove(commits, 0))
  5342. endif
  5343. call map(commits, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
  5344. let state.diff = commits
  5345. return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, argv, state)
  5346. endfunction
  5347. " Section: :Ggrep, :Glog
  5348. if !exists('g:fugitive_summary_format')
  5349. let g:fugitive_summary_format = '%s'
  5350. endif
  5351. function! fugitive#GrepComplete(A, L, P) abort
  5352. return s:CompleteSub('grep', a:A, a:L, a:P)
  5353. endfunction
  5354. function! fugitive#LogComplete(A, L, P) abort
  5355. return s:CompleteSub('log', a:A, a:L, a:P)
  5356. endfunction
  5357. function! s:GrepParseLine(options, quiet, dir, line) abort
  5358. if !a:quiet
  5359. echo a:line
  5360. endif
  5361. let entry = {'valid': 1}
  5362. let match = matchlist(a:line, '^\(.\{-\}\):\([1-9]\d*\):\([1-9]\d*:\)\=\(.*\)$')
  5363. if a:line =~# '^git: \|^usage: \|^error: \|^fatal: \|^BUG: '
  5364. return {'text': a:line}
  5365. elseif len(match)
  5366. let entry.module = match[1]
  5367. let entry.lnum = +match[2]
  5368. let entry.col = +match[3]
  5369. let entry.text = match[4]
  5370. else
  5371. let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
  5372. if len(entry.module)
  5373. let entry.text = 'Binary file'
  5374. let entry.valid = 0
  5375. endif
  5376. endif
  5377. if empty(entry.module) && !a:options.line_number
  5378. let match = matchlist(a:line, '^\(.\{-\}\):\(.*\)$')
  5379. if len(match)
  5380. let entry.module = match[1]
  5381. let entry.pattern = '\M^' . escape(match[2], '\.^$/') . '$'
  5382. endif
  5383. endif
  5384. if empty(entry.module) && a:options.name_count && a:line =~# ':\d\+$'
  5385. let entry.text = matchstr(a:line, '\d\+$')
  5386. let entry.module = strpart(a:line, 0, len(a:line) - len(entry.text) - 1)
  5387. endif
  5388. if empty(entry.module) && a:options.name_only
  5389. let entry.module = a:line
  5390. endif
  5391. if empty(entry.module)
  5392. return {'text': a:line}
  5393. endif
  5394. if entry.module !~# ':'
  5395. let entry.filename = s:PathJoin(a:options.prefix, entry.module)
  5396. else
  5397. let entry.filename = fugitive#Find(matchstr(entry.module, '^[^:]*:') .
  5398. \ substitute(matchstr(entry.module, ':\zs.*'), '/\=:', '/', 'g'), a:dir)
  5399. endif
  5400. return entry
  5401. endfunction
  5402. let s:grep_combine_flags = '[aiIrhHEGPFnlLzocpWq]\{-\}'
  5403. function! s:GrepOptions(args, dir) abort
  5404. let options = {'name_only': 0, 'name_count': 0, 'line_number': 0}
  5405. let tree = s:Tree(a:dir)
  5406. let prefix = empty(tree) ? fugitive#Find(':0:', a:dir) :
  5407. \ s:VimSlash(tree . '/')
  5408. let options.prefix = prefix
  5409. for arg in a:args
  5410. if arg ==# '--'
  5411. break
  5412. endif
  5413. if arg =~# '^\%(-' . s:grep_combine_flags . 'c\|--count\)$'
  5414. let options.name_count = 1
  5415. endif
  5416. if arg =~# '^\%(-' . s:grep_combine_flags . 'n\|--line-number\)$'
  5417. let options.line_number = 1
  5418. elseif arg =~# '^\%(--no-line-number\)$'
  5419. let options.line_number = 0
  5420. endif
  5421. if arg =~# '^\%(-' . s:grep_combine_flags . '[lL]\|--files-with-matches\|--name-only\|--files-without-match\)$'
  5422. let options.name_only = 1
  5423. endif
  5424. if arg ==# '--cached'
  5425. let options.prefix = fugitive#Find(':0:', a:dir)
  5426. elseif arg ==# '--no-cached'
  5427. let options.prefix = prefix
  5428. endif
  5429. endfor
  5430. return options
  5431. endfunction
  5432. function! s:GrepCfile(result) abort
  5433. let options = s:GrepOptions(a:result.args, a:result)
  5434. let entry = s:GrepParseLine(options, 1, a:result, getline('.'))
  5435. if get(entry, 'col')
  5436. return [entry.filename, entry.lnum, "norm!" . entry.col . "|"]
  5437. elseif has_key(entry, 'lnum')
  5438. return [entry.filename, entry.lnum]
  5439. elseif has_key(entry, 'pattern')
  5440. return [entry.filename, '', 'silent /' . entry.pattern]
  5441. elseif has_key(entry, 'filename')
  5442. return [entry.filename]
  5443. else
  5444. return []
  5445. endif
  5446. endfunction
  5447. function! s:GrepSubcommand(line1, line2, range, bang, mods, options) abort
  5448. let args = copy(a:options.subcommand_args)
  5449. let handle = -1
  5450. let quiet = 0
  5451. let i = 0
  5452. while i < len(args) && args[i] !=# '--'
  5453. let partition = matchstr(args[i], '^-' . s:grep_combine_flags . '\ze[qzO]')
  5454. if len(partition) > 1
  5455. call insert(args, '-' . strpart(args[i], len(partition)), i+1)
  5456. let args[i] = partition
  5457. elseif args[i] =~# '^\%(-' . s:grep_combine_flags . '[eABC]\|--max-depth\|--context\|--after-context\|--before-context\|--threads\)$'
  5458. let i += 1
  5459. elseif args[i] =~# '^\%(-O\|--open-files-in-pager\)$'
  5460. let handle = 1
  5461. call remove(args, i)
  5462. continue
  5463. elseif args[i] =~# '^\%(-O\|--open-files-in-pager=\)'
  5464. let handle = 0
  5465. elseif args[i] =~# '^-[qz].'
  5466. let args[i] = '-' . args[i][2:-1]
  5467. let quiet = 1
  5468. elseif args[i] =~# '^\%(-[qz]\|--quiet\)$'
  5469. let quiet = 1
  5470. call remove(args, i)
  5471. continue
  5472. elseif args[i] =~# '^--no-quiet$'
  5473. let quiet = 0
  5474. elseif args[i] =~# '^\%(--heading\)$'
  5475. call remove(args, i)
  5476. continue
  5477. endif
  5478. let i += 1
  5479. endwhile
  5480. if handle < 0 ? !quiet : !handle
  5481. return {}
  5482. endif
  5483. call fugitive#Autowrite()
  5484. let listnr = get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2
  5485. if s:HasOpt(args, '--no-line-number')
  5486. let lc = []
  5487. else
  5488. let lc = fugitive#GitVersion(2, 19) ? ['-n', '--column'] : ['-n']
  5489. endif
  5490. let cmd = ['grep', '--no-color', '--full-name'] + lc
  5491. let dir = s:Dir(a:options)
  5492. let options = s:GrepOptions(lc + args, dir)
  5493. if listnr > 0
  5494. exe listnr 'wincmd w'
  5495. else
  5496. call s:BlurStatus()
  5497. endif
  5498. let title = (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)
  5499. call s:QuickfixCreate(listnr, {'title': title})
  5500. let tempfile = tempname()
  5501. let state = {
  5502. \ 'git': a:options.git,
  5503. \ 'flags': a:options.flags,
  5504. \ 'args': cmd + args,
  5505. \ 'git_dir': s:GitDir(a:options),
  5506. \ 'cwd': s:UserCommandCwd(a:options),
  5507. \ 'filetype': 'git',
  5508. \ 'mods': s:Mods(a:mods),
  5509. \ 'file': s:Resolve(tempfile)}
  5510. let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
  5511. exe s:DoAutocmd('QuickFixCmdPre ' . event)
  5512. try
  5513. if !quiet && &more
  5514. let more = 1
  5515. set nomore
  5516. endif
  5517. if !quiet
  5518. echo title
  5519. endif
  5520. let list = s:SystemList(s:UserCommandList(a:options) + cmd + args)[0]
  5521. call writefile(list + [''], tempfile, 'b')
  5522. call s:RunSave(state)
  5523. call map(list, 's:GrepParseLine(options, ' . quiet . ', dir, v:val)')
  5524. call s:QuickfixSet(listnr, list, 'a')
  5525. let press_enter_shortfall = &cmdheight - len(list)
  5526. if press_enter_shortfall > 0 && !quiet
  5527. echo repeat("\n", press_enter_shortfall - 1)
  5528. endif
  5529. finally
  5530. if exists('l:more')
  5531. let &more = more
  5532. endif
  5533. endtry
  5534. call s:RunFinished(state)
  5535. exe s:DoAutocmd('QuickFixCmdPost ' . event)
  5536. if quiet
  5537. let bufnr = bufnr('')
  5538. exe s:QuickfixOpen(listnr, a:mods)
  5539. if bufnr != bufnr('') && !a:bang
  5540. wincmd p
  5541. endif
  5542. end
  5543. if !a:bang && !empty(list)
  5544. return 'silent ' . (listnr < 0 ? 'c' : 'l').'first'
  5545. else
  5546. return ''
  5547. endif
  5548. endfunction
  5549. function! fugitive#GrepCommand(line1, line2, range, bang, mods, arg) abort
  5550. return fugitive#Command(a:line1, a:line2, a:range, a:bang, a:mods,
  5551. \ "grep -O " . a:arg)
  5552. endfunction
  5553. 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}'
  5554. function! s:LogFlushQueue(state, dir) abort
  5555. let queue = remove(a:state, 'queue')
  5556. if a:state.child_found && get(a:state, 'ignore_commit')
  5557. call remove(queue, 0)
  5558. elseif len(queue) && len(a:state.target) && len(get(a:state, 'parents', []))
  5559. let from = substitute(a:state.target, '^/', ':', '')
  5560. let offsets = []
  5561. let queue[0].context.diff = map(copy(a:state.parents), s:log_diff_context)
  5562. endif
  5563. if len(queue) && queue[-1] ==# {'text': ''}
  5564. call remove(queue, -1)
  5565. endif
  5566. return queue
  5567. endfunction
  5568. function! s:LogParse(state, dir, prefix, line) abort
  5569. if a:state.mode ==# 'hunk' && a:line =~# '^[-+ ]'
  5570. return []
  5571. endif
  5572. let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
  5573. if len(list)
  5574. let queue = s:LogFlushQueue(a:state, a:dir)
  5575. let a:state.mode = 'commit'
  5576. let a:state.base = a:prefix . list[2]
  5577. if len(list[1])
  5578. let [a:state.base_module; a:state.parents] = split(list[1], ' ')
  5579. else
  5580. let a:state.base_module = list[2]
  5581. let a:state.parents = []
  5582. endif
  5583. let a:state.message = list[3]
  5584. let a:state.from = ''
  5585. let a:state.to = ''
  5586. let context = {}
  5587. let a:state.queue = [{
  5588. \ 'valid': 1,
  5589. \ 'context': context,
  5590. \ 'filename': s:PathJoin(a:state.base, a:state.target),
  5591. \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
  5592. \ 'text': a:state.message}]
  5593. let a:state.child_found = 0
  5594. return queue
  5595. elseif type(a:line) == type(0)
  5596. return s:LogFlushQueue(a:state, a:dir)
  5597. elseif a:line =~# '^diff'
  5598. let a:state.mode = 'diffhead'
  5599. let a:state.from = ''
  5600. let a:state.to = ''
  5601. elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- \w/'
  5602. let a:state.from = a:line[6:-1]
  5603. let a:state.to = a:state.from
  5604. elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ \w/'
  5605. let a:state.to = a:line[6:-1]
  5606. if empty(get(a:state, 'from', ''))
  5607. let a:state.from = a:state.to
  5608. endif
  5609. elseif a:line =~# '^@@[^@]*+\d' && len(get(a:state, 'to', '')) && has_key(a:state, 'base')
  5610. let a:state.mode = 'hunk'
  5611. if empty(a:state.target) || a:state.target ==# '/' . a:state.to
  5612. if !a:state.child_found && len(a:state.queue) && a:state.queue[-1] ==# {'text': ''}
  5613. call remove(a:state.queue, -1)
  5614. endif
  5615. let a:state.child_found = 1
  5616. let offsets = map(split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' '), '+matchstr(v:val, "\\d\\+")')
  5617. let context = {}
  5618. if len(a:state.parents)
  5619. let from = ":" . a:state.from
  5620. let context.diff = map(copy(a:state.parents), s:log_diff_context)
  5621. endif
  5622. call add(a:state.queue, {
  5623. \ 'valid': 1,
  5624. \ 'context': context,
  5625. \ 'filename': s:VimSlash(a:state.base . '/' . a:state.to),
  5626. \ 'module': a:state.base_module . ':' . a:state.to,
  5627. \ 'lnum': offsets[-1],
  5628. \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
  5629. endif
  5630. elseif a:state.follow &&
  5631. \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
  5632. let rename = matchstr(a:line, '^ \%(copy\|rename\) \zs.* => .*\ze (\d\+%)$')
  5633. if len(rename)
  5634. let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
  5635. if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
  5636. let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
  5637. endif
  5638. endif
  5639. if !get(a:state, 'ignore_summary')
  5640. call add(a:state.queue, {'text': a:line})
  5641. endif
  5642. elseif a:state.mode ==# 'commit' || a:state.mode ==# 'init'
  5643. call add(a:state.queue, {'text': a:line})
  5644. endif
  5645. return []
  5646. endfunction
  5647. function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
  5648. exe s:VersionCheck()
  5649. let dir = s:Dir()
  5650. exe s:DirCheck(dir)
  5651. let listnr = a:type =~# '^l' ? 0 : -1
  5652. let [args, after] = s:SplitExpandChain('log ' . a:args, s:Tree(dir))
  5653. call remove(args, 0)
  5654. let split = index(args, '--')
  5655. if split > 0
  5656. let paths = args[split : -1]
  5657. let args = args[0 : split - 1]
  5658. elseif split == 0
  5659. let paths = args
  5660. let args = []
  5661. else
  5662. let paths = []
  5663. endif
  5664. if a:line1 == 0 && a:count
  5665. let path = fugitive#Path(bufname(a:count), '/', dir)
  5666. let titlepre = ':0,' . a:count
  5667. elseif a:count >= 0
  5668. let path = fugitive#Path(@%, '/', dir)
  5669. let titlepre = a:count == 0 ? ':0,' . bufnr('') : ':'
  5670. else
  5671. let titlepre = ':'
  5672. let path = ''
  5673. endif
  5674. let range = ''
  5675. let extra_args = []
  5676. let extra_paths = []
  5677. let state = {'mode': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
  5678. if path =~# '^/\.git\%(/\|$\)\|^$'
  5679. let path = ''
  5680. elseif a:line1 == 0
  5681. let range = "0," . (a:count ? a:count : bufnr(''))
  5682. let extra_paths = ['.' . path]
  5683. if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
  5684. let state.follow = 1
  5685. if !s:HasOpt(args, '--follow')
  5686. call insert(extra_args, '--follow')
  5687. endif
  5688. if !s:HasOpt(args, '--summary')
  5689. call insert(extra_args, '--summary')
  5690. let state.ignore_summary = 1
  5691. endif
  5692. endif
  5693. let state.ignore_commit = 1
  5694. elseif a:count > 0
  5695. if !s:HasOpt(args, '--merges', '--no-merges')
  5696. call insert(extra_args, '--no-merges')
  5697. endif
  5698. call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
  5699. let state.ignore_commit = 1
  5700. endif
  5701. if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
  5702. let owner = s:Owner(@%, dir)
  5703. if len(owner)
  5704. call add(args, owner . (owner =~# '^\x\{40,}' ? '' : '^{}'))
  5705. endif
  5706. endif
  5707. if empty(extra_paths)
  5708. let path = ''
  5709. endif
  5710. if s:HasOpt(args, '-g', '--walk-reflogs')
  5711. let format = "%gd %P\t%H %gs"
  5712. else
  5713. let format = "%h %P\t%H " . g:fugitive_summary_format
  5714. endif
  5715. let cmd = ['--no-pager']
  5716. call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'] +
  5717. \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
  5718. \ args + extra_args + paths + extra_paths)
  5719. let state.target = path
  5720. let title = titlepre . (listnr < 0 ? 'Gclog ' : 'Gllog ') . s:fnameescape(args + paths)
  5721. return s:QuickfixStream(listnr, 'log', title, s:UserCommandList(dir) + cmd, !a:bang, a:mods, s:function('s:LogParse'), state, dir, s:DirUrlPrefix(dir)) . after
  5722. endfunction
  5723. " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
  5724. function! s:UsableWin(nr) abort
  5725. return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
  5726. \ !getwinvar(a:nr, '&winfixbuf') &&
  5727. \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
  5728. \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
  5729. \ index(['nofile','help','quickfix', 'terminal'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
  5730. endfunction
  5731. function! s:ArgSplit(string) abort
  5732. let string = a:string
  5733. let args = []
  5734. while string =~# '\S'
  5735. let arg = matchstr(string, '^\s*\%(\\.\|\S\)\+')
  5736. let string = strpart(string, len(arg))
  5737. let arg = substitute(arg, '^\s\+', '', '')
  5738. call add(args, substitute(arg, '\\\+[|" ]', '\=submatch(0)[len(submatch(0))/2 : -1]', 'g'))
  5739. endwhile
  5740. return args
  5741. endfunction
  5742. function! s:PlusEscape(string) abort
  5743. return substitute(a:string, '\\*[|" ]', '\=repeat("\\", len(submatch(0))).submatch(0)', 'g')
  5744. endfunction
  5745. function! s:OpenParse(string, wants_cmd, wants_multiple) abort
  5746. let opts = []
  5747. let cmds = []
  5748. let args = s:ArgSplit(a:string)
  5749. while !empty(args)
  5750. if args[0] =~# '^++'
  5751. call add(opts, ' ' . s:PlusEscape(remove(args, 0)))
  5752. elseif a:wants_cmd && args[0] ==# '+'
  5753. call remove(args, 0)
  5754. call add(cmds, '$')
  5755. elseif a:wants_cmd && args[0] =~# '^+'
  5756. call add(cmds, remove(args, 0)[1:-1])
  5757. else
  5758. break
  5759. endif
  5760. endwhile
  5761. if !a:wants_multiple && empty(args)
  5762. let args = ['>:']
  5763. endif
  5764. let dir = s:Dir()
  5765. let wants_cmd = a:wants_cmd
  5766. let urls = []
  5767. for arg in args
  5768. let [url, lnum] = s:OpenExpand(dir, arg, wants_cmd)
  5769. if lnum
  5770. call insert(cmds, lnum)
  5771. endif
  5772. call add(urls, url)
  5773. let wants_cmd = 0
  5774. endfor
  5775. let pre = join(opts, '')
  5776. if len(cmds) > 1
  5777. let pre .= ' +' . s:PlusEscape(join(map(cmds, '"exe ".string(v:val)'), '|'))
  5778. elseif len(cmds)
  5779. let pre .= ' +' . s:PlusEscape(cmds[0])
  5780. endif
  5781. return [a:wants_multiple ? urls : urls[0], pre]
  5782. endfunction
  5783. function! s:OpenExpand(dir, file, wants_cmd) abort
  5784. if a:file ==# '-'
  5785. let result = fugitive#Result()
  5786. if has_key(result, 'file')
  5787. let efile = result.file
  5788. else
  5789. throw 'fugitive: no previous command output'
  5790. endif
  5791. else
  5792. let efile = s:Expand(a:file)
  5793. endif
  5794. if efile =~# '^https\=://'
  5795. let [url, lnum] = s:ResolveUrl(efile, a:dir)
  5796. return [url, a:wants_cmd ? lnum : 0]
  5797. endif
  5798. let url = s:Generate(efile, a:dir)
  5799. if a:wants_cmd && a:file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
  5800. let line = line('.')
  5801. if s:Slash(expand('%:p')) !=# s:Slash(url)
  5802. let diffcmd = 'diff'
  5803. let from = s:DirRev(@%)[1]
  5804. let to = s:DirRev(url)[1]
  5805. if empty(from) && empty(to)
  5806. let diffcmd = 'diff-files'
  5807. let args = ['--', expand('%:p'), url]
  5808. elseif empty(to)
  5809. let args = [from, '--', url]
  5810. elseif empty(from)
  5811. let args = [to, '--', expand('%:p')]
  5812. let reverse = 1
  5813. else
  5814. let args = [from, to]
  5815. endif
  5816. let [res, exec_error] = s:LinesError([a:dir, diffcmd, '-U0'] + args)
  5817. if !exec_error
  5818. call filter(res, 'v:val =~# "^@@ "')
  5819. call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
  5820. call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
  5821. if exists('reverse')
  5822. call map(res, 'v:val[2:3] + v:val[0:1]')
  5823. endif
  5824. call filter(res, 'v:val[0] < '.line('.'))
  5825. let hunk = get(res, -1, [0,0,0,0])
  5826. if hunk[0] + hunk[1] > line('.')
  5827. let line = hunk[2] + max([1 - hunk[3], 0])
  5828. else
  5829. let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
  5830. endif
  5831. endif
  5832. endif
  5833. return [url, line]
  5834. endif
  5835. return [url, 0]
  5836. endfunction
  5837. function! fugitive#DiffClose() abort
  5838. let mywinnr = winnr()
  5839. for winnr in [winnr('#')] + range(winnr('$'),1,-1)
  5840. if winnr != mywinnr && getwinvar(winnr,'&diff')
  5841. execute winnr.'wincmd w'
  5842. close
  5843. if winnr('$') > 1
  5844. wincmd p
  5845. endif
  5846. endif
  5847. endfor
  5848. diffoff!
  5849. endfunction
  5850. function! s:BlurStatus() abort
  5851. if (&previewwindow || getwinvar(winnr(), '&winfixbuf') is# 1 || exists('w:fugitive_status')) && get(b:, 'fugitive_type', '') ==# 'index'
  5852. let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
  5853. if len(winnrs)
  5854. exe winnrs[0].'wincmd w'
  5855. else
  5856. belowright new +setl\ bufhidden=delete
  5857. endif
  5858. if &diff
  5859. call fugitive#DiffClose()
  5860. endif
  5861. endif
  5862. endfunction
  5863. let s:bang_edits = {'split': 'Git', 'vsplit': 'vertical Git', 'tabedit': 'tab Git', 'pedit': 'Git!'}
  5864. function! fugitive#Open(cmd, bang, mods, arg, ...) abort
  5865. exe s:VersionCheck()
  5866. if a:bang
  5867. return 'echoerr ' . string(':G' . a:cmd . '! for temp buffer output has been replaced by :' . get(s:bang_edits, a:cmd, 'Git') . ' --paginate')
  5868. endif
  5869. try
  5870. let [file, pre] = s:OpenParse(a:arg, 1, 0)
  5871. catch /^fugitive:/
  5872. return 'echoerr ' . string(v:exception)
  5873. endtry
  5874. let mods = s:Mods(a:mods)
  5875. if a:cmd ==# 'edit'
  5876. call s:BlurStatus()
  5877. endif
  5878. return mods . a:cmd . pre . ' ' . s:fnameescape(file)
  5879. endfunction
  5880. function! fugitive#DropCommand(line1, count, range, bang, mods, arg, ...) abort
  5881. exe s:VersionCheck()
  5882. let mods = s:Mods(a:mods)
  5883. try
  5884. let [files, pre] = s:OpenParse(a:arg, 1, 1)
  5885. catch /^fugitive:/
  5886. return 'echoerr ' . string(v:exception)
  5887. endtry
  5888. if empty(files)
  5889. return 'drop'
  5890. endif
  5891. call s:BlurStatus()
  5892. return mods . 'drop' . ' ' . s:fnameescape(files) . substitute(pre, '^ *+', '|', '')
  5893. endfunction
  5894. function! s:ReadPrepare(line1, count, range, mods) abort
  5895. let mods = s:Mods(a:mods)
  5896. let after = a:count
  5897. if a:count < 0
  5898. let delete = 'silent 1,' . line('$') . 'delete_|'
  5899. let after = line('$')
  5900. elseif a:range == 2
  5901. let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
  5902. else
  5903. let delete = ''
  5904. endif
  5905. if foldlevel(after)
  5906. let pre = after . 'foldopen!|'
  5907. else
  5908. let pre = ''
  5909. endif
  5910. return [pre . 'keepalt ' . mods . after . 'read', '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')]
  5911. endfunction
  5912. function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, ...) abort
  5913. exe s:VersionCheck()
  5914. let [read, post] = s:ReadPrepare(a:line1, a:count, a:range, a:mods)
  5915. try
  5916. let [file, pre] = s:OpenParse(a:arg, 0, 0)
  5917. catch /^fugitive:/
  5918. return 'echoerr ' . string(v:exception)
  5919. endtry
  5920. if file =~# '^fugitive:' && a:count is# 0
  5921. return 'exe ' .string('keepalt ' . s:Mods(a:mods) . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
  5922. endif
  5923. return read . ' ' . pre . ' ' . s:fnameescape(file) . post
  5924. endfunction
  5925. function! fugitive#EditComplete(A, L, P) abort
  5926. if a:A =~# '^>'
  5927. return map(s:FilterEscape(s:CompleteHeads(s:Dir()), a:A[1:-1]), "'>' . v:val")
  5928. else
  5929. return fugitive#CompleteObject(a:A, a:L, a:P)
  5930. endif
  5931. endfunction
  5932. function! fugitive#ReadComplete(A, L, P) abort
  5933. return fugitive#EditComplete(a:A, a:L, a:P)
  5934. endfunction
  5935. " Section: :Gwrite, :Gwq
  5936. function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, ...) abort
  5937. exe s:VersionCheck()
  5938. if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG')) && empty(a:arg)
  5939. return (empty($GIT_INDEX_FILE) ? 'write|bdelete' : 'wq') . (a:bang ? '!' : '')
  5940. elseif get(b:, 'fugitive_type', '') ==# 'index' && empty(a:arg)
  5941. return 'Git commit'
  5942. elseif &buftype ==# 'nowrite' && getline(4) =~# '^[+-]\{3\} '
  5943. return 'echoerr ' . string('fugitive: :Gwrite from :Git diff has been removed in favor of :Git add --edit')
  5944. endif
  5945. let mytab = tabpagenr()
  5946. let mybufnr = bufnr('')
  5947. let args = s:ArgSplit(a:arg)
  5948. let after = ''
  5949. if get(args, 0) =~# '^+'
  5950. let after = '|' . remove(args, 0)[1:-1]
  5951. endif
  5952. try
  5953. let file = len(args) ? s:Generate(s:Expand(join(args, ' '))) : fugitive#Real(@%)
  5954. catch /^fugitive:/
  5955. return 'echoerr ' . string(v:exception)
  5956. endtry
  5957. if empty(file)
  5958. return 'echoerr '.string('fugitive: cannot determine file path')
  5959. endif
  5960. if file =~# '^fugitive:'
  5961. return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
  5962. endif
  5963. exe s:DirCheck()
  5964. let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
  5965. if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
  5966. let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
  5967. return 'echoerr v:errmsg'
  5968. endif
  5969. let treebufnr = 0
  5970. for nr in range(1,bufnr('$'))
  5971. if fnamemodify(bufname(nr),':p') ==# file
  5972. let treebufnr = nr
  5973. endif
  5974. endfor
  5975. if treebufnr > 0 && treebufnr != bufnr('')
  5976. let temp = tempname()
  5977. silent execute 'keepalt %write '.temp
  5978. for tab in [mytab] + range(1,tabpagenr('$'))
  5979. for winnr in range(1,tabpagewinnr(tab,'$'))
  5980. if tabpagebuflist(tab)[winnr-1] == treebufnr
  5981. execute 'tabnext '.tab
  5982. if winnr != winnr()
  5983. execute winnr.'wincmd w'
  5984. let restorewinnr = 1
  5985. endif
  5986. try
  5987. let lnum = line('.')
  5988. let last = line('$')
  5989. silent execute '$read '.temp
  5990. silent execute '1,'.last.'delete_'
  5991. silent write!
  5992. silent execute lnum
  5993. diffupdate
  5994. let did = 1
  5995. finally
  5996. if exists('restorewinnr')
  5997. wincmd p
  5998. endif
  5999. execute 'tabnext '.mytab
  6000. endtry
  6001. break
  6002. endif
  6003. endfor
  6004. endfor
  6005. if !exists('did')
  6006. call writefile(readfile(temp,'b'),file,'b')
  6007. endif
  6008. else
  6009. execute 'write! '.s:fnameescape(file)
  6010. endif
  6011. let message = s:ChompStderr(['add'] + (a:bang ? ['--force'] : []) + ['--', file])
  6012. if len(message)
  6013. let v:errmsg = 'fugitive: '.message
  6014. return 'echoerr v:errmsg'
  6015. endif
  6016. if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
  6017. setlocal nomodified
  6018. endif
  6019. let one = fugitive#Find(':1:'.file)
  6020. let two = fugitive#Find(':2:'.file)
  6021. let three = fugitive#Find(':3:'.file)
  6022. for nr in range(1,bufnr('$'))
  6023. let name = fnamemodify(bufname(nr), ':p')
  6024. if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
  6025. execute nr.'bdelete'
  6026. endif
  6027. endfor
  6028. unlet! restorewinnr
  6029. let zero = fugitive#Find(':0:'.file)
  6030. exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
  6031. for tab in range(1,tabpagenr('$'))
  6032. for winnr in range(1,tabpagewinnr(tab,'$'))
  6033. let bufnr = tabpagebuflist(tab)[winnr-1]
  6034. let bufname = fnamemodify(bufname(bufnr), ':p')
  6035. if bufname ==# zero && bufnr != mybufnr
  6036. execute 'tabnext '.tab
  6037. if winnr != winnr()
  6038. execute winnr.'wincmd w'
  6039. let restorewinnr = 1
  6040. endif
  6041. try
  6042. let lnum = line('.')
  6043. let last = line('$')
  6044. silent execute '$read '.s:fnameescape(file)
  6045. silent execute '1,'.last.'delete_'
  6046. silent execute lnum
  6047. setlocal nomodified
  6048. diffupdate
  6049. finally
  6050. if exists('restorewinnr')
  6051. wincmd p
  6052. endif
  6053. execute 'tabnext '.mytab
  6054. endtry
  6055. break
  6056. endif
  6057. endfor
  6058. endfor
  6059. call fugitive#DidChange()
  6060. return 'checktime' . after
  6061. endfunction
  6062. function! fugitive#WqCommand(...) abort
  6063. let bang = a:4 ? '!' : ''
  6064. if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG'))
  6065. return 'wq'.bang
  6066. endif
  6067. let result = call('fugitive#WriteCommand', a:000)
  6068. if result =~# '^\%(write\|wq\|echoerr\)'
  6069. return s:sub(result,'^write','wq')
  6070. else
  6071. return result.'|quit'.bang
  6072. endif
  6073. endfunction
  6074. " Section: :Git push, :Git fetch
  6075. function! s:CompletePush(A, L, P, ...) abort
  6076. let dir = a:0 ? a:1 : s:Dir()
  6077. let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
  6078. if empty(remote)
  6079. let matches = s:LinesError([dir, 'remote'])[0]
  6080. elseif a:A =~# ':'
  6081. let lead = matchstr(a:A, '^[^:]*:')
  6082. let matches = s:LinesError([dir, 'ls-remote', remote])[0]
  6083. call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
  6084. call map(matches, 'lead . s:sub(v:val, "^.*\t", "")')
  6085. else
  6086. let matches = s:CompleteHeads(dir)
  6087. if a:A =~# '^[\''"]\=+'
  6088. call map(matches, '"+" . v:val')
  6089. endif
  6090. endif
  6091. return s:FilterEscape(matches, a:A)
  6092. endfunction
  6093. function! fugitive#PushComplete(A, L, P, ...) abort
  6094. return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompletePush'), a:000)
  6095. endfunction
  6096. function! fugitive#FetchComplete(A, L, P, ...) abort
  6097. return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
  6098. endfunction
  6099. function! s:PushSubcommand(...) abort
  6100. return {'no_more': 1}
  6101. endfunction
  6102. function! s:FetchSubcommand(...) abort
  6103. return {'no_more': 1}
  6104. endfunction
  6105. " Section: :Gdiff
  6106. augroup fugitive_diff
  6107. autocmd!
  6108. autocmd BufWinLeave * nested
  6109. \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
  6110. \ call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
  6111. \ endif
  6112. autocmd BufWinEnter * nested
  6113. \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
  6114. \ call s:diffoff() |
  6115. \ endif
  6116. augroup END
  6117. function! s:can_diffoff(buf) abort
  6118. return getwinvar(bufwinnr(a:buf), '&diff') &&
  6119. \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
  6120. endfunction
  6121. function! fugitive#CanDiffoff(buf) abort
  6122. return s:can_diffoff(bufnr(a:buf))
  6123. endfunction
  6124. function! s:DiffModifier(count, default) abort
  6125. let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
  6126. if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
  6127. return ''
  6128. elseif &diffopt =~# 'vertical'
  6129. return 'vertical '
  6130. elseif !get(g:, 'fugitive_diffsplit_directional_fit', a:default)
  6131. return ''
  6132. elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
  6133. return ''
  6134. else
  6135. return 'vertical '
  6136. endif
  6137. endfunction
  6138. function! s:diff_window_count() abort
  6139. let c = 0
  6140. for nr in range(1,winnr('$'))
  6141. let c += getwinvar(nr,'&diff')
  6142. endfor
  6143. return c
  6144. endfunction
  6145. function! s:diffthis() abort
  6146. if !&diff
  6147. let w:fugitive_diff_restore = 1
  6148. diffthis
  6149. endif
  6150. endfunction
  6151. function! s:diffoff() abort
  6152. unlet! w:fugitive_diff_restore
  6153. diffoff
  6154. endfunction
  6155. function! s:diffoff_all(dir) abort
  6156. let curwin = winnr()
  6157. for nr in range(1,winnr('$'))
  6158. if getwinvar(nr, '&diff') && !empty(getwinvar(nr, 'fugitive_diff_restore'))
  6159. call setwinvar(nr, 'fugitive_diff_restore', '')
  6160. endif
  6161. endfor
  6162. if curwin != winnr()
  6163. execute curwin.'wincmd w'
  6164. endif
  6165. diffoff!
  6166. endfunction
  6167. function! s:IsConflicted() abort
  6168. return len(@%) && !empty(s:ChompDefault('', ['ls-files', '--unmerged', '--', expand('%:p')]))
  6169. endfunction
  6170. function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, ...) abort
  6171. exe s:VersionCheck()
  6172. let args = s:ArgSplit(a:arg)
  6173. let post = ''
  6174. let autodir = a:autodir
  6175. while get(args, 0, '') =~# '^++'
  6176. if args[0] =~? '^++novertical$'
  6177. let autodir = 0
  6178. else
  6179. return 'echoerr ' . string('fugitive: unknown option ' . args[0])
  6180. endif
  6181. call remove(args, 0)
  6182. endwhile
  6183. if get(args, 0) =~# '^+'
  6184. let post = remove(args, 0)[1:-1]
  6185. endif
  6186. if exists(':DiffGitCached') && empty(args)
  6187. return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
  6188. endif
  6189. let commit = s:DirCommitFile(@%)[1]
  6190. if a:mods =~# '\<\d*tab\>'
  6191. let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
  6192. let pre = matchstr(a:mods, '\<\d*tab\>') . ' split'
  6193. else
  6194. let mods = 'keepalt ' . a:mods
  6195. let pre = ''
  6196. endif
  6197. let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
  6198. if (empty(args) || args[0] =~# '^>\=:$') && a:keepfocus
  6199. exe s:DirCheck()
  6200. if commit =~# '^1\=$' && s:IsConflicted()
  6201. let parents = [s:Relative(':2:'), s:Relative(':3:')]
  6202. elseif empty(commit)
  6203. let parents = [s:Relative(':0:')]
  6204. elseif commit =~# '^\d\=$'
  6205. let parents = [s:Relative('@:')]
  6206. elseif commit =~# '^\x\x\+$'
  6207. let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
  6208. call map(parents, 's:Relative(v:val . ":")')
  6209. endif
  6210. endif
  6211. try
  6212. if exists('parents') && len(parents) > 1
  6213. exe pre
  6214. let mods = (autodir ? s:DiffModifier(len(parents) + 1, empty(args) || args[0] =~# '^>') : '') . s:Mods(mods, 'leftabove')
  6215. let nr = bufnr('')
  6216. if len(parents) > 1 && !&equalalways
  6217. let equalalways = 0
  6218. set equalalways
  6219. endif
  6220. execute mods 'split' s:fnameescape(fugitive#Find(parents[0]))
  6221. call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
  6222. let nr2 = bufnr('')
  6223. call s:diffthis()
  6224. exe back
  6225. call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
  6226. let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
  6227. for i in range(len(parents)-1, 1, -1)
  6228. execute mods 'split' s:fnameescape(fugitive#Find(parents[i]))
  6229. call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
  6230. let nrx = bufnr('')
  6231. call s:diffthis()
  6232. exe back
  6233. call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
  6234. endfor
  6235. call s:diffthis()
  6236. return post
  6237. elseif len(args)
  6238. let arg = join(args, ' ')
  6239. if arg ==# ''
  6240. return post
  6241. elseif arg ==# ':/'
  6242. exe s:DirCheck()
  6243. let file = s:Relative()
  6244. elseif arg ==# ':'
  6245. exe s:DirCheck()
  6246. let file = len(commit) ? s:Relative() : s:Relative(s:IsConflicted() ? ':1:' : ':0:')
  6247. elseif arg =~# '^:\d$'
  6248. exe s:DirCheck()
  6249. let file = s:Relative(arg . ':')
  6250. elseif arg =~# '^[~^]\d*$'
  6251. return 'echoerr ' . string('fugitive: change ' . arg . ' to !' . arg . ' to diff against ancestor')
  6252. else
  6253. try
  6254. let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
  6255. catch /^fugitive:/
  6256. return 'echoerr ' . string(v:exception)
  6257. endtry
  6258. endif
  6259. if a:keepfocus || arg =~# '^>'
  6260. let mods = s:Mods(a:mods, 'leftabove')
  6261. else
  6262. let mods = s:Mods(a:mods)
  6263. endif
  6264. elseif exists('parents')
  6265. let file = get(parents, -1, s:Relative(repeat('0', 40). ':'))
  6266. let mods = s:Mods(a:mods, 'leftabove')
  6267. elseif len(commit)
  6268. let file = s:Relative()
  6269. let mods = s:Mods(a:mods, 'rightbelow')
  6270. elseif s:IsConflicted()
  6271. let file = s:Relative(':1:')
  6272. let mods = s:Mods(a:mods, 'leftabove')
  6273. if get(g:, 'fugitive_legacy_commands', 1)
  6274. let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
  6275. endif
  6276. else
  6277. exe s:DirCheck()
  6278. let file = s:Relative(':0:')
  6279. let mods = s:Mods(a:mods, 'leftabove')
  6280. endif
  6281. let spec = s:Generate(file)
  6282. if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
  6283. let spec = s:VimSlash(spec . s:Relative('/'))
  6284. endif
  6285. exe pre
  6286. let w:fugitive_diff_restore = 1
  6287. let mods = (autodir ? s:DiffModifier(2, empty(args) || args[0] =~# '^>') : '') . mods
  6288. if &diffopt =~# 'vertical'
  6289. let diffopt = &diffopt
  6290. set diffopt-=vertical
  6291. endif
  6292. execute mods 'diffsplit' s:fnameescape(spec)
  6293. let w:fugitive_diff_restore = 1
  6294. let winnr = winnr()
  6295. if getwinvar('#', '&diff')
  6296. if a:keepfocus
  6297. exe back
  6298. endif
  6299. endif
  6300. return post
  6301. catch /^fugitive:/
  6302. return 'echoerr ' . string(v:exception)
  6303. finally
  6304. if exists('l:equalalways')
  6305. let &g:equalalways = equalalways
  6306. endif
  6307. if exists('diffopt')
  6308. let &diffopt = diffopt
  6309. endif
  6310. endtry
  6311. endfunction
  6312. " Section: :GMove, :GRemove
  6313. function! s:Move(force, rename, destination) abort
  6314. exe s:VersionCheck()
  6315. let dir = s:Dir()
  6316. exe s:DirCheck(dir)
  6317. if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
  6318. return 'echoerr ' . string('fugitive: mv not supported for this buffer')
  6319. endif
  6320. if a:rename
  6321. let default_root = expand('%:p:s?[\/]$??:h') . '/'
  6322. else
  6323. let default_root = s:Tree(dir) . '/'
  6324. endif
  6325. if a:destination =~# '^:/:\='
  6326. let destination = s:Tree(dir) . s:Expand(substitute(a:destination, '^:/:\=', '', ''))
  6327. elseif a:destination =~# '^:(top)'
  6328. let destination = s:Expand(matchstr(a:destination, ')\zs.*'))
  6329. if destination !~# '^/\|^\a\+:'
  6330. let destination = s:Tree(dir) . '/' . destination
  6331. endif
  6332. let destination = s:Tree(dir) .
  6333. elseif a:destination =~# '^:(\%(top,literal\|literal,top\))'
  6334. let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
  6335. elseif a:destination =~# '^:(literal)\.\.\=\%(/\|$\)'
  6336. let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
  6337. elseif a:destination =~# '^:(literal)'
  6338. let destination = simplify(default_root . matchstr(a:destination, ')\zs.*'))
  6339. else
  6340. let destination = s:Expand(a:destination)
  6341. if destination =~# '^\.\.\=\%(/\|$\)'
  6342. let destination = simplify(getcwd() . '/' . destination)
  6343. elseif destination !~# '^\a\+:\|^/'
  6344. let destination = default_root . destination
  6345. endif
  6346. endif
  6347. let destination = s:Slash(destination)
  6348. if isdirectory(@%)
  6349. setlocal noswapfile
  6350. endif
  6351. let exec = fugitive#Execute(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
  6352. if exec.exit_status && exec.stderr !=# ['']
  6353. return 'echoerr ' .string('fugitive: '.s:JoinChomp(exec.stderr))
  6354. endif
  6355. if isdirectory(destination)
  6356. let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
  6357. endif
  6358. let reload = '|call fugitive#DidChange(' . string(exec) . ')'
  6359. if empty(s:DirCommitFile(@%)[1])
  6360. if isdirectory(destination)
  6361. return 'keepalt edit '.s:fnameescape(destination) . reload
  6362. else
  6363. return 'keepalt saveas! '.s:fnameescape(destination) . reload
  6364. endif
  6365. else
  6366. return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
  6367. endif
  6368. endfunction
  6369. function! fugitive#RenameComplete(A,L,P) abort
  6370. if a:A =~# '^[.:]\=/'
  6371. return fugitive#CompletePath(a:A)
  6372. else
  6373. let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
  6374. return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
  6375. endif
  6376. endfunction
  6377. function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, ...) abort
  6378. return s:Move(a:bang, 0, a:arg)
  6379. endfunction
  6380. function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, ...) abort
  6381. return s:Move(a:bang, 1, a:arg)
  6382. endfunction
  6383. function! s:Remove(after, force) abort
  6384. exe s:VersionCheck()
  6385. let dir = s:Dir()
  6386. exe s:DirCheck(dir)
  6387. if len(@%) && s:DirCommitFile(@%)[1] ==# ''
  6388. let cmd = ['rm']
  6389. elseif s:DirCommitFile(@%)[1] ==# '0'
  6390. let cmd = ['rm','--cached']
  6391. else
  6392. return 'echoerr ' . string('fugitive: rm not supported for this buffer')
  6393. endif
  6394. if a:force
  6395. let cmd += ['--force']
  6396. endif
  6397. let message = s:ChompStderr(cmd + ['--', expand('%:p')], dir)
  6398. if len(message)
  6399. let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
  6400. return 'echoerr '.string(v:errmsg)
  6401. else
  6402. return a:after . (a:force ? '!' : ''). '|call fugitive#DidChange(' . string(dir) . ')'
  6403. endif
  6404. endfunction
  6405. function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, ...) abort
  6406. return s:Remove('edit', a:bang)
  6407. endfunction
  6408. function! fugitive#UnlinkCommand(line1, line2, range, bang, mods, arg, ...) abort
  6409. return s:Remove('edit', a:bang)
  6410. endfunction
  6411. function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, ...) abort
  6412. return s:Remove('bdelete', a:bang)
  6413. endfunction
  6414. " Section: :Git blame
  6415. function! s:Keywordprg() abort
  6416. let args = ' --git-dir=' . escape(FugitiveGitPath(s:GitDir()), "\\\"' ")
  6417. if has('gui_running') && !has('win32')
  6418. return s:GitShellCmd() . ' --no-pager' . args . ' log -1'
  6419. else
  6420. return s:GitShellCmd() . args . ' show'
  6421. endif
  6422. endfunction
  6423. function! s:linechars(pattern) abort
  6424. let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
  6425. if &conceallevel > 1
  6426. for col in range(1, chars)
  6427. let chars -= synconcealed(line('.'), col)[0]
  6428. endfor
  6429. endif
  6430. return chars
  6431. endfunction
  6432. function! s:BlameBufnr(...) abort
  6433. let state = s:TempState(a:0 ? a:1 : bufnr(''))
  6434. if get(state, 'filetype', '') ==# 'fugitiveblame'
  6435. return get(state, 'origin_bufnr', -1)
  6436. else
  6437. return -1
  6438. endif
  6439. endfunction
  6440. function! s:BlameCommitFileLnum(...) abort
  6441. let line = a:0 ? a:1 : getline('.')
  6442. let state = a:0 > 1 ? a:2 : s:TempState()
  6443. if get(state, 'filetype', '') !=# 'fugitiveblame'
  6444. return ['', '', 0]
  6445. endif
  6446. let commit = matchstr(line, '^\^\=[?*]*\zs\x\+')
  6447. if commit =~# '^0\+$'
  6448. let commit = ''
  6449. elseif has_key(state, 'blame_reverse_end')
  6450. let commit = get(s:LinesError([state.git_dir, 'rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end])[0], 0, '')
  6451. endif
  6452. let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
  6453. let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s*\d\+ \%((\| *\d\+)\)')
  6454. if empty(path) && lnum
  6455. let path = get(state, 'blame_file', '')
  6456. endif
  6457. return [commit, path, lnum]
  6458. endfunction
  6459. function! s:BlameLeave() abort
  6460. let state = s:TempState()
  6461. let bufwinnr = exists('*win_id2win') ? win_id2win(get(state, 'origin_winid')) : 0
  6462. if bufwinnr == 0
  6463. let bufwinnr = bufwinnr(get(state, 'origin_bufnr', -1))
  6464. endif
  6465. if get(state, 'filetype', '') ==# 'fugitiveblame' && bufwinnr > 0
  6466. let bufnr = bufnr('')
  6467. exe bufwinnr . 'wincmd w'
  6468. return bufnr . 'bdelete'
  6469. endif
  6470. return ''
  6471. endfunction
  6472. function! s:BlameQuit() abort
  6473. let cmd = s:BlameLeave()
  6474. if empty(cmd)
  6475. return 'bdelete'
  6476. elseif len(s:DirCommitFile(@%)[1])
  6477. return cmd . '|Gedit'
  6478. else
  6479. return cmd
  6480. endif
  6481. endfunction
  6482. function! fugitive#BlameComplete(A, L, P) abort
  6483. return s:CompleteSub('blame', a:A, a:L, a:P)
  6484. endfunction
  6485. function! s:BlameSubcommand(line1, count, range, bang, mods, options) abort
  6486. let dir = s:Dir(a:options)
  6487. exe s:DirCheck(dir)
  6488. let flags = copy(a:options.subcommand_args)
  6489. let i = 0
  6490. let raw = 0
  6491. let commits = []
  6492. let files = []
  6493. let ranges = []
  6494. if a:line1 > 0 && a:count > 0 && a:range != 1
  6495. call extend(ranges, ['-L', a:line1 . ',' . a:count])
  6496. endif
  6497. while i < len(flags)
  6498. let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
  6499. if len(match) && len(match[2])
  6500. call insert(flags, match[1])
  6501. let flags[i+1] = '-' . match[2]
  6502. continue
  6503. endif
  6504. let arg = flags[i]
  6505. if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
  6506. let raw = 1
  6507. elseif arg ==# '--contents' && i + 1 < len(flags)
  6508. call extend(commits, remove(flags, i, i+1))
  6509. continue
  6510. elseif arg ==# '-L' && i + 1 < len(flags)
  6511. call extend(ranges, remove(flags, i, i+1))
  6512. continue
  6513. elseif arg =~# '^--contents='
  6514. call add(commits, remove(flags, i))
  6515. continue
  6516. elseif arg =~# '^-L.'
  6517. call add(ranges, remove(flags, i))
  6518. continue
  6519. elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
  6520. let i += 1
  6521. if i == len(flags)
  6522. echohl ErrorMsg
  6523. echo s:ChompStderr([dir, 'blame', arg])
  6524. echohl NONE
  6525. return ''
  6526. endif
  6527. elseif arg ==# '--'
  6528. if i + 1 < len(flags)
  6529. call extend(files, remove(flags, i + 1, -1))
  6530. endif
  6531. call remove(flags, i)
  6532. break
  6533. elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
  6534. if index(flags, '--') >= 0
  6535. call add(commits, remove(flags, i))
  6536. continue
  6537. endif
  6538. if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
  6539. call add(commits, remove(flags, i))
  6540. continue
  6541. endif
  6542. try
  6543. let dcf = s:DirCommitFile(fugitive#Find(arg, dir))
  6544. if len(dcf[1]) && empty(dcf[2])
  6545. call add(commits, remove(flags, i))
  6546. continue
  6547. endif
  6548. catch /^fugitive:/
  6549. endtry
  6550. call add(files, remove(flags, i))
  6551. continue
  6552. endif
  6553. let i += 1
  6554. endwhile
  6555. let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./', dir))), '^\.\%(/\|$\)', '', '')
  6556. if empty(commits) && len(files) > 1
  6557. call add(commits, remove(files, 1))
  6558. endif
  6559. exe s:BlameLeave()
  6560. try
  6561. let cmd = a:options.flags + ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', a:options.subcommand, '--show-number']
  6562. call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
  6563. if a:count > 0 && empty(ranges)
  6564. let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
  6565. endif
  6566. call extend(cmd, ranges)
  6567. let tempname = tempname()
  6568. let temp = tempname . (raw ? '' : '.fugitiveblame')
  6569. if len(commits)
  6570. let cmd += commits
  6571. elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
  6572. let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
  6573. elseif empty(files) && !s:HasOpt(flags, '--reverse')
  6574. if &modified || !empty(s:DirCommitFile(@%)[1])
  6575. let cmd += ['--contents', tempname . '.in']
  6576. silent execute 'noautocmd keepalt %write ' . s:fnameescape(tempname . '.in')
  6577. let delete_in = 1
  6578. elseif &autoread
  6579. exe 'checktime ' . bufnr('')
  6580. endif
  6581. else
  6582. call fugitive#Autowrite()
  6583. endif
  6584. let basecmd = [{'git': a:options.git}, dir, '--literal-pathspecs'] + cmd + ['--'] + (len(files) ? files : [file])
  6585. let [err, exec_error] = s:StdoutToFile(temp, basecmd)
  6586. if exists('delete_in')
  6587. call delete(tempname . '.in')
  6588. endif
  6589. redraw
  6590. try
  6591. if exec_error
  6592. let lines = split(err, "\n")
  6593. if empty(lines)
  6594. let lines = readfile(temp)
  6595. endif
  6596. for i in range(len(lines))
  6597. if lines[i] =~# '^error: \|^fatal: '
  6598. echohl ErrorMsg
  6599. echon lines[i]
  6600. echohl NONE
  6601. break
  6602. else
  6603. echon lines[i]
  6604. endif
  6605. if i != len(lines) - 1
  6606. echon "\n"
  6607. endif
  6608. endfor
  6609. return ''
  6610. endif
  6611. let temp_state = {
  6612. \ 'git': a:options.git,
  6613. \ 'flags': a:options.flags,
  6614. \ 'args': [a:options.subcommand] + a:options.subcommand_args,
  6615. \ 'git_dir': s:GitDir(a:options),
  6616. \ 'cwd': s:UserCommandCwd(a:options),
  6617. \ 'filetype': (raw ? 'git' : 'fugitiveblame'),
  6618. \ 'blame_options': a:options,
  6619. \ 'blame_flags': flags,
  6620. \ 'blame_file': file}
  6621. if s:HasOpt(flags, '--reverse')
  6622. let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
  6623. endif
  6624. if a:line1 == 0 && a:count == 1
  6625. if get(a:options, 'curwin')
  6626. let edit = 'edit'
  6627. elseif a:bang
  6628. let edit = 'pedit'
  6629. else
  6630. let edit = 'split'
  6631. endif
  6632. return s:BlameCommit(s:Mods(a:mods) . edit, get(readfile(temp), 0, ''), temp_state)
  6633. elseif (a:line1 == 0 || a:range == 1) && a:count > 0
  6634. let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit', 'edit'], a:count - (a:line1 ? a:line1 : 1), 'split')
  6635. return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
  6636. else
  6637. let temp = s:Resolve(temp)
  6638. let temp_state.file = temp
  6639. call s:RunSave(temp_state)
  6640. if len(ranges + commits + files) || raw
  6641. let reload = '|call fugitive#DidChange(fugitive#Result(' . string(temp_state.file) . '))'
  6642. let mods = s:Mods(a:mods)
  6643. if a:count != 0
  6644. exe 'silent keepalt' mods get(a:options, 'curwin') ? 'edit' : 'split' s:fnameescape(temp)
  6645. elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
  6646. exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
  6647. else
  6648. return mods . 'edit ' . s:fnameescape(temp) . reload
  6649. endif
  6650. return reload[1 : -1]
  6651. endif
  6652. let tabmod = matchstr(a:mods, '\<\d*tab\>')
  6653. let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
  6654. if !empty(tabmod)
  6655. silent execute tabmod . 'edit %'
  6656. endif
  6657. let temp_state.origin_bufnr = bufnr('')
  6658. if exists('*win_getid')
  6659. let temp_state.origin_winid = win_getid()
  6660. endif
  6661. let restore = []
  6662. for winnr in range(winnr('$'),1,-1)
  6663. if getwinvar(winnr, '&scrollbind')
  6664. if !&l:scrollbind
  6665. call setwinvar(winnr, '&scrollbind', 0)
  6666. elseif winnr != winnr() && getwinvar(winnr, '&foldenable')
  6667. call setwinvar(winnr, '&foldenable', 0)
  6668. call add(restore, 'call setwinvar(bufwinnr('.winbufnr(winnr).'),"&foldenable",1)')
  6669. endif
  6670. endif
  6671. let win_blame_bufnr = s:BlameBufnr(winbufnr(winnr))
  6672. if getwinvar(winnr, '&scrollbind') ? win_blame_bufnr == temp_state.origin_bufnr : win_blame_bufnr > 0
  6673. execute winbufnr(winnr).'bdelete'
  6674. endif
  6675. endfor
  6676. let restore_winnr = get(temp_state, 'origin_winid', 'bufwinnr(' . temp_state.origin_bufnr . ')')
  6677. if !&l:scrollbind
  6678. call add(restore, 'call setwinvar(' . restore_winnr . ',"&scrollbind",0)')
  6679. endif
  6680. if &l:wrap
  6681. call add(restore, 'call setwinvar(' . restore_winnr . ',"&wrap",1)')
  6682. endif
  6683. if &l:foldenable
  6684. call add(restore, 'call setwinvar(' . restore_winnr . ',"&foldenable",1)')
  6685. endif
  6686. setlocal scrollbind nowrap nofoldenable
  6687. let top = line('w0') + &scrolloff
  6688. let current = line('.')
  6689. exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
  6690. let w:fugitive_leave = join(restore, '|')
  6691. execute top
  6692. normal! zt
  6693. execute current
  6694. setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
  6695. if exists('&winfixbuf')
  6696. setlocal winfixbuf
  6697. endif
  6698. if exists('+relativenumber')
  6699. setlocal norelativenumber
  6700. endif
  6701. if exists('+signcolumn')
  6702. setlocal signcolumn=no
  6703. endif
  6704. execute "vertical resize ".(s:linechars('.\{-\}\s\+\d\+\ze)')+1)
  6705. redraw
  6706. syncbind
  6707. exe s:DoAutocmdChanged(temp_state)
  6708. endif
  6709. endtry
  6710. return ''
  6711. catch /^fugitive:/
  6712. return 'echoerr ' . string(v:exception)
  6713. endtry
  6714. endfunction
  6715. function! s:BlameCommit(cmd, ...) abort
  6716. let line = a:0 ? a:1 : getline('.')
  6717. let state = a:0 ? a:2 : s:TempState()
  6718. let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
  6719. let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
  6720. let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
  6721. if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
  6722. let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
  6723. return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
  6724. endif
  6725. if commit =~# '^0*$'
  6726. return 'echoerr ' . string('fugitive: no commit')
  6727. endif
  6728. if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
  6729. let path = commit . ':' . path
  6730. return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
  6731. endif
  6732. let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
  6733. if cmd =~# '^echoerr'
  6734. return cmd
  6735. endif
  6736. execute cmd
  6737. if a:cmd ==# 'pedit' || empty(path)
  6738. return ''
  6739. endif
  6740. if search('^diff .* b/\M'.escape(path,'\').'$','W')
  6741. call search('^+++')
  6742. let head = line('.')
  6743. while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
  6744. let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
  6745. let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
  6746. if lnum >= top && lnum <= top + len
  6747. let offset = lnum - top
  6748. if &scrolloff
  6749. +
  6750. normal! zt
  6751. else
  6752. normal! zt
  6753. +
  6754. endif
  6755. while offset > 0 && line('.') < line('$')
  6756. +
  6757. if getline('.') =~# '^[ ' . sigil . ']'
  6758. let offset -= 1
  6759. endif
  6760. endwhile
  6761. return 'normal! zv'
  6762. endif
  6763. endwhile
  6764. execute head
  6765. normal! zt
  6766. endif
  6767. return ''
  6768. endfunction
  6769. function! s:BlameJump(suffix, ...) abort
  6770. let suffix = a:suffix
  6771. let [commit, path, lnum] = s:BlameCommitFileLnum()
  6772. if empty(path)
  6773. return 'echoerr ' . string('fugitive: could not determine filename for blame')
  6774. endif
  6775. if commit =~# '^0*$'
  6776. let commit = '@'
  6777. let suffix = ''
  6778. endif
  6779. let offset = line('.') - line('w0')
  6780. let state = s:TempState()
  6781. let flags = get(state, 'blame_flags', [])
  6782. let blame_bufnr = s:BlameBufnr()
  6783. if blame_bufnr > 0
  6784. let bufnr = bufnr('')
  6785. let winnr = bufwinnr(blame_bufnr)
  6786. if winnr > 0
  6787. exe winnr.'wincmd w'
  6788. exe bufnr.'bdelete'
  6789. endif
  6790. execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
  6791. execute lnum
  6792. endif
  6793. let my_bufnr = bufnr('')
  6794. if blame_bufnr < 0
  6795. let blame_args = flags + [commit . suffix, '--', path]
  6796. let result = s:BlameSubcommand(0, 0, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
  6797. else
  6798. let blame_args = flags
  6799. let result = s:BlameSubcommand(-1, -1, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
  6800. endif
  6801. if bufnr('') == my_bufnr
  6802. return result
  6803. endif
  6804. execute result
  6805. execute lnum
  6806. let delta = line('.') - line('w0') - offset
  6807. if delta > 0
  6808. execute 'normal! '.delta."\<C-E>"
  6809. elseif delta < 0
  6810. execute 'normal! '.(-delta)."\<C-Y>"
  6811. endif
  6812. keepjumps syncbind
  6813. redraw
  6814. echo ':Git blame' s:fnameescape(blame_args)
  6815. return ''
  6816. endfunction
  6817. let s:hash_colors = {}
  6818. function! fugitive#BlameSyntax() abort
  6819. let conceal = has('conceal') ? ' conceal' : ''
  6820. let flags = get(s:TempState(), 'blame_flags', [])
  6821. syn spell notoplevel
  6822. syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
  6823. syn match FugitiveblameHash "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
  6824. if s:HasOpt(flags, '-b') || FugitiveConfigGet('blame.blankBoundary') =~# '^1$\|^true$'
  6825. syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
  6826. else
  6827. syn match FugitiveblameBoundary "^\^"
  6828. endif
  6829. syn match FugitiveblameScoreDebug " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
  6830. syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
  6831. syn match FugitiveblameTime "\<[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
  6832. exec 'syn match FugitiveblameLineNumber "\s[[:digit:][:space:]]\{0,' . (len(line('$'))-1). '\}\d)\@=" contained containedin=FugitiveblameAnnotation' conceal
  6833. 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)
  6834. exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
  6835. exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
  6836. syn match FugitiveblameShort " \+\d\+)" contained contains=FugitiveblameLineNumber
  6837. syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
  6838. hi def link FugitiveblameBoundary Keyword
  6839. hi def link FugitiveblameHash Identifier
  6840. hi def link FugitiveblameBoundaryIgnore Ignore
  6841. hi def link FugitiveblameUncommitted Ignore
  6842. hi def link FugitiveblameScoreDebug Debug
  6843. hi def link FugitiveblameTime PreProc
  6844. hi def link FugitiveblameLineNumber Number
  6845. hi def link FugitiveblameOriginalFile String
  6846. hi def link FugitiveblameOriginalLineNumber Float
  6847. hi def link FugitiveblameShort FugitiveblameDelimiter
  6848. hi def link FugitiveblameDelimiter Delimiter
  6849. hi def link FugitiveblameNotCommittedYet Comment
  6850. if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
  6851. return
  6852. endif
  6853. let seen = {}
  6854. for x in split('01234567890abcdef', '\zs')
  6855. exe 'syn match FugitiveblameHashGroup'.x '"\%(^\^\=[*?]*\)\@<='.x.'\x\{5,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
  6856. endfor
  6857. for lnum in range(1, line('$'))
  6858. let orig_hash = matchstr(getline(lnum), '^\^\=[*?]*\zs\x\{6\}')
  6859. let hash = orig_hash
  6860. let hash = substitute(hash, '\(\x\)\x', '\=submatch(1).printf("%x", 15-str2nr(submatch(1),16))', 'g')
  6861. let hash = substitute(hash, '\(\x\x\)', '\=printf("%02x", str2nr(submatch(1),16)*3/4+32)', 'g')
  6862. if hash ==# '' || orig_hash ==# '000000' || has_key(seen, hash)
  6863. continue
  6864. endif
  6865. let seen[hash] = 1
  6866. if &t_Co == 256
  6867. let [s, r, g, b; __] = map(matchlist(orig_hash, '\(\x\)\x\(\x\)\x\(\x\)\x'), 'str2nr(v:val,16)')
  6868. let color = 16 + (r + 1) / 3 * 36 + (g + 1) / 3 * 6 + (b + 1) / 3
  6869. if color == 16
  6870. let color = 235
  6871. elseif color == 231
  6872. let color = 255
  6873. endif
  6874. let s:hash_colors[hash] = ' ctermfg='.color
  6875. else
  6876. let s:hash_colors[hash] = ''
  6877. endif
  6878. let pattern = substitute(orig_hash, '^\(\x\)\x\(\x\)\x\(\x\)\x$', '\1\\x\2\\x\3\\x', '') . '*'
  6879. exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=[*?]*\)\@<='.pattern.'" contained containedin=FugitiveblameHashGroup' . orig_hash[0]
  6880. endfor
  6881. syn match FugitiveblameUncommitted "\%(^\^\=[?*]*\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
  6882. call s:BlameRehighlight()
  6883. endfunction
  6884. function! s:BlameRehighlight() abort
  6885. for [hash, cterm] in items(s:hash_colors)
  6886. if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
  6887. exe 'hi FugitiveblameHash'.hash.' guifg=#' . hash . cterm
  6888. else
  6889. exe 'hi link FugitiveblameHash'.hash.' Identifier'
  6890. endif
  6891. endfor
  6892. endfunction
  6893. function! s:BlameMaps(is_ftplugin) abort
  6894. let ft = a:is_ftplugin
  6895. call s:MapGitOps(ft)
  6896. call s:Map('n', '<F1>', ':help :Git_blame<CR>', '<silent>', ft)
  6897. call s:Map('n', 'g?', ':help :Git_blame<CR>', '<silent>', ft)
  6898. call s:Map('n', 'gq', ':exe <SID>BlameQuit()<CR>', '<silent>', ft)
  6899. call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
  6900. call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
  6901. call s:Map('n', '-', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
  6902. call s:Map('n', 's', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
  6903. call s:Map('n', 'u', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
  6904. call s:Map('n', 'P', ':<C-U>if !v:count<Bar>echoerr "Use ~ (or provide a count)"<Bar>else<Bar>exe <SID>BlameJump("^".v:count1)<Bar>endif<CR>', '<silent>', ft)
  6905. call s:Map('n', '~', ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>', ft)
  6906. call s:Map('n', 'i', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
  6907. call s:Map('n', 'o', ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>', ft)
  6908. call s:Map('n', 'O', ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>', ft)
  6909. call s:Map('n', 'p', ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>', ft)
  6910. exe s:Map('n', '.', ":<C-U> <C-R>=substitute(<SID>BlameCommitFileLnum()[0],'^$','@','')<CR><Home>", '', ft)
  6911. exe s:Map('n', '(', "-", '', ft)
  6912. exe s:Map('n', ')', "+", '', ft)
  6913. call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>', ft)
  6914. call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>', ft)
  6915. call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>', ft)
  6916. endfunction
  6917. function! fugitive#BlameFileType() abort
  6918. setlocal nomodeline
  6919. setlocal foldmethod=manual
  6920. if len(s:GitDir())
  6921. let &l:keywordprg = s:Keywordprg()
  6922. endif
  6923. let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
  6924. if exists('+concealcursor')
  6925. setlocal concealcursor=nc conceallevel=2
  6926. let b:undo_ftplugin .= ' concealcursor< conceallevel<'
  6927. endif
  6928. if &modifiable
  6929. return ''
  6930. endif
  6931. call s:BlameMaps(1)
  6932. endfunction
  6933. function! s:BlameCursorSync(bufnr, line) abort
  6934. if a:line == line('.')
  6935. return
  6936. endif
  6937. if get(s:TempState(), 'origin_bufnr') == a:bufnr || get(s:TempState(a:bufnr), 'origin_bufnr') == bufnr('')
  6938. if &startofline
  6939. execute a:line
  6940. else
  6941. let pos = getpos('.')
  6942. let pos[1] = a:line
  6943. call setpos('.', pos)
  6944. endif
  6945. endif
  6946. endfunction
  6947. augroup fugitive_blame
  6948. autocmd!
  6949. autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
  6950. autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
  6951. autocmd WinLeave * let s:cursor_for_blame = [bufnr(''), line('.')]
  6952. autocmd WinEnter * if exists('s:cursor_for_blame') | call call('s:BlameCursorSync', s:cursor_for_blame) | endif
  6953. augroup END
  6954. " Section: :GBrowse
  6955. function! s:BrowserOpen(url, mods, echo_copy) abort
  6956. let [_, main, query, anchor; __] = matchlist(a:url, '^\([^#?]*\)\(?[^#]*\)\=\(#.*\)\=')
  6957. let url = main . tr(query, ' ', '+') . anchor
  6958. let url = substitute(url, '[ <>\|"]', '\="%".printf("%02X",char2nr(submatch(0)))', 'g')
  6959. let mods = s:Mods(a:mods)
  6960. if a:echo_copy
  6961. if has('clipboard')
  6962. let @+ = url
  6963. endif
  6964. return 'echo '.string(url)
  6965. elseif exists(':Browse') == 2
  6966. return 'echo '.string(url).'|' . mods . 'Browse '.url
  6967. elseif exists(':OpenBrowser') == 2
  6968. return 'echo '.string(url).'|' . mods . 'OpenBrowser '.url
  6969. else
  6970. if !exists('g:loaded_netrw')
  6971. runtime! autoload/netrw.vim
  6972. endif
  6973. if exists('*netrw#BrowseX')
  6974. return 'echo '.string(url).'|' . mods . 'call netrw#BrowseX('.string(url).', 0)'
  6975. elseif exists('*netrw#NetrwBrowseX')
  6976. return 'echo '.string(url).'|' . mods . 'call netrw#NetrwBrowseX('.string(url).', 0)'
  6977. elseif has('nvim-0.10')
  6978. return mods . 'echo luaeval("({vim.ui.open(_A)})[2] or _A", ' . string(url) . ')'
  6979. else
  6980. return 'echoerr ' . string('Netrw not found. Define your own :Browse to use :GBrowse')
  6981. endif
  6982. endif
  6983. endfunction
  6984. function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, ...) abort
  6985. exe s:VersionCheck()
  6986. let dir = s:Dir()
  6987. try
  6988. let arg = a:arg
  6989. if arg =~# '^++\%([Gg]it\)\=[Rr]emote='
  6990. let remote = matchstr(arg, '^++\%([Gg]it\)\=[Rr]emote=\zs\S\+')
  6991. let arg = matchstr(arg, '\s\zs\S.*')
  6992. endif
  6993. let validremote = '\.\%(git\)\=\|\.\=/.*\|\a[[:alnum:]_-]*\%(://.\{-\}\)\='
  6994. if arg ==# '-'
  6995. let remote = ''
  6996. let rev = ''
  6997. let result = fugitive#Result()
  6998. if filereadable(get(result, 'file', ''))
  6999. let rev = s:fnameescape(result.file)
  7000. else
  7001. return 'echoerr ' . string('fugitive: could not find prior :Git invocation')
  7002. endif
  7003. elseif !exists('l:remote')
  7004. let remote = matchstr(arg, '\\\@<!\%(\\\\\)*[!@]\zs\%('.validremote.'\)$')
  7005. let rev = strpart(arg, 0, len(arg) - len(remote) - (empty(remote) ? 0 : 1))
  7006. else
  7007. let rev = arg
  7008. endif
  7009. let expanded = s:Expand(rev)
  7010. if expanded =~? '^\a\a\+:[\/][\/]' && expanded !~? '^fugitive:'
  7011. return s:BrowserOpen(s:Slash(expanded), a:mods, a:bang)
  7012. endif
  7013. if !exists('l:result')
  7014. let result = s:TempState(empty(expanded) ? bufnr('') : expanded)
  7015. endif
  7016. if !get(result, 'origin_bufnr', 1) && filereadable(get(result, 'file', ''))
  7017. for line in readfile(result.file, '', 4096)
  7018. let rev = s:fnameescape(matchstr(line, '\<https\=://[^[:space:]<>]*[^[:space:]<>.,;:"''!?]'))
  7019. if len(rev)
  7020. return s:BrowserOpen(rev, a:mods, a:bang)
  7021. endif
  7022. endfor
  7023. return 'echoerr ' . string('fugitive: no URL found in output of :Git')
  7024. endif
  7025. if empty(remote) && expanded =~# '^[^-./:^~][^:^~]*$' && !empty(dir)
  7026. let config = fugitive#Config(dir)
  7027. if !empty(FugitiveConfigGet('remote.' . expanded . '.url', config))
  7028. let remote = expanded
  7029. let expanded = ''
  7030. endif
  7031. endif
  7032. if empty(expanded)
  7033. let bufname = &buftype =~# '^\%(nofile\|terminal\)$' ? '' : s:BufName('%')
  7034. let expanded = s:DirRev(bufname)[1]
  7035. if empty(expanded)
  7036. let expanded = fugitive#Path(bufname, ':(top)', dir)
  7037. endif
  7038. if a:count > 0 && has_key(result, 'origin_bufnr') && a:range != 2
  7039. let blame = s:BlameCommitFileLnum(getline(a:count))
  7040. if len(blame[0])
  7041. let expanded = blame[0]
  7042. endif
  7043. endif
  7044. endif
  7045. let full = s:Generate(expanded, dir)
  7046. let commit = ''
  7047. let ref = ''
  7048. let forbid_ref_as_commit = 0
  7049. if full =~# '^fugitive:'
  7050. let [dir, commit, path] = s:DirCommitFile(full)
  7051. if commit =~# '^\d\=$'
  7052. let commit = ''
  7053. let type = path =~# '^/\=$' ? 'tree' : 'blob'
  7054. else
  7055. let ref_match = matchlist(expanded, '^\(@{\@!\|[^:~^@]\+\)\(:\%(//\)\@!\|[~^@]\|$\)')
  7056. let ref = get(ref_match, 1, '')
  7057. let forbid_ref_as_commit = ref =~# '^@\=$' || ref_match[2] !~# '^:\=$'
  7058. if empty(path) && !forbid_ref_as_commit
  7059. let type = 'ref'
  7060. else
  7061. let type = s:ChompDefault(empty(path) ? 'commit': 'blob',
  7062. \ ['cat-file', '-t', commit . substitute(path, '^/', ':', '')], dir)
  7063. endif
  7064. endif
  7065. let path = path[1:-1]
  7066. elseif !empty(s:Tree(dir))
  7067. let relevant_dir = FugitiveExtractGitDir(full)
  7068. if !empty(relevant_dir)
  7069. let dir = relevant_dir
  7070. endif
  7071. let path = fugitive#Path(full, '/', dir)[1:-1]
  7072. if empty(path) || isdirectory(full)
  7073. let type = 'tree'
  7074. else
  7075. let type = 'blob'
  7076. endif
  7077. else
  7078. let path = '.git/' . full[strlen(dir)+1:-1]
  7079. let type = ''
  7080. endif
  7081. exe s:DirCheck(dir)
  7082. if path =~# '^\.git/'
  7083. let ref = matchstr(path, '^.git/\zs\%(refs/[^/]\+/[^/].*\|\w*HEAD\)$')
  7084. let type = empty(ref) ? 'root': 'ref'
  7085. let path = ''
  7086. endif
  7087. if empty(ref) || ref ==# 'HEAD' || ref ==# '@'
  7088. let ref = fugitive#Head(-1, dir)
  7089. endif
  7090. if ref =~# '^\x\{40,\}$'
  7091. let ref = ''
  7092. elseif !empty(ref) && ref !~# '^refs/'
  7093. let ref = FugitiveExecute(['rev-parse', '--symbolic-full-name', ref], dir).stdout[0]
  7094. if ref !~# '^refs/'
  7095. let ref = ''
  7096. endif
  7097. endif
  7098. if !exists('l:config') || s:Dir(config) !=# dir
  7099. let config = fugitive#Config(dir)
  7100. endif
  7101. let merge = ''
  7102. if !empty(remote) && ref =~# '^refs/remotes/[^/]\+/[^/]\|^refs/heads/[^/]'
  7103. let merge = matchstr(ref, '^refs/\%(heads/\|remotes/[^/]\+/\)\zs.\+')
  7104. let ref = 'refs/heads/' . merge
  7105. elseif ref =~# '^refs/remotes/[^/]\+/[^/]'
  7106. let remote = matchstr(ref, '^refs/remotes/\zs[^/]\+')
  7107. let merge = matchstr(ref, '^refs/remotes/[^/]\+/\zs.\+')
  7108. let ref = 'refs/heads/' . merge
  7109. elseif ref =~# '^refs/heads/[^/]'
  7110. let merge = strpart(ref, 11)
  7111. let r = FugitiveConfigGet('branch.' . merge . '.remote', config)
  7112. let m = FugitiveConfigGet('branch.' . merge . '.merge', config)[11:-1]
  7113. if r ==# '.' && !empty(m)
  7114. let r2 = FugitiveConfigGet('branch.'.m.'.remote', config)
  7115. if r2 !~# '^\.\=$'
  7116. let r = r2
  7117. let m = FugitiveConfigGet('branch.'.m.'.merge', config)[11:-1]
  7118. endif
  7119. endif
  7120. if r !~# '^\.\=$'
  7121. let remote = r
  7122. endif
  7123. if !empty(remote)
  7124. let remote_ref = 'refs/remotes/' . remote . '/' . merge
  7125. if FugitiveConfigGet('push.default', config) ==# 'upstream' ||
  7126. \ !filereadable(FugitiveFind('.git/' . remote_ref, dir)) && empty(s:ChompDefault('', ['rev-parse', '--verify', remote_ref, '--'], dir))
  7127. let merge = m
  7128. let ref = 'refs/heads/' . merge
  7129. endif
  7130. endif
  7131. endif
  7132. if empty(remote) || remote ==# '.'
  7133. let remote = s:RemoteDefault(config)
  7134. endif
  7135. if empty(merge) || empty(remote)
  7136. let provider_ref = ref
  7137. else
  7138. let provider_ref = 'refs/remotes/' . remote . '/' . merge
  7139. endif
  7140. if forbid_ref_as_commit || a:count >= 0
  7141. let ref = ''
  7142. if type ==# 'ref'
  7143. let type = 'commit'
  7144. endif
  7145. elseif type ==# 'ref' && ref =~# '^refs/\%(heads\|tags\)/[^/]'
  7146. let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
  7147. endif
  7148. let line1 = a:count > 0 && type ==# 'blob' ? a:line1 : 0
  7149. let line2 = a:count > 0 && type ==# 'blob' ? a:count : 0
  7150. if empty(commit) && type =~# '^\%(tree\|blob\)$'
  7151. if a:count < 0
  7152. let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
  7153. elseif len(provider_ref)
  7154. let owner = s:Owner(@%, dir)
  7155. let commit = s:ChompDefault('', ['merge-base', provider_ref, empty(owner) ? '@' : owner, '--'], dir)
  7156. if line2 > 0 && empty(arg) && commit =~# '^\x\{40,\}$' && type ==# 'blob'
  7157. let blame_list = tempname()
  7158. call writefile([commit, ''], blame_list, 'b')
  7159. let blame_cmd = ['-c', 'blame.coloring=none', 'blame', '-L', line1.','.line2, '-S', blame_list, '-s', '--show-number']
  7160. if !&l:modified || has_key(result, 'origin_bufnr')
  7161. let [blame, exec_error] = s:LinesError(blame_cmd + ['./' . path], dir)
  7162. else
  7163. let blame_in = tempname()
  7164. silent exe 'noautocmd keepalt %write' blame_in
  7165. let [blame, exec_error] = s:LinesError(blame_cmd + ['--contents', blame_in, './' . path], dir)
  7166. call delete(blame_in)
  7167. endif
  7168. call delete(blame_list)
  7169. if !exec_error
  7170. let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
  7171. if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
  7172. let line1 = +matchstr(blame[0], blame_regex)
  7173. let line2 = +matchstr(blame[-1], blame_regex)
  7174. else
  7175. throw "fugitive: can't browse to unpushed change"
  7176. endif
  7177. endif
  7178. endif
  7179. endif
  7180. if empty(commit)
  7181. let commit = fugitive#RevParse(empty(ref) ? 'HEAD' : ref, dir)
  7182. endif
  7183. endif
  7184. if remote =~# ':'
  7185. let remote_url = remote
  7186. else
  7187. let remote_url = fugitive#RemoteUrl(remote, config)
  7188. endif
  7189. let raw = empty(remote_url) ? remote : remote_url
  7190. let git_dir = s:GitDir(dir)
  7191. let opts = {
  7192. \ 'git_dir': git_dir,
  7193. \ 'repo': {'git_dir': git_dir},
  7194. \ 'remote': raw,
  7195. \ 'remote_name': remote,
  7196. \ 'commit': s:UrlEncode(commit),
  7197. \ 'path': substitute(s:UrlEncode(path), '%20', ' ', 'g'),
  7198. \ 'type': type,
  7199. \ 'line1': line1,
  7200. \ 'line2': line2}
  7201. if empty(path)
  7202. if type ==# 'ref' && ref =~# '^refs/'
  7203. let opts.path = '.git/' . s:UrlEncode(ref)
  7204. let opts.type = ''
  7205. elseif type ==# 'root'
  7206. let opts.path ='.git/index'
  7207. let opts.type = ''
  7208. endif
  7209. elseif type ==# 'tree' && !empty(path)
  7210. let opts.path = s:sub(opts.path, '/\=$', '/')
  7211. endif
  7212. for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
  7213. let l:.url = call(Handler, [copy(opts)])
  7214. if type(url) == type('') && url =~# '://'
  7215. return s:BrowserOpen(url, a:mods, a:bang)
  7216. endif
  7217. endfor
  7218. if !empty(remote_url)
  7219. return 'echoerr ' . string("fugitive: no GBrowse handler installed for '".remote_url."'")
  7220. else
  7221. return 'echoerr ' . string("fugitive: could not find remote named '".remote."'")
  7222. endif
  7223. catch /^fugitive:/
  7224. return 'echoerr ' . string(v:exception)
  7225. endtry
  7226. endfunction
  7227. function! s:RemoteRefToLocalRef(repo, remote_url, ref_path) abort
  7228. let ref_path = substitute(a:ref_path, ':', '/', '')
  7229. let rev = ''
  7230. if ref_path =~# '^\x\{40,\}\%(/\|$\)'
  7231. let rev = substitute(ref_path, '/', ':', '')
  7232. elseif ref_path =~# '^[^:/^~]\+'
  7233. let first_component = matchstr(ref_path, '^[^:/^~]\+')
  7234. let lines = fugitive#Execute(['ls-remote', a:remote_url, first_component, first_component . '/*'], a:repo).stdout[0:-2]
  7235. for line in lines
  7236. let full = matchstr(line, "\t\\zs.*")
  7237. for candidate in [full, matchstr(full, '^refs/\w\+/\zs.*')]
  7238. if candidate ==# first_component || strpart(ref_path . '/', 0, len(candidate) + 1) ==# candidate . '/'
  7239. let rev = matchstr(line, '^\x\+') . substitute(strpart(ref_path, len(candidate)), '/', ':', '')
  7240. endif
  7241. endfor
  7242. endfor
  7243. endif
  7244. if empty(rev)
  7245. return ''
  7246. endif
  7247. let commitish = matchstr(rev, '^[^:^~]*')
  7248. let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
  7249. if rev_parse.exit_status
  7250. if fugitive#Execute(['fetch', remote_url, commitish], a:repo).exit_status
  7251. return ''
  7252. endif
  7253. let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
  7254. endif
  7255. if rev_parse.exit_status
  7256. return ''
  7257. endif
  7258. return rev_parse.stdout[0] . matchstr(rev, ':.*')
  7259. endfunction
  7260. function! fugitive#ResolveUrl(target, ...) abort
  7261. let repo = call('s:Dir', a:000)
  7262. let origins = get(g:, 'fugitive_url_origins', {})
  7263. let prefix = substitute(s:Slash(a:target), '#.*', '', '')
  7264. while prefix =~# '://'
  7265. let extracted = FugitiveExtractGitDir(expand(get(origins, prefix, '')))
  7266. if !empty(extracted)
  7267. let repo = s:Dir(extracted)
  7268. break
  7269. endif
  7270. let prefix = matchstr(prefix, '.*\ze/')
  7271. endwhile
  7272. let git_dir = s:GitDir(repo)
  7273. for remote_name in keys(FugitiveConfigGetRegexp('^remote\.\zs.*\ze\.url$', repo))
  7274. let remote_url = fugitive#RemoteUrl(remote_name, repo)
  7275. for [no_anchor; variant] in [[1, 'commit'], [1, 'tree'], [1, 'tree', 1], [1, 'blob', 1], [0, 'blob', 1, '1`line1`', '1`line1`'], [0, 'blob', 1, '1`line1`', '2`line2`']]
  7276. let handler_opts = {
  7277. \ 'git_dir': git_dir,
  7278. \ 'repo': {'git_dir': git_dir},
  7279. \ 'remote': remote_url,
  7280. \ 'remote_name': remote_name,
  7281. \ 'commit': '1`commit`',
  7282. \ 'type': get(variant, 0),
  7283. \ 'path': get(variant, 1) ? '1`path`' : '',
  7284. \ 'line1': get(variant, 2),
  7285. \ 'line2': get(variant, 3)}
  7286. let url = ''
  7287. for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
  7288. let l:.url = call(Handler, [copy(handler_opts)])
  7289. if type(url) == type('') && url =~# '://'
  7290. break
  7291. endif
  7292. endfor
  7293. if type(url) != type('') || url !~# '://'
  7294. continue
  7295. endif
  7296. let keys = split(substitute(url, '\d`\(\w\+`\)\|.', '\1', 'g'), '`')
  7297. let pattern = substitute(url, '\d`\w\+`\|[][^$.*\~]', '\=len(submatch(0)) == 1 ? "\\" . submatch(0) : "\\([^#?&;]\\{-\\}\\)"', 'g')
  7298. let pattern = '^' . substitute(pattern, '^https\=:', 'https\\=:', '') . '$'
  7299. let target = s:Slash(no_anchor ? substitute(a:target, '#.*', '', '') : a:target)
  7300. let values = matchlist(s:Slash(a:target), pattern)[1:-1]
  7301. if empty(values)
  7302. continue
  7303. endif
  7304. let kvs = {}
  7305. for i in range(len(keys))
  7306. let kvs[keys[i]] = values[i]
  7307. endfor
  7308. if has_key(kvs, 'commit') && has_key(kvs, 'path')
  7309. let ref_path = kvs.commit . '/' . kvs.path
  7310. elseif has_key(kvs, 'commit') && variant[0] ==# 'tree'
  7311. let ref_path = kvs.commit . '/'
  7312. elseif has_key(kvs, 'commit')
  7313. let ref_path = kvs.commit
  7314. else
  7315. continue
  7316. endif
  7317. let rev = s:RemoteRefToLocalRef(repo, remote_url, fugitive#UrlDecode(ref_path))
  7318. return [fugitive#Find(rev, repo), empty(rev) ? 0 : +get(kvs, 'line1')]
  7319. endfor
  7320. endfor
  7321. return ['', 0]
  7322. endfunction
  7323. function! s:ResolveUrl(target, ...) abort
  7324. try
  7325. let [url, lnum] = call('fugitive#ResolveUrl', [a:target] + a:000)
  7326. if !empty(url)
  7327. return [url, lnum]
  7328. endif
  7329. catch
  7330. endtry
  7331. return [substitute(a:target, '#.*', '', ''), 0]
  7332. endfunction
  7333. " Section: Maps
  7334. let s:ref_header = '\%(Merge\|Rebase\|Upstream\|Pull\|Push\)'
  7335. nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
  7336. function! fugitive#MapCfile(...) abort
  7337. exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
  7338. let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
  7339. if !exists('g:fugitive_no_maps')
  7340. call s:Map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
  7341. call s:Map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
  7342. call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
  7343. call s:Map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
  7344. call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<unique>', 1)
  7345. endif
  7346. endfunction
  7347. function! s:ContainingCommit() abort
  7348. let commit = s:Owner(@%)
  7349. return empty(commit) ? '@' : commit
  7350. endfunction
  7351. function! s:SquashArgument(...) abort
  7352. if &filetype == 'fugitive'
  7353. let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze \|^' . s:ref_header . ': \zs\S\+')
  7354. elseif has_key(s:temp_files, s:cpath(expand('%:p')))
  7355. let commit = matchstr(getline('.'), '\S\@<!\x\{4,\}\S\@!')
  7356. else
  7357. let commit = s:Owner(@%)
  7358. endif
  7359. return len(commit) && a:0 ? printf(a:1, commit) : commit
  7360. endfunction
  7361. function! s:RebaseArgument() abort
  7362. return s:SquashArgument(' %s^')
  7363. endfunction
  7364. function! s:NavigateUp(count) abort
  7365. let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
  7366. let c = a:count
  7367. while c
  7368. if rev =~# ':.*/.'
  7369. let rev = matchstr(rev, '.*\ze/.\+', '')
  7370. elseif rev =~# '.:.'
  7371. let rev = matchstr(rev, '^.[^:]*:')
  7372. elseif rev =~# '^:'
  7373. let rev = '@^{}'
  7374. elseif rev =~# ':$'
  7375. let rev = rev[0:-2]
  7376. else
  7377. return rev.'~'.c
  7378. endif
  7379. let c -= 1
  7380. endwhile
  7381. return rev
  7382. endfunction
  7383. function! s:ParseDiffHeader(str) abort
  7384. let list = matchlist(a:str, '\Cdiff --git \("\=\w/.*\|/dev/null\) \("\=\w/.*\|/dev/null\)$')
  7385. if empty(list)
  7386. let list = matchlist(a:str, '\Cdiff --git \("\=[^/].*\|/dev/null\) \("\=[^/].*\|/dev/null\)$')
  7387. endif
  7388. return [fugitive#Unquote(get(list, 1, '')), fugitive#Unquote(get(list, 2, ''))]
  7389. endfunction
  7390. function! s:HunkPosition(lnum) abort
  7391. let lnum = a:lnum + get({'@': 1, '\': -1}, getline(a:lnum)[0], 0)
  7392. let offsets = {' ': -1, '+': 0, '-': 0}
  7393. let sigil = getline(lnum)[0]
  7394. let line_char = sigil
  7395. while has_key(offsets, line_char)
  7396. let offsets[line_char] += 1
  7397. let lnum -= 1
  7398. let line_char = getline(lnum)[0]
  7399. endwhile
  7400. let starts = matchlist(getline(lnum), '^@@\+[ 0-9,-]* -\(\d\+\)\%(,\d\+\)\= +\(\d\+\)[ ,]')
  7401. if empty(starts)
  7402. return [0, 0, 0]
  7403. endif
  7404. return [lnum,
  7405. \ sigil ==# '+' ? 0 : starts[1] + offsets[' '] + offsets['-'],
  7406. \ sigil ==# '-' ? 0 : starts[2] + offsets[' '] + offsets['+']]
  7407. endfunction
  7408. function! s:MapMotion(lhs, rhs) abort
  7409. let maps = [
  7410. \ s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
  7411. \ s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
  7412. \ s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")]
  7413. call filter(maps, '!empty(v:val)')
  7414. return join(maps, '|')
  7415. endfunction
  7416. function! s:MapGitOps(is_ftplugin) abort
  7417. let ft = a:is_ftplugin
  7418. if &modifiable
  7419. return ''
  7420. endif
  7421. exe s:Map('n', 'c<Space>', ':Git commit<Space>', '', ft)
  7422. exe s:Map('n', 'c<CR>', ':Git commit<CR>', '', ft)
  7423. exe s:Map('n', 'cv<Space>', ':tab Git commit -v<Space>', '', ft)
  7424. exe s:Map('n', 'cv<CR>', ':tab Git commit -v<CR>', '', ft)
  7425. exe s:Map('n', 'ca', ':<C-U>Git commit --amend<CR>', '<silent>', ft)
  7426. exe s:Map('n', 'cc', ':<C-U>Git commit<CR>', '<silent>', ft)
  7427. exe s:Map('n', 'ce', ':<C-U>Git commit --amend --no-edit<CR>', '<silent>', ft)
  7428. exe s:Map('n', 'cw', ':<C-U>Git commit --amend --only<CR>', '<silent>', ft)
  7429. exe s:Map('n', 'cva', ':<C-U>tab Git commit -v --amend<CR>', '<silent>', ft)
  7430. exe s:Map('n', 'cvc', ':<C-U>tab Git commit -v<CR>', '<silent>', ft)
  7431. exe s:Map('n', 'cRa', ':<C-U>Git commit --reset-author --amend<CR>', '<silent>', ft)
  7432. exe s:Map('n', 'cRe', ':<C-U>Git commit --reset-author --amend --no-edit<CR>', '<silent>', ft)
  7433. exe s:Map('n', 'cRw', ':<C-U>Git commit --reset-author --amend --only<CR>', '<silent>', ft)
  7434. exe s:Map('n', 'cf', ':<C-U>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>', '', ft)
  7435. exe 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>', '', ft)
  7436. exe s:Map('n', 'cs', ':<C-U>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
  7437. exe 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>', '', ft)
  7438. exe s:Map('n', 'cA', ':<C-U>Git commit --edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
  7439. exe s:Map('n', 'c?', ':<C-U>help fugitive_c<CR>', '<silent>', ft)
  7440. exe s:Map('n', 'cr<Space>', ':Git revert<Space>', '', ft)
  7441. exe s:Map('n', 'cr<CR>', ':Git revert<CR>', '', ft)
  7442. exe s:Map('n', 'crc', ':<C-U>Git revert <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
  7443. exe s:Map('n', 'crn', ':<C-U>Git revert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
  7444. exe s:Map('n', 'cr?', ':<C-U>help fugitive_cr<CR>', '<silent>', ft)
  7445. exe s:Map('n', 'cm<Space>', ':Git merge<Space>', '', ft)
  7446. exe s:Map('n', 'cm<CR>', ':Git merge<CR>', '', ft)
  7447. exe s:Map('n', 'cmt', ':Git mergetool', '', ft)
  7448. exe s:Map('n', 'cm?', ':<C-U>help fugitive_cm<CR>', '<silent>', ft)
  7449. exe s:Map('n', 'cz<Space>', ':Git stash<Space>', '', ft)
  7450. exe s:Map('n', 'cz<CR>', ':Git stash<CR>', '', ft)
  7451. exe s:Map('n', 'cza', ':<C-U>Git stash apply --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
  7452. exe s:Map('n', 'czA', ':<C-U>Git stash apply --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
  7453. exe s:Map('n', 'czp', ':<C-U>Git stash pop --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
  7454. exe s:Map('n', 'czP', ':<C-U>Git stash pop --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
  7455. exe s:Map('n', 'czs', ':<C-U>Git stash push --staged<CR>', '', ft)
  7456. exe s:Map('n', 'czv', ':<C-U>exe "Gedit" fugitive#RevParse("stash@{" . v:count . "}")<CR>', '<silent>', ft)
  7457. exe s:Map('n', 'czw', ':<C-U>Git stash push --keep-index<C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>', '', ft)
  7458. exe s:Map('n', 'czz', ':<C-U>Git stash push <C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>', '', ft)
  7459. exe s:Map('n', 'cz?', ':<C-U>help fugitive_cz<CR>', '<silent>', ft)
  7460. exe s:Map('n', 'co<Space>', ':Git checkout<Space>', '', ft)
  7461. exe s:Map('n', 'co<CR>', ':Git checkout<CR>', '', ft)
  7462. exe s:Map('n', 'coo', ':<C-U>Git checkout <C-R>=substitute(<SID>SquashArgument(),"^$",get(<SID>TempState(),"filetype","") ==# "git" ? expand("<cfile>") : "","")<CR> --<CR>', '', ft)
  7463. exe s:Map('n', 'co?', ':<C-U>help fugitive_co<CR>', '<silent>', ft)
  7464. exe s:Map('n', 'cb<Space>', ':Git branch<Space>', '', ft)
  7465. exe s:Map('n', 'cb<CR>', ':Git branch<CR>', '', ft)
  7466. exe s:Map('n', 'cb?', ':<C-U>help fugitive_cb<CR>', '<silent>', ft)
  7467. exe s:Map('n', 'r<Space>', ':Git rebase<Space>', '', ft)
  7468. exe s:Map('n', 'r<CR>', ':Git rebase<CR>', '', ft)
  7469. exe s:Map('n', 'ri', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
  7470. exe s:Map('n', 'rf', ':<C-U>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
  7471. exe s:Map('n', 'ru', ':<C-U>Git rebase --interactive @{upstream}<CR>', '<silent>', ft)
  7472. exe s:Map('n', 'rp', ':<C-U>Git rebase --interactive @{push}<CR>', '<silent>', ft)
  7473. exe s:Map('n', 'rw', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>', '<silent>', ft)
  7474. exe s:Map('n', 'rm', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>', '<silent>', ft)
  7475. exe s:Map('n', 'rd', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
  7476. exe s:Map('n', 'rk', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
  7477. exe s:Map('n', 'rx', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
  7478. exe s:Map('n', 'rr', ':<C-U>Git rebase --continue<CR>', '<silent>', ft)
  7479. exe s:Map('n', 'rs', ':<C-U>Git rebase --skip<CR>', '<silent>', ft)
  7480. exe s:Map('n', 're', ':<C-U>Git rebase --edit-todo<CR>', '<silent>', ft)
  7481. exe s:Map('n', 'ra', ':<C-U>Git rebase --abort<CR>', '<silent>', ft)
  7482. exe s:Map('n', 'r?', ':<C-U>help fugitive_r<CR>', '<silent>', ft)
  7483. endfunction
  7484. function! fugitive#MapJumps(...) abort
  7485. if !&modifiable
  7486. if get(b:, 'fugitive_type', '') ==# 'blob'
  7487. let blame_tail = '<C-R>=v:count ? " --reverse" : ""<CR><CR>'
  7488. exe s:Map('n', '<2-LeftMouse>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
  7489. exe s:Map('n', '<CR>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
  7490. exe s:Map('n', 'o', ':<C-U>0,1Git blame' . blame_tail, '<silent>')
  7491. exe s:Map('n', 'p', ':<C-U>0,1Git! blame' . blame_tail, '<silent>')
  7492. if has('patch-7.4.1898')
  7493. exe s:Map('n', 'gO', ':<C-U>vertical 0,1Git blame' . blame_tail, '<silent>')
  7494. exe s:Map('n', 'O', ':<C-U>tab 0,1Git blame' . blame_tail, '<silent>')
  7495. else
  7496. exe s:Map('n', 'gO', ':<C-U>0,4Git blame' . blame_tail, '<silent>')
  7497. exe s:Map('n', 'O', ':<C-U>0,5Git blame' . blame_tail, '<silent>')
  7498. endif
  7499. call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
  7500. call s:Map('n', 'dd', ":<C-U>call fugitive#DiffClose()<Bar>keepalt Gdiffsplit!<CR>", '<silent>')
  7501. call s:Map('n', 'dh', ":<C-U>call fugitive#DiffClose()<Bar>keepalt Ghdiffsplit!<CR>", '<silent>')
  7502. call s:Map('n', 'ds', ":<C-U>call fugitive#DiffClose()<Bar>keepalt Ghdiffsplit!<CR>", '<silent>')
  7503. call s:Map('n', 'dv', ":<C-U>call fugitive#DiffClose()<Bar>keepalt Gvdiffsplit!<CR>", '<silent>')
  7504. call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
  7505. else
  7506. call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
  7507. call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
  7508. call s:Map('n', 'o', ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
  7509. call s:Map('n', 'gO', ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
  7510. call s:Map('n', 'O', ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
  7511. call s:Map('n', 'p', ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
  7512. if !exists('g:fugitive_no_maps')
  7513. 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>')
  7514. 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>')
  7515. endif
  7516. call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
  7517. call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
  7518. call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
  7519. call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
  7520. call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
  7521. call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
  7522. call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
  7523. call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
  7524. call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
  7525. call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
  7526. call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
  7527. call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
  7528. call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
  7529. call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
  7530. call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
  7531. call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
  7532. endif
  7533. call s:Map('n', 'S', ':<C-U>echoerr "Use gO"<CR>', '<silent><unique>')
  7534. call s:Map('n', 'dq', ":<C-U>call fugitive#DiffClose()<CR>", '<silent>')
  7535. 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>')
  7536. call s:Map('n', 'P', ":<C-U>if !v:count<Bar>echoerr 'Use ~ (or provide a count)'<Bar>else<Bar>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<Bar>endif<CR>", '<silent>')
  7537. call s:Map('n', '~', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
  7538. call s:Map('n', 'C', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
  7539. call s:Map('n', 'cp', ":<C-U>echoerr 'Use gC'<CR>", '<silent><unique>')
  7540. call s:Map('n', 'gC', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
  7541. call s:Map('n', 'gc', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
  7542. call s:Map('n', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
  7543. call s:Map('x', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
  7544. call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
  7545. call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
  7546. call s:Map('n', 'g?', ":<C-U>help fugitive-map<CR>", '<silent>')
  7547. call s:Map('n', '<F1>', ":<C-U>help fugitive-map<CR>", '<silent>')
  7548. endif
  7549. let old_browsex = maparg('<Plug>NetrwBrowseX', 'n')
  7550. let new_browsex = substitute(old_browsex, '\Cnetrw#CheckIfRemote(\%(netrw#GX()\)\=)', '0', 'g')
  7551. let new_browsex = substitute(new_browsex, 'netrw#GX()\|expand((exists("g:netrw_gx")? g:netrw_gx : ''<cfile>''))', 'fugitive#GX()', 'g')
  7552. if new_browsex !=# old_browsex
  7553. exe 'nnoremap <silent> <buffer> <Plug>NetrwBrowseX' new_browsex
  7554. endif
  7555. call s:MapGitOps(0)
  7556. endfunction
  7557. function! fugitive#GX() abort
  7558. try
  7559. let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'git' ? s:cfile() : []
  7560. if len(results) && len(results[0])
  7561. return FugitiveReal(s:Generate(results[0]))
  7562. endif
  7563. catch /^fugitive:/
  7564. endtry
  7565. return expand(get(g:, 'netrw_gx', expand('<cfile>')))
  7566. endfunction
  7567. function! s:CfilePorcelain(...) abort
  7568. let tree = s:Tree()
  7569. if empty(tree)
  7570. return ['']
  7571. endif
  7572. let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
  7573. let info = s:StageInfo()
  7574. let line = getline('.')
  7575. if len(info.sigil) && len(info.section) && len(info.paths)
  7576. if info.section ==# 'Unstaged' && info.sigil !=# '-'
  7577. return [lead . info.relative[0], info.offset, 'normal!zv']
  7578. elseif info.section ==# 'Staged' && info.sigil ==# '-'
  7579. return ['@:' . info.relative[0], info.offset, 'normal!zv']
  7580. else
  7581. return [':0:' . info.relative[0], info.offset, 'normal!zv']
  7582. endif
  7583. elseif len(info.paths)
  7584. return [lead . info.relative[0]]
  7585. elseif len(info.commit)
  7586. return [info.commit]
  7587. elseif line =~# '^' . s:ref_header . ': \|^Head: '
  7588. return [matchstr(line, ' \zs.*')]
  7589. else
  7590. return ['']
  7591. endif
  7592. endfunction
  7593. function! fugitive#PorcelainCfile() abort
  7594. let file = fugitive#Find(s:CfilePorcelain()[0])
  7595. return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
  7596. endfunction
  7597. function! s:StatusCfile(...) abort
  7598. let tree = s:Tree()
  7599. if empty(tree)
  7600. return []
  7601. endif
  7602. let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
  7603. if getline('.') =~# '^.\=\trenamed:.* -> '
  7604. return [lead . matchstr(getline('.'),' -> \zs.*')]
  7605. elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
  7606. return [lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')]
  7607. elseif getline('.') =~# '^.\=\t.'
  7608. return [lead . matchstr(getline('.'),'\t\zs.*')]
  7609. elseif getline('.') =~# ': needs merge$'
  7610. return [lead . matchstr(getline('.'),'.*\ze: needs merge$')]
  7611. elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
  7612. return ['HEAD']
  7613. elseif getline('.') =~# '^\%(. \)\=On branch '
  7614. return ['refs/heads/'.getline('.')[12:]]
  7615. elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
  7616. return [matchstr(getline('.'),"'\\zs\\S\\+\\ze'")]
  7617. else
  7618. return []
  7619. endif
  7620. endfunction
  7621. function! fugitive#MessageCfile() abort
  7622. let file = fugitive#Find(get(s:StatusCfile(), 0, ''))
  7623. return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
  7624. endfunction
  7625. function! s:BranchCfile(result) abort
  7626. return matchstr(getline('.'), '^. \zs\S\+')
  7627. endfunction
  7628. let s:diff_header_pattern = '^diff --git \%("\=[abciow12]/.*\|/dev/null\) \%("\=[abciow12]/.*\|/dev/null\)$'
  7629. function! s:cfile() abort
  7630. let temp_state = s:TempState()
  7631. let name = substitute(get(get(temp_state, 'args', []), 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
  7632. if exists('*s:' . name . 'Cfile')
  7633. let cfile = s:{name}Cfile(temp_state)
  7634. if !empty(cfile)
  7635. return type(cfile) == type('') ? [cfile] : cfile
  7636. endif
  7637. endif
  7638. if empty(FugitiveGitDir())
  7639. return []
  7640. endif
  7641. try
  7642. let myhash = s:DirRev(@%)[1]
  7643. if len(myhash)
  7644. try
  7645. let myhash = fugitive#RevParse(myhash)
  7646. catch /^fugitive:/
  7647. let myhash = ''
  7648. endtry
  7649. endif
  7650. if empty(myhash) && get(temp_state, 'filetype', '') ==# 'git'
  7651. let lnum = line('.')
  7652. while lnum > 0
  7653. if getline(lnum) =~# '^\%(commit\|tag\) \w'
  7654. let myhash = matchstr(getline(lnum),'^\w\+ \zs\S\+')
  7655. break
  7656. endif
  7657. let lnum -= 1
  7658. endwhile
  7659. endif
  7660. let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
  7661. let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
  7662. \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
  7663. if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
  7664. return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
  7665. elseif showtree
  7666. return [treebase . s:sub(getline('.'),'/$','')]
  7667. else
  7668. let dcmds = []
  7669. " Index
  7670. if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
  7671. let ref = matchstr(getline('.'),'\x\{40,\}')
  7672. let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
  7673. return [file]
  7674. endif
  7675. if getline('.') =~# '^ref: '
  7676. let ref = strpart(getline('.'),5)
  7677. elseif getline('.') =~# '^\%([|/\\_ ]*\*[|/\\_ ]*\)\=commit \x\{40,\}\>'
  7678. let ref = matchstr(getline('.'),'\x\{40,\}')
  7679. return [ref]
  7680. elseif getline('.') =~# '^parent \x\{40,\}\>'
  7681. let ref = matchstr(getline('.'),'\x\{40,\}')
  7682. let line = line('.')
  7683. let parent = 0
  7684. while getline(line) =~# '^parent '
  7685. let parent += 1
  7686. let line -= 1
  7687. endwhile
  7688. return [ref]
  7689. elseif getline('.') =~# '^tree \x\{40,\}$'
  7690. let ref = matchstr(getline('.'),'\x\{40,\}')
  7691. if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
  7692. let ref = myhash.':'
  7693. endif
  7694. return [ref]
  7695. elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
  7696. let ref = matchstr(getline('.'),'\x\{40,\}')
  7697. let type = matchstr(getline(line('.')+1),'type \zs.*')
  7698. elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
  7699. let ref = s:DirRev(@%)[1]
  7700. elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
  7701. let ref = matchstr(getline('.'),'\x\{40,\}')
  7702. echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
  7703. elseif getline('.') =~# '^[A-Z]\d*\t\S' && len(myhash)
  7704. let files = split(getline('.'), "\t")[1:-1]
  7705. let ref = 'b/' . files[-1]
  7706. if getline('.') =~# '^D'
  7707. let ref = 'a/' . files[0]
  7708. elseif getline('.') !~# '^A'
  7709. let dcmds = ['', 'Gdiffsplit! >' . myhash . '^:' . fnameescape(files[0])]
  7710. endif
  7711. elseif getline('.') =~# '^[+-]'
  7712. let [header_lnum, old_lnum, new_lnum] = s:HunkPosition(line('.'))
  7713. if new_lnum > 0
  7714. let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[1]
  7715. let dcmds = [new_lnum, 'normal!zv']
  7716. elseif old_lnum > 0
  7717. let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[0]
  7718. let dcmds = [old_lnum, 'normal!zv']
  7719. else
  7720. let ref = fugitive#Unquote(matchstr(getline('.'), '\C[+-]\{3\} \zs"\=[abciow12]/.*'))
  7721. endif
  7722. elseif getline('.') =~# '^rename from '
  7723. let ref = 'a/'.getline('.')[12:]
  7724. elseif getline('.') =~# '^rename to '
  7725. let ref = 'b/'.getline('.')[10:]
  7726. elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
  7727. let diff = getline(search(s:diff_header_pattern, 'bcnW'))
  7728. let offset = matchstr(getline('.'), '+\zs\d\+')
  7729. let [dref, ref] = s:ParseDiffHeader(diff)
  7730. let dcmd = 'Gdiffsplit! +'.offset
  7731. elseif getline('.') =~# s:diff_header_pattern
  7732. let [dref, ref] = s:ParseDiffHeader(getline('.'))
  7733. let dcmd = 'Gdiffsplit!'
  7734. elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# s:diff_header_pattern
  7735. let [dref, ref] = s:ParseDiffHeader(getline(line('.') - '.'))
  7736. let dcmd = 'Gdiffsplit!'
  7737. elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
  7738. let ref = getline('.')
  7739. elseif expand('<cword>') =~# '^\x\{7,\}\>'
  7740. return [expand('<cword>')]
  7741. else
  7742. let ref = ''
  7743. endif
  7744. let prefixes = {
  7745. \ '1': '',
  7746. \ '2': '',
  7747. \ 'b': ':0:',
  7748. \ 'i': ':0:',
  7749. \ 'o': '',
  7750. \ 'w': ''}
  7751. if len(myhash)
  7752. let prefixes.a = myhash.'^:'
  7753. let prefixes.b = myhash.':'
  7754. endif
  7755. let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
  7756. if exists('dref')
  7757. let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
  7758. endif
  7759. if ref ==# '/dev/null'
  7760. " Empty blob
  7761. let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
  7762. endif
  7763. if exists('dref')
  7764. return [ref, dcmd . ' >' . s:fnameescape(dref)] + dcmds
  7765. elseif ref != ""
  7766. return [ref] + dcmds
  7767. endif
  7768. endif
  7769. return []
  7770. endtry
  7771. endfunction
  7772. function! s:GF(mode) abort
  7773. try
  7774. let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'gitcommit' ? s:StatusCfile() : s:cfile()
  7775. catch /^fugitive:/
  7776. return 'echoerr ' . string(v:exception)
  7777. endtry
  7778. if len(results) > 1
  7779. let cmd = 'G' . a:mode .
  7780. \ (empty(results[1]) ? '' : ' +' . s:PlusEscape(results[1])) . ' ' .
  7781. \ fnameescape(results[0])
  7782. let tail = join(map(results[2:-1], '"|" . v:val'), '')
  7783. if a:mode ==# 'pedit' && len(tail)
  7784. return cmd . '|wincmd P|exe ' . string(tail[1:-1]) . '|wincmd p'
  7785. else
  7786. return cmd . tail
  7787. endif
  7788. elseif len(results) && len(results[0])
  7789. return 'G' . a:mode . ' ' . s:fnameescape(results[0])
  7790. else
  7791. return ''
  7792. endif
  7793. endfunction
  7794. function! fugitive#Cfile() abort
  7795. let pre = ''
  7796. let results = s:cfile()
  7797. if empty(results)
  7798. if !empty(s:TempState())
  7799. let cfile = s:TempDotMap()
  7800. if !empty(cfile)
  7801. return fnameescape(s:Generate(cfile))
  7802. endif
  7803. endif
  7804. let cfile = expand('<cfile>')
  7805. if &includeexpr =~# '\<v:fname\>'
  7806. sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
  7807. endif
  7808. return cfile
  7809. elseif len(results) > 1
  7810. let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
  7811. endif
  7812. return pre . fnameescape(s:Generate(results[0]))
  7813. endfunction
  7814. " Section: Statusline
  7815. function! fugitive#Statusline(...) abort
  7816. let dir = s:Dir(bufnr(''))
  7817. if empty(dir)
  7818. return ''
  7819. endif
  7820. let status = ''
  7821. let commit = s:DirCommitFile(@%)[1]
  7822. if len(commit)
  7823. let status .= ':' . commit[0:6]
  7824. endif
  7825. let status .= '('.fugitive#Head(7, dir).')'
  7826. return '[Git'.status.']'
  7827. endfunction
  7828. function! fugitive#statusline(...) abort
  7829. return fugitive#Statusline()
  7830. endfunction
  7831. " Section: Folding
  7832. function! fugitive#Foldtext() abort
  7833. if &foldmethod !=# 'syntax'
  7834. return foldtext()
  7835. endif
  7836. let line_foldstart = getline(v:foldstart)
  7837. if line_foldstart =~# '^diff '
  7838. let [add, remove] = [-1, -1]
  7839. let filename = ''
  7840. for lnum in range(v:foldstart, v:foldend)
  7841. let line = getline(lnum)
  7842. if filename ==# '' && line =~# '^[+-]\{3\} "\=[abciow12]/'
  7843. let filename = fugitive#Unquote(line[4:-1])[2:-1]
  7844. endif
  7845. if line =~# '^+'
  7846. let add += 1
  7847. elseif line =~# '^-'
  7848. let remove += 1
  7849. elseif line =~# '^Binary '
  7850. let binary = 1
  7851. endif
  7852. endfor
  7853. if filename ==# ''
  7854. let filename = fugitive#Unquote(matchstr(line_foldstart, '^diff .\{-\} \zs"\=[abciow12]/\zs.*\ze "\=[abciow12]/'))[2:-1]
  7855. endif
  7856. if filename ==# ''
  7857. let filename = line_foldstart[5:-1]
  7858. endif
  7859. if exists('binary')
  7860. return 'Binary: '.filename
  7861. else
  7862. return '+-' . v:folddashes . ' ' . (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
  7863. endif
  7864. elseif line_foldstart =~# '^@@\+ .* @@'
  7865. return '+-' . v:folddashes . ' ' . line_foldstart
  7866. elseif &filetype ==# 'fugitive' && line_foldstart =~# '^[A-Z][a-z].* (\d\+)$'
  7867. let c = +matchstr(line_foldstart, '(\zs\d\+\ze)$')
  7868. return '+-' . v:folddashes . printf('%3d item', c) . (c == 1 ? ': ' : 's: ') . matchstr(line_foldstart, '.*\ze (\d\+)$')
  7869. elseif &filetype ==# 'gitcommit' && line_foldstart =~# '^# .*:$'
  7870. let lines = getline(v:foldstart, v:foldend)
  7871. call filter(lines, 'v:val =~# "^#\t"')
  7872. call map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
  7873. call map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
  7874. return line_foldstart.' '.join(lines, ', ')
  7875. endif
  7876. return foldtext()
  7877. endfunction
  7878. function! fugitive#foldtext() abort
  7879. return fugitive#Foldtext()
  7880. endfunction
  7881. " Section: End