You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DirCacheCheckout.java 51KB

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