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>
9 * Redistribution and use in source and binary forms, with or
10 * without modification, are permitted provided that the following
13 * - Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
16 * - Redistributions in binary form must reproduce the above
17 * copyright notice, this list of conditions and the following
18 * disclaimer in the documentation and/or other materials provided
19 * with the distribution.
21 * - Neither the name of the Git Development Community nor the
22 * names of its contributors may be used to endorse or promote
23 * products derived from this software without specific prior
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
27 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
28 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
29 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
31 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
33 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
36 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
38 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 package org
.spearce
.jgit
.lib
;
44 import java
.io
.FileNotFoundException
;
45 import java
.io
.IOException
;
46 import java
.util
.ArrayList
;
47 import java
.util
.HashMap
;
49 import org
.spearce
.jgit
.errors
.CheckoutConflictException
;
50 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
53 * This class handles checking out one or two trees merging
54 * with the index (actually a tree too).
56 * Three-way merges are no performed. See {@link #setFailOnConflict(boolean)}.
58 public class WorkDirCheckout
{
65 private boolean failOnConflict
= true;
71 * If <code>true</code>, will scan first to see if it's possible to check out,
72 * otherwise throw {@link CheckoutConflictException}. If <code>false</code>,
73 * it will silently deal with the problem.
74 * @param failOnConflict
76 public void setFailOnConflict(boolean failOnConflict
) {
77 this.failOnConflict
= failOnConflict
;
80 WorkDirCheckout(Repository repo
, File workDir
,
81 GitIndex oldIndex
, GitIndex newIndex
) throws IOException
{
84 this.index
= oldIndex
;
85 this.merge
= repo
.mapTree(newIndex
.writeTree());
89 * Create a checkout class for checking out one tree, merging with the index
93 * @param index current index
94 * @param merge tree to check out
96 public WorkDirCheckout(Repository repo
, File root
,
97 GitIndex index
, Tree merge
) {
105 * Create a checkout class for merging and checking our two trees and the index.
108 * @param root workdir
113 public WorkDirCheckout(Repository repo
, File root
, Tree head
, GitIndex index
, Tree merge
) {
114 this(repo
, root
, index
, merge
);
119 * Execute this checkout
121 * @throws IOException
123 public void checkout() throws IOException
{
126 else prescanTwoTrees();
127 if (!conflicts
.isEmpty()) {
128 if (failOnConflict
) {
129 String
[] entries
= conflicts
.toArray(new String
[0]);
130 throw new CheckoutConflictException(entries
);
136 checkoutOutIndexNoHead();
137 else checkoutTwoTrees();
140 private void checkoutTwoTrees() throws FileNotFoundException
, IOException
{
141 for (String path
: removed
) {
142 index
.remove(root
, new File(root
, path
));
145 for (java
.util
.Map
.Entry
<String
, ObjectId
> entry
: updated
.entrySet()) {
146 Entry newEntry
= index
.addEntry(merge
.findBlobMember(entry
.getKey()));
147 index
.checkoutEntry(root
, newEntry
);
151 ArrayList
<String
> conflicts
= new ArrayList
<String
>();
152 ArrayList
<String
> removed
= new ArrayList
<String
>();
156 HashMap
<String
, ObjectId
> updated
= new HashMap
<String
, ObjectId
>();
158 private void checkoutOutIndexNoHead() throws IOException
{
159 new IndexTreeWalker(index
, merge
, root
, new AbstractIndexTreeVisitor() {
160 public void visitEntry(TreeEntry m
, Entry i
, File f
) throws IOException
{
162 index
.remove(root
, f
);
166 boolean needsCheckout
= false;
168 needsCheckout
= true;
169 else if (i
.getObjectId().equals(m
.getId())) {
170 if (i
.isModified(root
, true))
171 needsCheckout
= true;
172 } else needsCheckout
= true;
175 Entry newEntry
= index
.addEntry(m
);
176 index
.checkoutEntry(root
, newEntry
);
182 private void cleanUpConflicts() throws CheckoutConflictException
{
183 for (String c
: conflicts
) {
184 File conflict
= new File(root
, c
);
185 if (!conflict
.delete())
186 throw new CheckoutConflictException("Cannot delete file: " + c
);
187 removeEmptyParents(conflict
);
189 for (String r
: removed
) {
190 File file
= new File(root
, r
);
192 removeEmptyParents(file
);
196 private void removeEmptyParents(File f
) {
197 File parentFile
= f
.getParentFile();
198 while (!parentFile
.equals(root
)) {
199 if (parentFile
.list().length
== 0)
203 parentFile
= parentFile
.getParentFile();
207 void prescanOneTree() throws IOException
{
208 new IndexTreeWalker(index
, merge
, root
, new AbstractIndexTreeVisitor() {
209 public void visitEntry(TreeEntry m
, Entry i
, File file
) throws IOException
{
211 if (!file
.isFile()) {
212 checkConflictsWithFile(file
);
216 removed
.add(i
.getName());
217 conflicts
.remove(i
.getName());
222 conflicts
.removeAll(removed
);
225 private ArrayList
<String
> listFiles(File file
) {
226 ArrayList
<String
> list
= new ArrayList
<String
>();
227 listFiles(file
, list
);
231 private void listFiles(File dir
, ArrayList
<String
> list
) {
232 for (File f
: dir
.listFiles()) {
236 list
.add(Repository
.stripWorkDir(root
, f
));
242 * @return a list of conflicts created by this checkout
244 public ArrayList
<String
> getConflicts() {
249 * @return a list of all files removed by this checkout
251 public ArrayList
<String
> getRemoved() {
255 void prescanTwoTrees() throws IOException
{
256 new IndexTreeWalker(index
, head
, merge
, root
, new AbstractIndexTreeVisitor() {
257 public void visitEntry(TreeEntry treeEntry
, TreeEntry auxEntry
,
258 Entry indexEntry
, File file
) throws IOException
{
259 if (treeEntry
instanceof Tree
|| auxEntry
instanceof Tree
) {
260 throw new IllegalArgumentException("Can't pass me a tree!");
262 processEntry(treeEntry
, auxEntry
, indexEntry
);
266 public void finishVisitTree(Tree tree
, Tree auxTree
, String curDir
) throws IOException
{
267 if (curDir
.length() == 0) return;
269 if (auxTree
!= null) {
270 if (index
.getEntry(curDir
) != null)
277 // if there's a conflict, don't list it under
278 // to-be-removed, since that messed up our next
280 removed
.removeAll(conflicts
);
282 for (String path
: updated
.keySet()) {
283 if (index
.getEntry(path
) == null) {
284 File file
= new File(root
, path
);
287 else if (file
.isDirectory()) {
288 checkConflictsWithFile(file
);
294 conflicts
.removeAll(removed
);
297 void processEntry(TreeEntry h
, TreeEntry m
, Entry i
) throws IOException
{
298 ObjectId iId
= (i
== null ?
null : i
.getObjectId());
299 ObjectId mId
= (m
== null ?
null : m
.getId());
300 ObjectId hId
= (h
== null ?
null : h
.getId());
302 String name
= (i
!= null ? i
.getName() :
303 (h
!= null ? h
.getFullName() :
309 -------------------------------------------------------
310 0 nothing nothing nothing (does not happen)
311 1 nothing nothing exists use M
312 2 nothing exists nothing remove path from index
313 3 nothing exists exists use M */
316 updated
.put(name
,mId
);
317 } else if (m
== null) {
320 updated
.put(name
, mId
);
322 } else if (h
== null) {
324 clean I==H I==M H M Result
325 -----------------------------------------------------
326 4 yes N/A N/A nothing nothing keep index
327 5 no N/A N/A nothing nothing keep index
329 6 yes N/A yes nothing exists keep index
330 7 no N/A yes nothing exists keep index
331 8 yes N/A no nothing exists fail
332 9 no N/A no nothing exists fail */
334 if (m
== null || mId
.equals(iId
)) {
335 if (hasParentBlob(merge
, name
)) {
336 if (i
.isModified(root
, true)) {
345 } else if (m
== null) {
347 10 yes yes N/A exists nothing remove path from index
348 11 no yes N/A exists nothing fail
349 12 yes no N/A exists nothing fail
350 13 no no N/A exists nothing fail
353 if (hId
.equals(iId
)) {
354 if (i
.isModified(root
, true)) {
363 if (!hId
.equals(mId
) && !hId
.equals(iId
)
364 && !mId
.equals(iId
)) {
366 } else if (hId
.equals(iId
) && !mId
.equals(iId
)) {
367 if (i
.isModified(root
, true))
369 else updated
.put(name
, mId
);
374 private boolean hasParentBlob(Tree t
, String name
) throws IOException
{
375 if (name
.indexOf("/") == -1) return false;
377 String parent
= name
.substring(0, name
.lastIndexOf("/"));
378 if (t
.findBlobMember(parent
) != null)
380 return hasParentBlob(t
, parent
);
383 private void checkConflictsWithFile(File file
) {
384 if (file
.isDirectory()) {
385 ArrayList
<String
> childFiles
= listFiles(file
);
386 conflicts
.addAll(childFiles
);
388 File parent
= file
.getParentFile();
389 while (!parent
.equals(root
)) {
390 if (parent
.isDirectory())
392 if (parent
.isFile()) {
393 conflicts
.add(Repository
.stripWorkDir(root
, parent
));
396 parent
= parent
.getParentFile();