Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

DirCacheCheckout.java 55KB

Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
Send a detailed event on working tree modifications Currently there is no way to determine the precise changes done to the working tree by a JGit command. Only the CheckoutCommand actually provides access to the lists of modified, deleted, and to-be-deleted files, but those lists may be inaccurate (since they are determined up-front before the working tree is modified) if the actual checkout then fails halfway through. Moreover, other JGit commands that modify the working tree do not offer any way to figure out which files were changed. This poses problems for EGit, which may need to refresh parts of the Eclipse workspace when JGit has done java.io file operations. Provide the foundations for better file change tracking: the working tree is modified exclusively in DirCacheCheckout. Make it emit a new type of RepositoryEvent that lists all files that were modified or deleted, even if the checkout failed halfway through. We update the 'updated' and 'removed' lists determined up-front in case of file system problems to reflect the actual state of changes made. EGit thus can register a listener for these events and then knows exactly which parts of the Eclipse workspace may need to be refreshed. Two commands manage checking out individual DirCacheEntries themselves: checkout specific paths, and applying a stash with untracked files. Make those two also emit such a new WorkingTreeModifiedEvent. Furthermore, merges may modify files, and clean, rm, and stash create may delete files. CQ: 13969 Bug: 500106 Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
vor 6 Jahren
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646
  1. /*
  2. * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  3. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  4. * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
  5. * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
  6. * Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com> and
  7. * other copyright owners as documented in the project's IP log.
  8. *
  9. * This program and the accompanying materials are made available under the
  10. * terms of the Eclipse Distribution License v1.0 which accompanies this
  11. * distribution, is reproduced below, and is available at
  12. * http://www.eclipse.org/org/documents/edl-v10.php
  13. *
  14. * All rights reserved.
  15. *
  16. * Redistribution and use in source and binary forms, with or without
  17. * modification, are permitted provided that the following conditions are met:
  18. *
  19. * - Redistributions of source code must retain the above copyright notice, this
  20. * list of conditions and the following disclaimer.
  21. *
  22. * - Redistributions in binary form must reproduce the above copyright notice,
  23. * this list of conditions and the following disclaimer in the documentation
  24. * and/or other materials provided with the distribution.
  25. *
  26. * - Neither the name of the Eclipse Foundation, Inc. nor the names of its
  27. * contributors may be used to endorse or promote products derived from this
  28. * software without specific prior written permission.
  29. *
  30. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  31. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  32. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  34. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  35. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  36. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  37. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  38. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  39. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  40. * POSSIBILITY OF SUCH DAMAGE.
  41. */
  42. package org.eclipse.jgit.dircache;
  43. import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
  44. import java.io.File;
  45. import java.io.FileOutputStream;
  46. import java.io.IOException;
  47. import java.io.OutputStream;
  48. import java.nio.file.StandardCopyOption;
  49. import java.text.MessageFormat;
  50. import java.util.ArrayList;
  51. import java.util.HashMap;
  52. import java.util.Iterator;
  53. import java.util.List;
  54. import java.util.Map;
  55. import org.eclipse.jgit.api.errors.CanceledException;
  56. import org.eclipse.jgit.api.errors.FilterFailedException;
  57. import org.eclipse.jgit.attributes.FilterCommand;
  58. import org.eclipse.jgit.attributes.FilterCommandRegistry;
  59. import org.eclipse.jgit.errors.CheckoutConflictException;
  60. import org.eclipse.jgit.errors.CorruptObjectException;
  61. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  62. import org.eclipse.jgit.errors.IndexWriteException;
  63. import org.eclipse.jgit.errors.MissingObjectException;
  64. import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
  65. import org.eclipse.jgit.internal.JGitText;
  66. import org.eclipse.jgit.lib.ConfigConstants;
  67. import org.eclipse.jgit.lib.Constants;
  68. import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
  69. import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
  70. import org.eclipse.jgit.lib.CoreConfig.SymLinks;
  71. import org.eclipse.jgit.lib.FileMode;
  72. import org.eclipse.jgit.lib.NullProgressMonitor;
  73. import org.eclipse.jgit.lib.ObjectChecker;
  74. import org.eclipse.jgit.lib.ObjectId;
  75. import org.eclipse.jgit.lib.ObjectLoader;
  76. import org.eclipse.jgit.lib.ObjectReader;
  77. import org.eclipse.jgit.lib.ProgressMonitor;
  78. import org.eclipse.jgit.lib.Repository;
  79. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  80. import org.eclipse.jgit.treewalk.CanonicalTreeParser;
  81. import org.eclipse.jgit.treewalk.EmptyTreeIterator;
  82. import org.eclipse.jgit.treewalk.FileTreeIterator;
  83. import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
  84. import org.eclipse.jgit.treewalk.TreeWalk;
  85. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  86. import org.eclipse.jgit.treewalk.WorkingTreeOptions;
  87. import org.eclipse.jgit.treewalk.filter.PathFilter;
  88. import org.eclipse.jgit.util.FS;
  89. import org.eclipse.jgit.util.FS.ExecutionResult;
  90. import org.eclipse.jgit.util.FileUtils;
  91. import org.eclipse.jgit.util.IntList;
  92. import org.eclipse.jgit.util.RawParseUtils;
  93. import org.eclipse.jgit.util.SystemReader;
  94. import org.eclipse.jgit.util.io.EolStreamTypeUtil;
  95. import org.slf4j.Logger;
  96. import org.slf4j.LoggerFactory;
  97. /**
  98. * This class handles checking out one or two trees merging with the index.
  99. */
  100. public class DirCacheCheckout {
  101. private static Logger LOG = LoggerFactory.getLogger(DirCacheCheckout.class);
  102. private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
  103. /**
  104. * Metadata used in checkout process
  105. *
  106. * @since 4.3
  107. */
  108. public static class CheckoutMetadata {
  109. /** git attributes */
  110. public final EolStreamType eolStreamType;
  111. /** filter command to apply */
  112. public final String smudgeFilterCommand;
  113. /**
  114. * @param eolStreamType
  115. * @param smudgeFilterCommand
  116. */
  117. public CheckoutMetadata(EolStreamType eolStreamType,
  118. String smudgeFilterCommand) {
  119. this.eolStreamType = eolStreamType;
  120. this.smudgeFilterCommand = smudgeFilterCommand;
  121. }
  122. static CheckoutMetadata EMPTY = new CheckoutMetadata(
  123. EolStreamType.DIRECT, null);
  124. }
  125. private Repository repo;
  126. private HashMap<String, CheckoutMetadata> updated = new HashMap<>();
  127. private ArrayList<String> conflicts = new ArrayList<>();
  128. private ArrayList<String> removed = new ArrayList<>();
  129. private ObjectId mergeCommitTree;
  130. private DirCache dc;
  131. private DirCacheBuilder builder;
  132. private NameConflictTreeWalk walk;
  133. private ObjectId headCommitTree;
  134. private WorkingTreeIterator workingTree;
  135. private boolean failOnConflict = true;
  136. private boolean force = false;
  137. private ArrayList<String> toBeDeleted = new ArrayList<>();
  138. private boolean initialCheckout;
  139. private boolean performingCheckout;
  140. private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
  141. /**
  142. * Get list of updated paths and smudgeFilterCommands
  143. *
  144. * @return a list of updated paths and smudgeFilterCommands
  145. */
  146. public Map<String, CheckoutMetadata> getUpdated() {
  147. return updated;
  148. }
  149. /**
  150. * Get a list of conflicts created by this checkout
  151. *
  152. * @return a list of conflicts created by this checkout
  153. */
  154. public List<String> getConflicts() {
  155. return conflicts;
  156. }
  157. /**
  158. * Get list of paths of files which couldn't be deleted during last call to
  159. * {@link #checkout()}
  160. *
  161. * @return a list of paths (relative to the start of the working tree) of
  162. * files which couldn't be deleted during last call to
  163. * {@link #checkout()} . {@link #checkout()} detected that these
  164. * files should be deleted but the deletion in the filesystem failed
  165. * (e.g. because a file was locked). To have a consistent state of
  166. * the working tree these files have to be deleted by the callers of
  167. * {@link org.eclipse.jgit.dircache.DirCacheCheckout}.
  168. */
  169. public List<String> getToBeDeleted() {
  170. return toBeDeleted;
  171. }
  172. /**
  173. * Get list of all files removed by this checkout
  174. *
  175. * @return a list of all files removed by this checkout
  176. */
  177. public List<String> getRemoved() {
  178. return removed;
  179. }
  180. /**
  181. * Constructs a DirCacheCeckout for merging and checking out two trees (HEAD
  182. * and mergeCommitTree) and the index.
  183. *
  184. * @param repo
  185. * the repository in which we do the checkout
  186. * @param headCommitTree
  187. * the id of the tree of the head commit
  188. * @param dc
  189. * the (already locked) Dircache for this repo
  190. * @param mergeCommitTree
  191. * the id of the tree we want to fast-forward to
  192. * @param workingTree
  193. * an iterator over the repositories Working Tree
  194. * @throws java.io.IOException
  195. */
  196. public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc,
  197. ObjectId mergeCommitTree, WorkingTreeIterator workingTree)
  198. throws IOException {
  199. this.repo = repo;
  200. this.dc = dc;
  201. this.headCommitTree = headCommitTree;
  202. this.mergeCommitTree = mergeCommitTree;
  203. this.workingTree = workingTree;
  204. this.initialCheckout = !repo.isBare() && !repo.getIndexFile().exists();
  205. }
  206. /**
  207. * Constructs a DirCacheCeckout for merging and checking out two trees (HEAD
  208. * and mergeCommitTree) and the index. As iterator over the working tree
  209. * this constructor creates a standard
  210. * {@link org.eclipse.jgit.treewalk.FileTreeIterator}
  211. *
  212. * @param repo
  213. * the repository in which we do the checkout
  214. * @param headCommitTree
  215. * the id of the tree of the head commit
  216. * @param dc
  217. * the (already locked) Dircache for this repo
  218. * @param mergeCommitTree
  219. * the id of the tree we want to fast-forward to
  220. * @throws java.io.IOException
  221. */
  222. public DirCacheCheckout(Repository repo, ObjectId headCommitTree,
  223. DirCache dc, ObjectId mergeCommitTree) throws IOException {
  224. this(repo, headCommitTree, dc, mergeCommitTree, new FileTreeIterator(repo));
  225. }
  226. /**
  227. * Constructs a DirCacheCeckout for checking out one tree, merging with the
  228. * index.
  229. *
  230. * @param repo
  231. * the repository in which we do the checkout
  232. * @param dc
  233. * the (already locked) Dircache for this repo
  234. * @param mergeCommitTree
  235. * the id of the tree we want to fast-forward to
  236. * @param workingTree
  237. * an iterator over the repositories Working Tree
  238. * @throws java.io.IOException
  239. */
  240. public DirCacheCheckout(Repository repo, DirCache dc,
  241. ObjectId mergeCommitTree, WorkingTreeIterator workingTree)
  242. throws IOException {
  243. this(repo, null, dc, mergeCommitTree, workingTree);
  244. }
  245. /**
  246. * Constructs a DirCacheCeckout for checking out one tree, merging with the
  247. * index. As iterator over the working tree this constructor creates a
  248. * standard {@link org.eclipse.jgit.treewalk.FileTreeIterator}
  249. *
  250. * @param repo
  251. * the repository in which we do the checkout
  252. * @param dc
  253. * the (already locked) Dircache for this repo
  254. * @param mergeCommitTree
  255. * the id of the tree of the
  256. * @throws java.io.IOException
  257. */
  258. public DirCacheCheckout(Repository repo, DirCache dc,
  259. ObjectId mergeCommitTree) throws IOException {
  260. this(repo, null, dc, mergeCommitTree, new FileTreeIterator(repo));
  261. }
  262. /**
  263. * Set a progress monitor which can be passed to built-in filter commands,
  264. * providing progress information for long running tasks.
  265. *
  266. * @param monitor
  267. * the {@link ProgressMonitor}
  268. * @since 4.11
  269. */
  270. public void setProgressMonitor(ProgressMonitor monitor) {
  271. this.monitor = monitor != null ? monitor : NullProgressMonitor.INSTANCE;
  272. }
  273. /**
  274. * Scan head, index and merge tree. Used during normal checkout or merge
  275. * operations.
  276. *
  277. * @throws org.eclipse.jgit.errors.CorruptObjectException
  278. * @throws java.io.IOException
  279. */
  280. public void preScanTwoTrees() throws CorruptObjectException, IOException {
  281. removed.clear();
  282. updated.clear();
  283. conflicts.clear();
  284. walk = new NameConflictTreeWalk(repo);
  285. builder = dc.builder();
  286. addTree(walk, headCommitTree);
  287. addTree(walk, mergeCommitTree);
  288. int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
  289. walk.addTree(workingTree);
  290. workingTree.setDirCacheIterator(walk, dciPos);
  291. while (walk.next()) {
  292. processEntry(walk.getTree(0, CanonicalTreeParser.class),
  293. walk.getTree(1, CanonicalTreeParser.class),
  294. walk.getTree(2, DirCacheBuildIterator.class),
  295. walk.getTree(3, WorkingTreeIterator.class));
  296. if (walk.isSubtree())
  297. walk.enterSubtree();
  298. }
  299. }
  300. private void addTree(TreeWalk tw, ObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException {
  301. if (id == null)
  302. tw.addTree(new EmptyTreeIterator());
  303. else
  304. tw.addTree(id);
  305. }
  306. /**
  307. * Scan index and merge tree (no HEAD). Used e.g. for initial checkout when
  308. * there is no head yet.
  309. *
  310. * @throws org.eclipse.jgit.errors.MissingObjectException
  311. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  312. * @throws org.eclipse.jgit.errors.CorruptObjectException
  313. * @throws java.io.IOException
  314. */
  315. public void prescanOneTree()
  316. throws MissingObjectException, IncorrectObjectTypeException,
  317. CorruptObjectException, IOException {
  318. removed.clear();
  319. updated.clear();
  320. conflicts.clear();
  321. builder = dc.builder();
  322. walk = new NameConflictTreeWalk(repo);
  323. addTree(walk, mergeCommitTree);
  324. int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
  325. walk.addTree(workingTree);
  326. workingTree.setDirCacheIterator(walk, dciPos);
  327. while (walk.next()) {
  328. processEntry(walk.getTree(0, CanonicalTreeParser.class),
  329. walk.getTree(1, DirCacheBuildIterator.class),
  330. walk.getTree(2, WorkingTreeIterator.class));
  331. if (walk.isSubtree())
  332. walk.enterSubtree();
  333. }
  334. conflicts.removeAll(removed);
  335. }
  336. /**
  337. * Processing an entry in the context of {@link #prescanOneTree()} when only
  338. * one tree is given
  339. *
  340. * @param m the tree to merge
  341. * @param i the index
  342. * @param f the working tree
  343. * @throws IOException
  344. */
  345. void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
  346. WorkingTreeIterator f) throws IOException {
  347. if (m != null) {
  348. checkValidPath(m);
  349. // There is an entry in the merge commit. Means: we want to update
  350. // what's currently in the index and working-tree to that one
  351. if (i == null) {
  352. // The index entry is missing
  353. if (f != null && !FileMode.TREE.equals(f.getEntryFileMode())
  354. && !f.isEntryIgnored()) {
  355. if (failOnConflict) {
  356. // don't overwrite an untracked and not ignored file
  357. conflicts.add(walk.getPathString());
  358. } else {
  359. // failOnConflict is false. Putting something to conflicts
  360. // would mean we delete it. Instead we want the mergeCommit
  361. // content to be checked out.
  362. update(m.getEntryPathString(), m.getEntryObjectId(),
  363. m.getEntryFileMode());
  364. }
  365. } else
  366. update(m.getEntryPathString(), m.getEntryObjectId(),
  367. m.getEntryFileMode());
  368. } else if (f == null || !m.idEqual(i)) {
  369. // The working tree file is missing or the merge content differs
  370. // from index content
  371. update(m.getEntryPathString(), m.getEntryObjectId(),
  372. m.getEntryFileMode());
  373. } else if (i.getDirCacheEntry() != null) {
  374. // The index contains a file (and not a folder)
  375. if (f.isModified(i.getDirCacheEntry(), true,
  376. this.walk.getObjectReader())
  377. || i.getDirCacheEntry().getStage() != 0)
  378. // The working tree file is dirty or the index contains a
  379. // conflict
  380. update(m.getEntryPathString(), m.getEntryObjectId(),
  381. m.getEntryFileMode());
  382. else {
  383. // update the timestamp of the index with the one from the
  384. // file if not set, as we are sure to be in sync here.
  385. DirCacheEntry entry = i.getDirCacheEntry();
  386. if (entry.getLastModified() == 0)
  387. entry.setLastModified(f.getEntryLastModified());
  388. keep(entry, f);
  389. }
  390. } else
  391. // The index contains a folder
  392. keep(i.getDirCacheEntry(), f);
  393. } else {
  394. // There is no entry in the merge commit. Means: we want to delete
  395. // what's currently in the index and working tree
  396. if (f != null) {
  397. // There is a file/folder for that path in the working tree
  398. if (walk.isDirectoryFileConflict()) {
  399. // We put it in conflicts. Even if failOnConflict is false
  400. // this would cause the path to be deleted. Thats exactly what
  401. // we want in this situation
  402. conflicts.add(walk.getPathString());
  403. } else {
  404. // No file/folder conflict exists. All entries are files or
  405. // all entries are folders
  406. if (i != null) {
  407. // ... and the working tree contained a file or folder
  408. // -> add it to the removed set and remove it from
  409. // conflicts set
  410. remove(i.getEntryPathString());
  411. conflicts.remove(i.getEntryPathString());
  412. } else {
  413. // untracked file, neither contained in tree to merge
  414. // nor in index
  415. }
  416. }
  417. } else {
  418. // There is no file/folder for that path in the working tree,
  419. // nor in the merge head.
  420. // The only entry we have is the index entry. Like the case
  421. // where there is a file with the same name, remove it,
  422. }
  423. }
  424. }
  425. /**
  426. * Execute this checkout. A
  427. * {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent} is fired if the
  428. * working tree was modified; even if the checkout fails.
  429. *
  430. * @return <code>false</code> if this method could not delete all the files
  431. * which should be deleted (e.g. because one of the files was
  432. * locked). In this case {@link #getToBeDeleted()} lists the files
  433. * which should be tried to be deleted outside of this method.
  434. * Although <code>false</code> is returned the checkout was
  435. * successful and the working tree was updated for all other files.
  436. * <code>true</code> is returned when no such problem occurred
  437. * @throws java.io.IOException
  438. */
  439. public boolean checkout() throws IOException {
  440. try {
  441. return doCheckout();
  442. } catch (CanceledException ce) {
  443. // should actually be propagated, but this would change a LOT of
  444. // APIs
  445. throw new IOException(ce);
  446. } finally {
  447. try {
  448. dc.unlock();
  449. } finally {
  450. if (performingCheckout) {
  451. WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
  452. getUpdated().keySet(), getRemoved());
  453. if (!event.isEmpty()) {
  454. repo.fireEvent(event);
  455. }
  456. }
  457. }
  458. }
  459. }
  460. private boolean doCheckout() throws CorruptObjectException, IOException,
  461. MissingObjectException, IncorrectObjectTypeException,
  462. CheckoutConflictException, IndexWriteException, CanceledException {
  463. toBeDeleted.clear();
  464. try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
  465. if (headCommitTree != null)
  466. preScanTwoTrees();
  467. else
  468. prescanOneTree();
  469. if (!conflicts.isEmpty()) {
  470. if (failOnConflict)
  471. throw new CheckoutConflictException(conflicts.toArray(new String[0]));
  472. else
  473. cleanUpConflicts();
  474. }
  475. // update our index
  476. builder.finish();
  477. // init progress reporting
  478. int numTotal = removed.size() + updated.size() + conflicts.size();
  479. monitor.beginTask(JGitText.get().checkingOutFiles, numTotal);
  480. performingCheckout = true;
  481. File file = null;
  482. String last = null;
  483. // when deleting files process them in the opposite order as they have
  484. // been reported. This ensures the files are deleted before we delete
  485. // their parent folders
  486. IntList nonDeleted = new IntList();
  487. for (int i = removed.size() - 1; i >= 0; i--) {
  488. String r = removed.get(i);
  489. file = new File(repo.getWorkTree(), r);
  490. if (!file.delete() && repo.getFS().exists(file)) {
  491. // The list of stuff to delete comes from the index
  492. // which will only contain a directory if it is
  493. // a submodule, in which case we shall not attempt
  494. // to delete it. A submodule is not empty, so it
  495. // is safe to check this after a failed delete.
  496. if (!repo.getFS().isDirectory(file)) {
  497. nonDeleted.add(i);
  498. toBeDeleted.add(r);
  499. }
  500. } else {
  501. if (last != null && !isSamePrefix(r, last))
  502. removeEmptyParents(new File(repo.getWorkTree(), last));
  503. last = r;
  504. }
  505. monitor.update(1);
  506. if (monitor.isCancelled()) {
  507. throw new CanceledException(MessageFormat.format(
  508. JGitText.get().operationCanceled,
  509. JGitText.get().checkingOutFiles));
  510. }
  511. }
  512. if (file != null) {
  513. removeEmptyParents(file);
  514. }
  515. removed = filterOut(removed, nonDeleted);
  516. nonDeleted = null;
  517. Iterator<Map.Entry<String, CheckoutMetadata>> toUpdate = updated
  518. .entrySet().iterator();
  519. Map.Entry<String, CheckoutMetadata> e = null;
  520. try {
  521. while (toUpdate.hasNext()) {
  522. e = toUpdate.next();
  523. String path = e.getKey();
  524. CheckoutMetadata meta = e.getValue();
  525. DirCacheEntry entry = dc.getEntry(path);
  526. if (FileMode.GITLINK.equals(entry.getRawMode())) {
  527. checkoutGitlink(path, entry);
  528. } else {
  529. checkoutEntry(repo, entry, objectReader, false, meta);
  530. }
  531. e = null;
  532. monitor.update(1);
  533. if (monitor.isCancelled()) {
  534. throw new CanceledException(MessageFormat.format(
  535. JGitText.get().operationCanceled,
  536. JGitText.get().checkingOutFiles));
  537. }
  538. }
  539. } catch (Exception ex) {
  540. // We didn't actually modify the current entry nor any that
  541. // might follow.
  542. if (e != null) {
  543. toUpdate.remove();
  544. }
  545. while (toUpdate.hasNext()) {
  546. e = toUpdate.next();
  547. toUpdate.remove();
  548. }
  549. throw ex;
  550. }
  551. for (String conflict : conflicts) {
  552. // the conflicts are likely to have multiple entries in the
  553. // dircache, we only want to check out the one for the "theirs"
  554. // tree
  555. int entryIdx = dc.findEntry(conflict);
  556. if (entryIdx >= 0) {
  557. while (entryIdx < dc.getEntryCount()) {
  558. DirCacheEntry entry = dc.getEntry(entryIdx);
  559. if (!entry.getPathString().equals(conflict)) {
  560. break;
  561. }
  562. if (entry.getStage() == DirCacheEntry.STAGE_3) {
  563. checkoutEntry(repo, entry, objectReader, false,
  564. null);
  565. break;
  566. }
  567. ++entryIdx;
  568. }
  569. }
  570. monitor.update(1);
  571. if (monitor.isCancelled()) {
  572. throw new CanceledException(MessageFormat.format(
  573. JGitText.get().operationCanceled,
  574. JGitText.get().checkingOutFiles));
  575. }
  576. }
  577. monitor.endTask();
  578. // commit the index builder - a new index is persisted
  579. if (!builder.commit())
  580. throw new IndexWriteException();
  581. }
  582. return toBeDeleted.isEmpty();
  583. }
  584. private void checkoutGitlink(String path, DirCacheEntry entry)
  585. throws IOException {
  586. File gitlinkDir = new File(repo.getWorkTree(), path);
  587. FileUtils.mkdirs(gitlinkDir, true);
  588. FS fs = repo.getFS();
  589. entry.setLastModified(fs.lastModified(gitlinkDir));
  590. }
  591. private static ArrayList<String> filterOut(ArrayList<String> strings,
  592. IntList indicesToRemove) {
  593. int n = indicesToRemove.size();
  594. if (n == strings.size()) {
  595. return new ArrayList<>(0);
  596. }
  597. switch (n) {
  598. case 0:
  599. return strings;
  600. case 1:
  601. strings.remove(indicesToRemove.get(0));
  602. return strings;
  603. default:
  604. int length = strings.size();
  605. ArrayList<String> result = new ArrayList<>(length - n);
  606. // Process indicesToRemove from the back; we know that it
  607. // contains indices in descending order.
  608. int j = n - 1;
  609. int idx = indicesToRemove.get(j);
  610. for (int i = 0; i < length; i++) {
  611. if (i == idx) {
  612. idx = (--j >= 0) ? indicesToRemove.get(j) : -1;
  613. } else {
  614. result.add(strings.get(i));
  615. }
  616. }
  617. return result;
  618. }
  619. }
  620. private static boolean isSamePrefix(String a, String b) {
  621. int as = a.lastIndexOf('/');
  622. int bs = b.lastIndexOf('/');
  623. return a.substring(0, as + 1).equals(b.substring(0, bs + 1));
  624. }
  625. private void removeEmptyParents(File f) {
  626. File parentFile = f.getParentFile();
  627. while (parentFile != null && !parentFile.equals(repo.getWorkTree())) {
  628. if (!parentFile.delete())
  629. break;
  630. parentFile = parentFile.getParentFile();
  631. }
  632. }
  633. /**
  634. * Compares whether two pairs of ObjectId and FileMode are equal.
  635. *
  636. * @param id1
  637. * @param mode1
  638. * @param id2
  639. * @param mode2
  640. * @return <code>true</code> if FileModes and ObjectIds are equal.
  641. * <code>false</code> otherwise
  642. */
  643. private boolean equalIdAndMode(ObjectId id1, FileMode mode1, ObjectId id2,
  644. FileMode mode2) {
  645. if (!mode1.equals(mode2))
  646. return false;
  647. return id1 != null ? id1.equals(id2) : id2 == null;
  648. }
  649. /**
  650. * Here the main work is done. This method is called for each existing path
  651. * in head, index and merge. This method decides what to do with the
  652. * corresponding index entry: keep it, update it, remove it or mark a
  653. * conflict.
  654. *
  655. * @param h
  656. * the entry for the head
  657. * @param m
  658. * the entry for the merge
  659. * @param i
  660. * the entry for the index
  661. * @param f
  662. * the file in the working tree
  663. * @throws IOException
  664. */
  665. void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
  666. DirCacheBuildIterator i, WorkingTreeIterator f) throws IOException {
  667. DirCacheEntry dce = i != null ? i.getDirCacheEntry() : null;
  668. String name = walk.getPathString();
  669. if (m != null)
  670. checkValidPath(m);
  671. if (i == null && m == null && h == null) {
  672. // File/Directory conflict case #20
  673. if (walk.isDirectoryFileConflict())
  674. // TODO: check whether it is always correct to report a conflict here
  675. conflict(name, null, null, null);
  676. // file only exists in working tree -> ignore it
  677. return;
  678. }
  679. ObjectId iId = (i == null ? null : i.getEntryObjectId());
  680. ObjectId mId = (m == null ? null : m.getEntryObjectId());
  681. ObjectId hId = (h == null ? null : h.getEntryObjectId());
  682. FileMode iMode = (i == null ? null : i.getEntryFileMode());
  683. FileMode mMode = (m == null ? null : m.getEntryFileMode());
  684. FileMode hMode = (h == null ? null : h.getEntryFileMode());
  685. /**
  686. * <pre>
  687. * File/Directory conflicts:
  688. * the following table from ReadTreeTest tells what to do in case of directory/file
  689. * conflicts. I give comments here
  690. *
  691. * H I M Clean H==M H==I I==M Result
  692. * ------------------------------------------------------------------
  693. * 1 D D F Y N Y N Update
  694. * 2 D D F N N Y N Conflict
  695. * 3 D F D Y N N Keep
  696. * 4 D F D N N N Conflict
  697. * 5 D F F Y N N Y Keep
  698. * 5b D F F Y N N N Conflict
  699. * 6 D F F N N N Y Keep
  700. * 6b D F F N N N N Conflict
  701. * 7 F D F Y Y N N Update
  702. * 8 F D F N Y N N Conflict
  703. * 9 F D F N N N Conflict
  704. * 10 F D D N N Y Keep
  705. * 11 F D D N N N Conflict
  706. * 12 F F D Y N Y N Update
  707. * 13 F F D N N Y N Conflict
  708. * 14 F F D N N N Conflict
  709. * 15 0 F D N N N Conflict
  710. * 16 0 D F Y N N N Update
  711. * 17 0 D F N N N Conflict
  712. * 18 F 0 D Update
  713. * 19 D 0 F Update
  714. * 20 0 0 F N (worktree=dir) Conflict
  715. * </pre>
  716. */
  717. // The information whether head,index,merge iterators are currently
  718. // pointing to file/folder/non-existing is encoded into this variable.
  719. //
  720. // To decode write down ffMask in hexadecimal form. The last digit
  721. // represents the state for the merge iterator, the second last the
  722. // state for the index iterator and the third last represents the state
  723. // for the head iterator. The hexadecimal constant "F" stands for
  724. // "file", a "D" stands for "directory" (tree), and a "0" stands for
  725. // non-existing. Symbolic links and git links are treated as File here.
  726. //
  727. // Examples:
  728. // ffMask == 0xFFD -> Head=File, Index=File, Merge=Tree
  729. // ffMask == 0xDD0 -> Head=Tree, Index=Tree, Merge=Non-Existing
  730. int ffMask = 0;
  731. if (h != null)
  732. ffMask = FileMode.TREE.equals(hMode) ? 0xD00 : 0xF00;
  733. if (i != null)
  734. ffMask |= FileMode.TREE.equals(iMode) ? 0x0D0 : 0x0F0;
  735. if (m != null)
  736. ffMask |= FileMode.TREE.equals(mMode) ? 0x00D : 0x00F;
  737. // Check whether we have a possible file/folder conflict. Therefore we
  738. // need a least one file and one folder.
  739. if (((ffMask & 0x222) != 0x000)
  740. && (((ffMask & 0x00F) == 0x00D) || ((ffMask & 0x0F0) == 0x0D0) || ((ffMask & 0xF00) == 0xD00))) {
  741. // There are 3*3*3=27 possible combinations of file/folder
  742. // conflicts. Some of them are not-relevant because
  743. // they represent no conflict, e.g. 0xFFF, 0xDDD, ... The following
  744. // switch processes all relevant cases.
  745. switch (ffMask) {
  746. case 0xDDF: // 1 2
  747. if (f != null && isModifiedSubtree_IndexWorkingtree(name)) {
  748. conflict(name, dce, h, m); // 1
  749. } else {
  750. update(name, mId, mMode); // 2
  751. }
  752. break;
  753. case 0xDFD: // 3 4
  754. keep(dce, f);
  755. break;
  756. case 0xF0D: // 18
  757. remove(name);
  758. break;
  759. case 0xDFF: // 5 5b 6 6b
  760. if (equalIdAndMode(iId, iMode, mId, mMode))
  761. keep(dce, f); // 5 6
  762. else
  763. conflict(name, dce, h, m); // 5b 6b
  764. break;
  765. case 0xFDD: // 10 11
  766. // TODO: make use of tree extension as soon as available in jgit
  767. // we would like to do something like
  768. // if (!equalIdAndMode(iId, iMode, mId, mMode)
  769. // conflict(name, i.getDirCacheEntry(), h, m);
  770. // But since we don't know the id of a tree in the index we do
  771. // nothing here and wait that conflicts between index and merge
  772. // are found later
  773. break;
  774. case 0xD0F: // 19
  775. update(name, mId, mMode);
  776. break;
  777. case 0xDF0: // conflict without a rule
  778. case 0x0FD: // 15
  779. conflict(name, dce, h, m);
  780. break;
  781. case 0xFDF: // 7 8 9
  782. if (equalIdAndMode(hId, hMode, mId, mMode)) {
  783. if (isModifiedSubtree_IndexWorkingtree(name))
  784. conflict(name, dce, h, m); // 8
  785. else
  786. update(name, mId, mMode); // 7
  787. } else
  788. conflict(name, dce, h, m); // 9
  789. break;
  790. case 0xFD0: // keep without a rule
  791. keep(dce, f);
  792. break;
  793. case 0xFFD: // 12 13 14
  794. if (equalIdAndMode(hId, hMode, iId, iMode))
  795. if (f != null
  796. && f.isModified(dce, true,
  797. this.walk.getObjectReader()))
  798. conflict(name, dce, h, m); // 13
  799. else
  800. remove(name); // 12
  801. else
  802. conflict(name, dce, h, m); // 14
  803. break;
  804. case 0x0DF: // 16 17
  805. if (!isModifiedSubtree_IndexWorkingtree(name))
  806. update(name, mId, mMode);
  807. else
  808. conflict(name, dce, h, m);
  809. break;
  810. default:
  811. keep(dce, f);
  812. }
  813. return;
  814. }
  815. if ((ffMask & 0x222) == 0) {
  816. // HEAD, MERGE and index don't contain a file (e.g. all contain a
  817. // folder)
  818. if (f == null || FileMode.TREE.equals(f.getEntryFileMode())) {
  819. // the workingtree entry doesn't exist or also contains a folder
  820. // -> no problem
  821. return;
  822. } else {
  823. // the workingtree entry exists and is not a folder
  824. if (!idEqual(h, m)) {
  825. // Because HEAD and MERGE differ we will try to update the
  826. // workingtree with a folder -> return a conflict
  827. conflict(name, null, null, null);
  828. }
  829. return;
  830. }
  831. }
  832. if ((ffMask == 0x00F) && f != null && FileMode.TREE.equals(f.getEntryFileMode())) {
  833. // File/Directory conflict case #20
  834. conflict(name, null, h, m);
  835. return;
  836. }
  837. if (i == null) {
  838. // Nothing in Index
  839. // At least one of Head, Index, Merge is not empty
  840. // make sure not to overwrite untracked files
  841. if (f != null && !f.isEntryIgnored()) {
  842. // A submodule is not a file. We should ignore it
  843. if (!FileMode.GITLINK.equals(mMode)) {
  844. // a dirty worktree: the index is empty but we have a
  845. // workingtree-file
  846. if (mId == null
  847. || !equalIdAndMode(mId, mMode,
  848. f.getEntryObjectId(), f.getEntryFileMode())) {
  849. conflict(name, null, h, m);
  850. return;
  851. }
  852. }
  853. }
  854. /**
  855. * <pre>
  856. * I (index) H M H==M Result
  857. * -------------------------------------------
  858. * 0 nothing nothing nothing (does not happen)
  859. * 1 nothing nothing exists use M
  860. * 2 nothing exists nothing remove path from index
  861. * 3 nothing exists exists yes keep index if not in initial checkout
  862. * , otherwise use M
  863. * nothing exists exists no fail
  864. * </pre>
  865. */
  866. if (h == null)
  867. // Nothing in Head
  868. // Nothing in Index
  869. // At least one of Head, Index, Merge is not empty
  870. // -> only Merge contains something for this path. Use it!
  871. // Potentially update the file
  872. update(name, mId, mMode); // 1
  873. else if (m == null)
  874. // Nothing in Merge
  875. // Something in Head
  876. // Nothing in Index
  877. // -> only Head contains something for this path and it should
  878. // be deleted. Potentially removes the file!
  879. remove(name); // 2
  880. else { // 3
  881. // Something in Merge
  882. // Something in Head
  883. // Nothing in Index
  884. // -> Head and Merge contain something (maybe not the same) and
  885. // in the index there is nothing (e.g. 'git rm ...' was
  886. // called before). Ignore the cached deletion and use what we
  887. // find in Merge. Potentially updates the file.
  888. if (equalIdAndMode(hId, hMode, mId, mMode)) {
  889. if (initialCheckout)
  890. update(name, mId, mMode);
  891. else
  892. keep(dce, f);
  893. } else
  894. conflict(name, dce, h, m);
  895. }
  896. } else {
  897. // Something in Index
  898. if (h == null) {
  899. // Nothing in Head
  900. // Something in Index
  901. /**
  902. * <pre>
  903. * clean I==H I==M H M Result
  904. * -----------------------------------------------------
  905. * 4 yes N/A N/A nothing nothing keep index
  906. * 5 no N/A N/A nothing nothing keep index
  907. *
  908. * 6 yes N/A yes nothing exists keep index
  909. * 7 no N/A yes nothing exists keep index
  910. * 8 yes N/A no nothing exists fail
  911. * 9 no N/A no nothing exists fail
  912. * </pre>
  913. */
  914. if (m == null
  915. || !isModified_IndexTree(name, iId, iMode, mId, mMode,
  916. mergeCommitTree)) {
  917. // Merge contains nothing or the same as Index
  918. // Nothing in Head
  919. // Something in Index
  920. if (m==null && walk.isDirectoryFileConflict()) {
  921. // Nothing in Merge and current path is part of
  922. // File/Folder conflict
  923. // Nothing in Head
  924. // Something in Index
  925. if (dce != null
  926. && (f == null || f.isModified(dce, true,
  927. this.walk.getObjectReader())))
  928. // No file or file is dirty
  929. // Nothing in Merge and current path is part of
  930. // File/Folder conflict
  931. // Nothing in Head
  932. // Something in Index
  933. // -> File folder conflict and Merge wants this
  934. // path to be removed. Since the file is dirty
  935. // report a conflict
  936. conflict(name, dce, h, m);
  937. else
  938. // A file is present and file is not dirty
  939. // Nothing in Merge and current path is part of
  940. // File/Folder conflict
  941. // Nothing in Head
  942. // Something in Index
  943. // -> File folder conflict and Merge wants this path
  944. // to be removed. Since the file is not dirty remove
  945. // file and index entry
  946. remove(name);
  947. } else
  948. // Something in Merge or current path is not part of
  949. // File/Folder conflict
  950. // Merge contains nothing or the same as Index
  951. // Nothing in Head
  952. // Something in Index
  953. // -> Merge contains nothing new. Keep the index.
  954. keep(dce, f);
  955. } else
  956. // Merge contains something and it is not the same as Index
  957. // Nothing in Head
  958. // Something in Index
  959. // -> Index contains something new (different from Head)
  960. // and Merge is different from Index. Report a conflict
  961. conflict(name, dce, h, m);
  962. } else if (m == null) {
  963. // Nothing in Merge
  964. // Something in Head
  965. // Something in Index
  966. /**
  967. * <pre>
  968. * clean I==H I==M H M Result
  969. * -----------------------------------------------------
  970. * 10 yes yes N/A exists nothing remove path from index
  971. * 11 no yes N/A exists nothing keep file
  972. * 12 yes no N/A exists nothing fail
  973. * 13 no no N/A exists nothing fail
  974. * </pre>
  975. */
  976. if (iMode == FileMode.GITLINK) {
  977. // A submodule in Index
  978. // Nothing in Merge
  979. // Something in Head
  980. // Submodules that disappear from the checkout must
  981. // be removed from the index, but not deleted from disk.
  982. remove(name);
  983. } else {
  984. // Something different from a submodule in Index
  985. // Nothing in Merge
  986. // Something in Head
  987. if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
  988. headCommitTree)) {
  989. // Index contains the same as Head
  990. // Something different from a submodule in Index
  991. // Nothing in Merge
  992. // Something in Head
  993. if (f != null
  994. && f.isModified(dce, true,
  995. this.walk.getObjectReader())) {
  996. // file is dirty
  997. // Index contains the same as Head
  998. // Something different from a submodule in Index
  999. // Nothing in Merge
  1000. // Something in Head
  1001. if (!FileMode.TREE.equals(f.getEntryFileMode())
  1002. && FileMode.TREE.equals(iMode))
  1003. // The workingtree contains a file and the index semantically contains a folder.
  1004. // Git considers the workingtree file as untracked. Just keep the untracked file.
  1005. return;
  1006. else
  1007. // -> file is dirty and tracked but is should be
  1008. // removed. That's a conflict
  1009. conflict(name, dce, h, m);
  1010. } else
  1011. // file doesn't exist or is clean
  1012. // Index contains the same as Head
  1013. // Something different from a submodule in Index
  1014. // Nothing in Merge
  1015. // Something in Head
  1016. // -> Remove from index and delete the file
  1017. remove(name);
  1018. } else
  1019. // Index contains something different from Head
  1020. // Something different from a submodule in Index
  1021. // Nothing in Merge
  1022. // Something in Head
  1023. // -> Something new is in index (and maybe even on the
  1024. // filesystem). But Merge wants the path to be removed.
  1025. // Report a conflict
  1026. conflict(name, dce, h, m);
  1027. }
  1028. } else {
  1029. // Something in Merge
  1030. // Something in Head
  1031. // Something in Index
  1032. if (!equalIdAndMode(hId, hMode, mId, mMode)
  1033. && isModified_IndexTree(name, iId, iMode, hId, hMode,
  1034. headCommitTree)
  1035. && isModified_IndexTree(name, iId, iMode, mId, mMode,
  1036. mergeCommitTree))
  1037. // All three contents in Head, Merge, Index differ from each
  1038. // other
  1039. // -> All contents differ. Report a conflict.
  1040. conflict(name, dce, h, m);
  1041. else
  1042. // At least two of the contents of Head, Index, Merge
  1043. // are the same
  1044. // Something in Merge
  1045. // Something in Head
  1046. // Something in Index
  1047. if (!isModified_IndexTree(name, iId, iMode, hId, hMode,
  1048. headCommitTree)
  1049. && isModified_IndexTree(name, iId, iMode, mId, mMode,
  1050. mergeCommitTree)) {
  1051. // Head contains the same as Index. Merge differs
  1052. // Something in Merge
  1053. // For submodules just update the index with the new SHA-1
  1054. if (dce != null
  1055. && FileMode.GITLINK.equals(dce.getFileMode())) {
  1056. // Index and Head contain the same submodule. Merge
  1057. // differs
  1058. // Something in Merge
  1059. // -> Nothing new in index. Move to merge.
  1060. // Potentially updates the file
  1061. // TODO check that we don't overwrite some unsaved
  1062. // file content
  1063. update(name, mId, mMode);
  1064. } else if (dce != null
  1065. && (f != null && f.isModified(dce, true,
  1066. this.walk.getObjectReader()))) {
  1067. // File exists and is dirty
  1068. // Head and Index don't contain a submodule
  1069. // Head contains the same as Index. Merge differs
  1070. // Something in Merge
  1071. // -> Merge wants the index and file to be updated
  1072. // but the file is dirty. Report a conflict
  1073. conflict(name, dce, h, m);
  1074. } else {
  1075. // File doesn't exist or is clean
  1076. // Head and Index don't contain a submodule
  1077. // Head contains the same as Index. Merge differs
  1078. // Something in Merge
  1079. // -> Standard case when switching between branches:
  1080. // Nothing new in index but something different in
  1081. // Merge. Update index and file
  1082. update(name, mId, mMode);
  1083. }
  1084. } else {
  1085. // Head differs from index or merge is same as index
  1086. // At least two of the contents of Head, Index, Merge
  1087. // are the same
  1088. // Something in Merge
  1089. // Something in Head
  1090. // Something in Index
  1091. // Can be formulated as: Either all three states are
  1092. // equal or Merge is equal to Head or Index and differs
  1093. // to the other one.
  1094. // -> In all three cases we don't touch index and file.
  1095. keep(dce, f);
  1096. }
  1097. }
  1098. }
  1099. }
  1100. private static boolean idEqual(AbstractTreeIterator a,
  1101. AbstractTreeIterator b) {
  1102. if (a == b) {
  1103. return true;
  1104. }
  1105. if (a == null || b == null) {
  1106. return false;
  1107. }
  1108. return a.getEntryObjectId().equals(b.getEntryObjectId());
  1109. }
  1110. /**
  1111. * A conflict is detected - add the three different stages to the index
  1112. * @param path the path of the conflicting entry
  1113. * @param e the previous index entry
  1114. * @param h the first tree you want to merge (the HEAD)
  1115. * @param m the second tree you want to merge
  1116. */
  1117. private void conflict(String path, DirCacheEntry e, AbstractTreeIterator h, AbstractTreeIterator m) {
  1118. conflicts.add(path);
  1119. DirCacheEntry entry;
  1120. if (e != null) {
  1121. entry = new DirCacheEntry(e.getPathString(), DirCacheEntry.STAGE_1);
  1122. entry.copyMetaData(e, true);
  1123. builder.add(entry);
  1124. }
  1125. if (h != null && !FileMode.TREE.equals(h.getEntryFileMode())) {
  1126. entry = new DirCacheEntry(h.getEntryPathString(), DirCacheEntry.STAGE_2);
  1127. entry.setFileMode(h.getEntryFileMode());
  1128. entry.setObjectId(h.getEntryObjectId());
  1129. builder.add(entry);
  1130. }
  1131. if (m != null && !FileMode.TREE.equals(m.getEntryFileMode())) {
  1132. entry = new DirCacheEntry(m.getEntryPathString(), DirCacheEntry.STAGE_3);
  1133. entry.setFileMode(m.getEntryFileMode());
  1134. entry.setObjectId(m.getEntryObjectId());
  1135. builder.add(entry);
  1136. }
  1137. }
  1138. private void keep(DirCacheEntry e, WorkingTreeIterator f)
  1139. throws IOException {
  1140. if (e != null && !FileMode.TREE.equals(e.getFileMode()))
  1141. builder.add(e);
  1142. if (force) {
  1143. if (f.isModified(e, true, this.walk.getObjectReader())) {
  1144. checkoutEntry(repo, e, this.walk.getObjectReader());
  1145. }
  1146. }
  1147. }
  1148. private void remove(String path) {
  1149. removed.add(path);
  1150. }
  1151. private void update(String path, ObjectId mId, FileMode mode)
  1152. throws IOException {
  1153. if (!FileMode.TREE.equals(mode)) {
  1154. updated.put(path, new CheckoutMetadata(
  1155. walk.getEolStreamType(CHECKOUT_OP),
  1156. walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
  1157. DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
  1158. entry.setObjectId(mId);
  1159. entry.setFileMode(mode);
  1160. builder.add(entry);
  1161. }
  1162. }
  1163. /**
  1164. * If <code>true</code>, will scan first to see if it's possible to check
  1165. * out, otherwise throw
  1166. * {@link org.eclipse.jgit.errors.CheckoutConflictException}. If
  1167. * <code>false</code>, it will silently deal with the problem.
  1168. *
  1169. * @param failOnConflict
  1170. * a boolean.
  1171. */
  1172. public void setFailOnConflict(boolean failOnConflict) {
  1173. this.failOnConflict = failOnConflict;
  1174. }
  1175. /**
  1176. * If <code>true</code>, dirty worktree files may be overridden. If
  1177. * <code>false</code> dirty worktree files will not be overridden in order
  1178. * not to delete unsaved content. This corresponds to native git's 'git
  1179. * checkout -f' option. By default this option is set to false.
  1180. *
  1181. * @param force
  1182. * a boolean.
  1183. * @since 5.3
  1184. */
  1185. public void setForce(boolean force) {
  1186. this.force = force;
  1187. }
  1188. /**
  1189. * This method implements how to handle conflicts when
  1190. * {@link #failOnConflict} is false
  1191. *
  1192. * @throws CheckoutConflictException
  1193. */
  1194. private void cleanUpConflicts() throws CheckoutConflictException {
  1195. // TODO: couldn't we delete unsaved worktree content here?
  1196. for (String c : conflicts) {
  1197. File conflict = new File(repo.getWorkTree(), c);
  1198. if (!conflict.delete())
  1199. throw new CheckoutConflictException(MessageFormat.format(
  1200. JGitText.get().cannotDeleteFile, c));
  1201. removeEmptyParents(conflict);
  1202. }
  1203. for (String r : removed) {
  1204. File file = new File(repo.getWorkTree(), r);
  1205. if (!file.delete())
  1206. throw new CheckoutConflictException(
  1207. MessageFormat.format(JGitText.get().cannotDeleteFile,
  1208. file.getAbsolutePath()));
  1209. removeEmptyParents(file);
  1210. }
  1211. }
  1212. /**
  1213. * Checks whether the subtree starting at a given path differs between Index and
  1214. * workingtree.
  1215. *
  1216. * @param path
  1217. * @return true if the subtrees differ
  1218. * @throws CorruptObjectException
  1219. * @throws IOException
  1220. */
  1221. private boolean isModifiedSubtree_IndexWorkingtree(String path)
  1222. throws CorruptObjectException, IOException {
  1223. try (NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
  1224. int dciPos = tw.addTree(new DirCacheIterator(dc));
  1225. FileTreeIterator fti = new FileTreeIterator(repo);
  1226. tw.addTree(fti);
  1227. fti.setDirCacheIterator(tw, dciPos);
  1228. tw.setRecursive(true);
  1229. tw.setFilter(PathFilter.create(path));
  1230. DirCacheIterator dcIt;
  1231. WorkingTreeIterator wtIt;
  1232. while (tw.next()) {
  1233. dcIt = tw.getTree(0, DirCacheIterator.class);
  1234. wtIt = tw.getTree(1, WorkingTreeIterator.class);
  1235. if (dcIt == null || wtIt == null)
  1236. return true;
  1237. if (wtIt.isModified(dcIt.getDirCacheEntry(), true,
  1238. this.walk.getObjectReader())) {
  1239. return true;
  1240. }
  1241. }
  1242. return false;
  1243. }
  1244. }
  1245. private boolean isModified_IndexTree(String path, ObjectId iId,
  1246. FileMode iMode, ObjectId tId, FileMode tMode, ObjectId rootTree)
  1247. throws CorruptObjectException, IOException {
  1248. if (iMode != tMode)
  1249. return true;
  1250. if (FileMode.TREE.equals(iMode)
  1251. && (iId == null || ObjectId.zeroId().equals(iId)))
  1252. return isModifiedSubtree_IndexTree(path, rootTree);
  1253. else
  1254. return !equalIdAndMode(iId, iMode, tId, tMode);
  1255. }
  1256. /**
  1257. * Checks whether the subtree starting at a given path differs between Index and
  1258. * some tree.
  1259. *
  1260. * @param path
  1261. * @param tree
  1262. * the tree to compare
  1263. * @return true if the subtrees differ
  1264. * @throws CorruptObjectException
  1265. * @throws IOException
  1266. */
  1267. private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree)
  1268. throws CorruptObjectException, IOException {
  1269. try (NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
  1270. tw.addTree(new DirCacheIterator(dc));
  1271. tw.addTree(tree);
  1272. tw.setRecursive(true);
  1273. tw.setFilter(PathFilter.create(path));
  1274. while (tw.next()) {
  1275. AbstractTreeIterator dcIt = tw.getTree(0,
  1276. DirCacheIterator.class);
  1277. AbstractTreeIterator treeIt = tw.getTree(1,
  1278. AbstractTreeIterator.class);
  1279. if (dcIt == null || treeIt == null)
  1280. return true;
  1281. if (dcIt.getEntryRawMode() != treeIt.getEntryRawMode())
  1282. return true;
  1283. if (!dcIt.getEntryObjectId().equals(treeIt.getEntryObjectId()))
  1284. return true;
  1285. }
  1286. return false;
  1287. }
  1288. }
  1289. /**
  1290. * Updates the file in the working tree with content and mode from an entry
  1291. * in the index. The new content is first written to a new temporary file in
  1292. * the same directory as the real file. Then that new file is renamed to the
  1293. * final filename.
  1294. *
  1295. * <p>
  1296. * <b>Note:</b> if the entry path on local file system exists as a non-empty
  1297. * directory, and the target entry type is a link or file, the checkout will
  1298. * fail with {@link java.io.IOException} since existing non-empty directory
  1299. * cannot be renamed to file or link without deleting it recursively.
  1300. * </p>
  1301. *
  1302. * <p>
  1303. * TODO: this method works directly on File IO, we may need another
  1304. * abstraction (like WorkingTreeIterator). This way we could tell e.g.
  1305. * Eclipse that Files in the workspace got changed
  1306. * </p>
  1307. *
  1308. * @param repo
  1309. * repository managing the destination work tree.
  1310. * @param entry
  1311. * the entry containing new mode and content
  1312. * @param or
  1313. * object reader to use for checkout
  1314. * @throws java.io.IOException
  1315. * @since 3.6
  1316. * @deprecated since 5.1, use
  1317. * {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata)}
  1318. * instead
  1319. */
  1320. @Deprecated
  1321. public static void checkoutEntry(Repository repo, DirCacheEntry entry,
  1322. ObjectReader or) throws IOException {
  1323. checkoutEntry(repo, entry, or, false, null);
  1324. }
  1325. /**
  1326. * Updates the file in the working tree with content and mode from an entry
  1327. * in the index. The new content is first written to a new temporary file in
  1328. * the same directory as the real file. Then that new file is renamed to the
  1329. * final filename.
  1330. *
  1331. * <p>
  1332. * <b>Note:</b> if the entry path on local file system exists as a file, it
  1333. * will be deleted and if it exists as a directory, it will be deleted
  1334. * recursively, independently if has any content.
  1335. * </p>
  1336. *
  1337. * <p>
  1338. * TODO: this method works directly on File IO, we may need another
  1339. * abstraction (like WorkingTreeIterator). This way we could tell e.g.
  1340. * Eclipse that Files in the workspace got changed
  1341. * </p>
  1342. *
  1343. * @param repo
  1344. * repository managing the destination work tree.
  1345. * @param entry
  1346. * the entry containing new mode and content
  1347. * @param or
  1348. * object reader to use for checkout
  1349. * @param deleteRecursive
  1350. * true to recursively delete final path if it exists on the file
  1351. * system
  1352. * @param checkoutMetadata
  1353. * containing
  1354. * <ul>
  1355. * <li>smudgeFilterCommand to be run for smudging the entry to be
  1356. * checked out</li>
  1357. * <li>eolStreamType used for stream conversion</li>
  1358. * </ul>
  1359. * @throws java.io.IOException
  1360. * @since 4.2
  1361. */
  1362. public static void checkoutEntry(Repository repo, DirCacheEntry entry,
  1363. ObjectReader or, boolean deleteRecursive,
  1364. CheckoutMetadata checkoutMetadata) throws IOException {
  1365. if (checkoutMetadata == null)
  1366. checkoutMetadata = CheckoutMetadata.EMPTY;
  1367. ObjectLoader ol = or.open(entry.getObjectId());
  1368. File f = new File(repo.getWorkTree(), entry.getPathString());
  1369. File parentDir = f.getParentFile();
  1370. FileUtils.mkdirs(parentDir, true);
  1371. FS fs = repo.getFS();
  1372. WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
  1373. if (entry.getFileMode() == FileMode.SYMLINK
  1374. && opt.getSymLinks() == SymLinks.TRUE) {
  1375. byte[] bytes = ol.getBytes();
  1376. String target = RawParseUtils.decode(bytes);
  1377. if (deleteRecursive && f.isDirectory()) {
  1378. FileUtils.delete(f, FileUtils.RECURSIVE);
  1379. }
  1380. fs.createSymLink(f, target);
  1381. entry.setLength(bytes.length);
  1382. entry.setLastModified(fs.lastModified(f));
  1383. return;
  1384. }
  1385. String name = f.getName();
  1386. if (name.length() > 200) {
  1387. name = name.substring(0, 200);
  1388. }
  1389. File tmpFile = File.createTempFile(
  1390. "._" + name, null, parentDir); //$NON-NLS-1$
  1391. EolStreamType nonNullEolStreamType;
  1392. if (checkoutMetadata.eolStreamType != null) {
  1393. nonNullEolStreamType = checkoutMetadata.eolStreamType;
  1394. } else if (opt.getAutoCRLF() == AutoCRLF.TRUE) {
  1395. nonNullEolStreamType = EolStreamType.AUTO_CRLF;
  1396. } else {
  1397. nonNullEolStreamType = EolStreamType.DIRECT;
  1398. }
  1399. try (OutputStream channel = EolStreamTypeUtil.wrapOutputStream(
  1400. new FileOutputStream(tmpFile), nonNullEolStreamType)) {
  1401. if (checkoutMetadata.smudgeFilterCommand != null) {
  1402. if (FilterCommandRegistry
  1403. .isRegistered(checkoutMetadata.smudgeFilterCommand)) {
  1404. runBuiltinFilterCommand(repo, checkoutMetadata, ol,
  1405. channel);
  1406. } else {
  1407. runExternalFilterCommand(repo, entry, checkoutMetadata, ol,
  1408. fs, channel);
  1409. }
  1410. } else {
  1411. ol.copyTo(channel);
  1412. }
  1413. }
  1414. // The entry needs to correspond to the on-disk filesize. If the content
  1415. // was filtered (either by autocrlf handling or smudge filters) ask the
  1416. // filesystem again for the length. Otherwise the objectloader knows the
  1417. // size
  1418. if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT
  1419. && checkoutMetadata.smudgeFilterCommand == null) {
  1420. entry.setLength(ol.getSize());
  1421. } else {
  1422. entry.setLength(tmpFile.length());
  1423. }
  1424. if (opt.isFileMode() && fs.supportsExecute()) {
  1425. if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
  1426. if (!fs.canExecute(tmpFile))
  1427. fs.setExecute(tmpFile, true);
  1428. } else {
  1429. if (fs.canExecute(tmpFile))
  1430. fs.setExecute(tmpFile, false);
  1431. }
  1432. }
  1433. try {
  1434. if (deleteRecursive && f.isDirectory()) {
  1435. FileUtils.delete(f, FileUtils.RECURSIVE);
  1436. }
  1437. FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
  1438. } catch (IOException e) {
  1439. throw new IOException(
  1440. MessageFormat.format(JGitText.get().renameFileFailed,
  1441. tmpFile.getPath(), f.getPath()),
  1442. e);
  1443. } finally {
  1444. if (tmpFile.exists()) {
  1445. FileUtils.delete(tmpFile);
  1446. }
  1447. }
  1448. entry.setLastModified(fs.lastModified(f));
  1449. }
  1450. // Run an external filter command
  1451. private static void runExternalFilterCommand(Repository repo,
  1452. DirCacheEntry entry,
  1453. CheckoutMetadata checkoutMetadata, ObjectLoader ol, FS fs,
  1454. OutputStream channel) throws IOException {
  1455. ProcessBuilder filterProcessBuilder = fs.runInShell(
  1456. checkoutMetadata.smudgeFilterCommand, new String[0]);
  1457. filterProcessBuilder.directory(repo.getWorkTree());
  1458. filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
  1459. repo.getDirectory().getAbsolutePath());
  1460. ExecutionResult result;
  1461. int rc;
  1462. try {
  1463. // TODO: wire correctly with AUTOCRLF
  1464. result = fs.execute(filterProcessBuilder, ol.openStream());
  1465. rc = result.getRc();
  1466. if (rc == 0) {
  1467. result.getStdout().writeTo(channel,
  1468. NullProgressMonitor.INSTANCE);
  1469. }
  1470. } catch (IOException | InterruptedException e) {
  1471. throw new IOException(new FilterFailedException(e,
  1472. checkoutMetadata.smudgeFilterCommand,
  1473. entry.getPathString()));
  1474. }
  1475. if (rc != 0) {
  1476. throw new IOException(new FilterFailedException(rc,
  1477. checkoutMetadata.smudgeFilterCommand,
  1478. entry.getPathString(),
  1479. result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
  1480. RawParseUtils.decode(result.getStderr()
  1481. .toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
  1482. }
  1483. }
  1484. // Run a builtin filter command
  1485. private static void runBuiltinFilterCommand(Repository repo,
  1486. CheckoutMetadata checkoutMetadata, ObjectLoader ol,
  1487. OutputStream channel) throws MissingObjectException, IOException {
  1488. boolean isMandatory = repo.getConfig().getBoolean(
  1489. ConfigConstants.CONFIG_FILTER_SECTION,
  1490. ConfigConstants.CONFIG_SECTION_LFS,
  1491. ConfigConstants.CONFIG_KEY_REQUIRED, false);
  1492. FilterCommand command = null;
  1493. try {
  1494. command = FilterCommandRegistry.createFilterCommand(
  1495. checkoutMetadata.smudgeFilterCommand, repo, ol.openStream(),
  1496. channel);
  1497. } catch (IOException e) {
  1498. LOG.error(JGitText.get().failedToDetermineFilterDefinition, e);
  1499. if (!isMandatory) {
  1500. // In case an IOException occurred during creating of the
  1501. // command then proceed as if there would not have been a
  1502. // builtin filter (only if the filter is not mandatory).
  1503. ol.copyTo(channel);
  1504. } else {
  1505. throw e;
  1506. }
  1507. }
  1508. if (command != null) {
  1509. while (command.run() != -1) {
  1510. // loop as long as command.run() tells there is work to do
  1511. }
  1512. }
  1513. }
  1514. @SuppressWarnings("deprecation")
  1515. private static void checkValidPath(CanonicalTreeParser t)
  1516. throws InvalidPathException {
  1517. ObjectChecker chk = new ObjectChecker()
  1518. .setSafeForWindows(SystemReader.getInstance().isWindows())
  1519. .setSafeForMacOS(SystemReader.getInstance().isMacOS());
  1520. for (CanonicalTreeParser i = t; i != null; i = i.getParent())
  1521. checkValidPathSegment(chk, i);
  1522. }
  1523. private static void checkValidPathSegment(ObjectChecker chk,
  1524. CanonicalTreeParser t) throws InvalidPathException {
  1525. try {
  1526. int ptr = t.getNameOffset();
  1527. int end = ptr + t.getNameLength();
  1528. chk.checkPathSegment(t.getEntryPathBuffer(), ptr, end);
  1529. } catch (CorruptObjectException err) {
  1530. String path = t.getEntryPathString();
  1531. InvalidPathException i = new InvalidPathException(path);
  1532. i.initCause(err);
  1533. throw i;
  1534. }
  1535. }
  1536. }