Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

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