選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

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