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 55KB

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