2 * Copyright (C) 2007 Dave Watson <dwatson@mimvista.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org
.spearce
.jgit
.lib
;
20 import java
.io
.FileNotFoundException
;
21 import java
.io
.IOException
;
22 import java
.util
.ArrayList
;
23 import java
.util
.HashMap
;
25 import org
.spearce
.jgit
.errors
.CheckoutConflictException
;
26 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
29 * This class handles checking out one or two trees merging
30 * with the index (actually a tree too).
32 * Three-way merges are no performed. See {@link #setFailOnConflict(boolean)}.
34 public class WorkDirCheckout
{
35 protected Repository repo
;
39 protected GitIndex index
;
41 private boolean failOnConflict
= true;
47 * If <code>true</code>, will scan first to see if it's possible to check out,
48 * otherwise throw {@link CheckoutConflictException}. If <code>false</code>,
49 * it will silently deal with the problem.
50 * @param failOnConflict
52 public void setFailOnConflict(boolean failOnConflict
) {
53 this.failOnConflict
= failOnConflict
;
56 WorkDirCheckout(Repository repo
, File workDir
,
57 GitIndex oldIndex
, GitIndex newIndex
) throws IOException
{
60 this.index
= oldIndex
;
61 this.merge
= repo
.mapTree(newIndex
.writeTree());
65 * Create a checkout class for checking out one tree, merging with the index
69 * @param index current index
70 * @param merge tree to check out
72 public WorkDirCheckout(Repository repo
, File root
,
73 GitIndex index
, Tree merge
) {
81 * Create a checkout class for merging and checking our two trees and the index.
89 public WorkDirCheckout(Repository repo
, File root
, Tree head
, GitIndex index
, Tree merge
) {
90 this(repo
, root
, index
, merge
);
95 * Execute this checkout
99 public void checkout() throws IOException
{
102 else prescanTwoTrees();
103 if (!conflicts
.isEmpty()) {
104 if (failOnConflict
) {
105 String
[] entries
= conflicts
.toArray(new String
[0]);
106 throw new CheckoutConflictException(entries
);
112 checkoutOutIndexNoHead();
113 else checkoutTwoTrees();
116 private void checkoutTwoTrees() throws FileNotFoundException
, IOException
{
117 for (String path
: removed
) {
118 index
.remove(root
, new File(root
, path
));
121 for (java
.util
.Map
.Entry
<String
, ObjectId
> entry
: updated
.entrySet()) {
122 Entry newEntry
= index
.addEntry(merge
.findBlobMember(entry
.getKey()));
123 index
.checkoutEntry(root
, newEntry
);
127 ArrayList
<String
> conflicts
= new ArrayList
<String
>();
128 ArrayList
<String
> removed
= new ArrayList
<String
>();
130 protected Tree head
= null;
132 protected HashMap
<String
, ObjectId
> updated
= new HashMap
<String
, ObjectId
>();
134 private void checkoutOutIndexNoHead() throws IOException
{
135 new IndexTreeWalker(index
, merge
, root
, new AbstractIndexTreeVisitor() {
136 public void visitEntry(TreeEntry m
, Entry i
, File f
) throws IOException
{
138 index
.remove(root
, f
);
142 boolean needsCheckout
= false;
144 needsCheckout
= true;
145 else if (i
.getObjectId().equals(m
.getId())) {
146 if (i
.isModified(root
, true))
147 needsCheckout
= true;
148 } else needsCheckout
= true;
151 Entry newEntry
= index
.addEntry(m
);
152 index
.checkoutEntry(root
, newEntry
);
158 private void cleanUpConflicts() throws CheckoutConflictException
{
159 for (String c
: conflicts
) {
160 File conflict
= new File(root
, c
);
161 if (!conflict
.delete())
162 throw new CheckoutConflictException("Cannot delete file: " + c
);
163 removeEmptyParents(conflict
);
165 for (String r
: removed
) {
166 File file
= new File(root
, r
);
168 removeEmptyParents(file
);
172 private void removeEmptyParents(File f
) {
173 File parentFile
= f
.getParentFile();
174 while (!parentFile
.equals(root
)) {
175 if (parentFile
.list().length
== 0)
179 parentFile
= parentFile
.getParentFile();
183 void prescanOneTree() throws IOException
{
184 new IndexTreeWalker(index
, merge
, root
, new AbstractIndexTreeVisitor() {
185 public void visitEntry(TreeEntry m
, Entry i
, File file
) throws IOException
{
187 if (!file
.isFile()) {
188 checkConflictsWithFile(file
);
192 removed
.add(i
.getName());
193 conflicts
.remove(i
.getName());
198 conflicts
.removeAll(removed
);
201 private ArrayList
<String
> listFiles(File file
) {
202 ArrayList
<String
> list
= new ArrayList
<String
>();
203 listFiles(file
, list
);
207 private void listFiles(File dir
, ArrayList
<String
> list
) {
208 for (File f
: dir
.listFiles()) {
212 list
.add(Repository
.stripWorkDir(root
, f
));
218 * @return a list of conflicts created by this checkout
220 public ArrayList
<String
> getConflicts() {
225 * @return a list of all files removed by this checkout
227 public ArrayList
<String
> getRemoved() {
231 void prescanTwoTrees() throws IOException
{
232 new IndexTreeWalker(index
, head
, merge
, root
, new AbstractIndexTreeVisitor() {
233 public void visitEntry(TreeEntry treeEntry
, TreeEntry auxEntry
,
234 Entry indexEntry
, File file
) throws IOException
{
235 if (treeEntry
instanceof Tree
|| auxEntry
instanceof Tree
) {
236 throw new IllegalArgumentException("Can't pass me a tree!");
238 processEntry(treeEntry
, auxEntry
, indexEntry
, file
);
242 public void finishVisitTree(Tree tree
, Tree auxTree
, String curDir
) throws IOException
{
243 if (curDir
.length() == 0) return;
245 if (auxTree
!= null) {
246 if (index
.getEntry(curDir
) != null)
253 // if there's a conflict, don't list it under
254 // to-be-removed, since that messed up our next
256 removed
.removeAll(conflicts
);
258 for (String path
: updated
.keySet()) {
259 if (index
.getEntry(path
) == null) {
260 File file
= new File(root
, path
);
263 else if (file
.isDirectory()) {
264 checkConflictsWithFile(file
);
270 conflicts
.removeAll(removed
);
273 protected void processEntry(TreeEntry h
, TreeEntry m
, Entry i
,
274 File file
) throws IOException
{
275 ObjectId iId
= (i
== null ?
null : i
.getObjectId());
276 ObjectId mId
= (m
== null ?
null : m
.getId());
277 ObjectId hId
= (h
== null ?
null : h
.getId());
279 String name
= (i
!= null ? i
.getName() :
280 (h
!= null ? h
.getFullName() :
286 -------------------------------------------------------
287 0 nothing nothing nothing (does not happen)
288 1 nothing nothing exists use M
289 2 nothing exists nothing remove path from index
290 3 nothing exists exists use M */
293 updated
.put(name
,mId
);
294 } else if (m
== null) {
297 updated
.put(name
, mId
);
299 } else if (h
== null) {
301 clean I==H I==M H M Result
302 -----------------------------------------------------
303 4 yes N/A N/A nothing nothing keep index
304 5 no N/A N/A nothing nothing keep index
306 6 yes N/A yes nothing exists keep index
307 7 no N/A yes nothing exists keep index
308 8 yes N/A no nothing exists fail
309 9 no N/A no nothing exists fail */
311 if (m
== null || mId
.equals(iId
)) {
312 if (hasParentBlob(merge
, name
)) {
313 if (i
.isModified(root
, true)) {
322 } else if (m
== null) {
324 10 yes yes N/A exists nothing remove path from index
325 11 no yes N/A exists nothing fail
326 12 yes no N/A exists nothing fail
327 13 no no N/A exists nothing fail
330 if (hId
.equals(iId
)) {
331 if (i
.isModified(root
, true)) {
340 if (!hId
.equals(mId
) && !hId
.equals(iId
)
341 && !mId
.equals(iId
)) {
343 } else if (hId
.equals(iId
) && !mId
.equals(iId
)) {
344 if (i
.isModified(root
, true))
346 else updated
.put(name
, mId
);
351 private boolean hasParentBlob(Tree t
, String name
) throws IOException
{
352 if (name
.indexOf("/") == -1) return false;
354 String parent
= name
.substring(0, name
.lastIndexOf("/"));
355 if (t
.findBlobMember(parent
) != null)
357 return hasParentBlob(t
, parent
);
360 private void checkConflictsWithFile(File file
) {
361 if (file
.isDirectory()) {
362 ArrayList
<String
> childFiles
= listFiles(file
);
363 conflicts
.addAll(childFiles
);
365 File parent
= file
.getParentFile();
366 while (!parent
.equals(root
)) {
367 if (parent
.isDirectory())
369 if (parent
.isFile()) {
370 conflicts
.add(Repository
.stripWorkDir(root
, parent
));
373 parent
= parent
.getParentFile();