View Javadoc
1   /*
2    * Copyright (C) 2011, Chris Aniszczyk <zx@redhat.com>
3    * Copyright (C) 2011, Abhishek Bhatnagar <abhatnag@redhat.com>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  package org.eclipse.jgit.api;
45  
46  import java.io.File;
47  import java.io.IOException;
48  import java.util.Collections;
49  import java.util.Set;
50  import java.util.TreeSet;
51  
52  import org.eclipse.jgit.api.errors.GitAPIException;
53  import org.eclipse.jgit.api.errors.JGitInternalException;
54  import org.eclipse.jgit.errors.NoWorkTreeException;
55  import org.eclipse.jgit.lib.Repository;
56  import org.eclipse.jgit.util.FS;
57  import org.eclipse.jgit.util.FileUtils;
58  
59  /**
60   * Remove untracked files from the working tree
61   *
62   * @see <a
63   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-clean.html"
64   *      >Git documentation about Clean</a>
65   */
66  public class CleanCommand extends GitCommand<Set<String>> {
67  
68  	private Set<String> paths = Collections.emptySet();
69  
70  	private boolean dryRun;
71  
72  	private boolean directories;
73  
74  	private boolean ignore = true;
75  
76  	/**
77  	 * @param repo
78  	 */
79  	protected CleanCommand(Repository repo) {
80  		super(repo);
81  	}
82  
83  	/**
84  	 * Executes the {@code clean} command with all the options and parameters
85  	 * collected by the setter methods of this class. Each instance of this
86  	 * class should only be used for one invocation of the command (means: one
87  	 * call to {@link #call()})
88  	 *
89  	 * @return a set of strings representing each file cleaned.
90  	 * @throws GitAPIException
91  	 * @throws NoWorkTreeException
92  	 */
93  	public Set<String> call() throws NoWorkTreeException, GitAPIException {
94  		Set<String> files = new TreeSet<String>();
95  		try {
96  			StatusCommand command = new StatusCommand(repo);
97  			Status status = command.call();
98  
99  			Set<String> untrackedAndIgnoredFiles = new TreeSet<String>(
100 					status.getUntracked());
101 			Set<String> untrackedAndIgnoredDirs = new TreeSet<String>(
102 					status.getUntrackedFolders());
103 
104 			FS fs = getRepository().getFS();
105 			for (String p : status.getIgnoredNotInIndex()) {
106 				File f = new File(repo.getWorkTree(), p);
107 				if (fs.isFile(f) || fs.isSymLink(f))
108 					untrackedAndIgnoredFiles.add(p);
109 				else if (fs.isDirectory(f))
110 					untrackedAndIgnoredDirs.add(p);
111 			}
112 
113 			Set<String> filtered = filterFolders(untrackedAndIgnoredFiles,
114 					untrackedAndIgnoredDirs);
115 
116 			Set<String> notIgnoredFiles = filterIgnorePaths(filtered,
117 					status.getIgnoredNotInIndex(), true);
118 			Set<String> notIgnoredDirs = filterIgnorePaths(
119 					untrackedAndIgnoredDirs,
120 					status.getIgnoredNotInIndex(), false);
121 
122 			for (String file : notIgnoredFiles)
123 				if (paths.isEmpty() || paths.contains(file)) {
124 					if (!dryRun)
125 						FileUtils.delete(new File(repo.getWorkTree(), file));
126 					files.add(file);
127 				}
128 
129 			if (directories)
130 				for (String dir : notIgnoredDirs)
131 					if (paths.isEmpty() || paths.contains(dir)) {
132 						if (!dryRun)
133 							FileUtils.delete(new File(repo.getWorkTree(), dir),
134 									FileUtils.RECURSIVE);
135 						files.add(dir + "/"); //$NON-NLS-1$
136 					}
137 		} catch (IOException e) {
138 			throw new JGitInternalException(e.getMessage(), e);
139 		}
140 		return files;
141 	}
142 
143 	private Set<String> filterIgnorePaths(Set<String> inputPaths,
144 			Set<String> ignoredNotInIndex, boolean exact) {
145 		if (ignore) {
146 			Set<String> filtered = new TreeSet<String>(inputPaths);
147 			for (String path : inputPaths)
148 				for (String ignored : ignoredNotInIndex)
149 					if ((exact && path.equals(ignored))
150 							|| (!exact && path.startsWith(ignored))) {
151 						filtered.remove(path);
152 						break;
153 					}
154 
155 			return filtered;
156 		}
157 		return inputPaths;
158 	}
159 
160 	private Set<String> filterFolders(Set<String> untracked,
161 			Set<String> untrackedFolders) {
162 		Set<String> filtered = new TreeSet<String>(untracked);
163 		for (String file : untracked)
164 			for (String folder : untrackedFolders)
165 				if (file.startsWith(folder)) {
166 					filtered.remove(file);
167 					break;
168 				}
169 
170 
171 		return filtered;
172 	}
173 
174 	/**
175 	 * If paths are set, only these paths are affected by the cleaning.
176 	 *
177 	 * @param paths
178 	 *            the paths to set (with <code>/</code> as separator)
179 	 * @return {@code this}
180 	 */
181 	public CleanCommand setPaths(Set<String> paths) {
182 		this.paths = paths;
183 		return this;
184 	}
185 
186 	/**
187 	 * If dryRun is set, the paths in question will not actually be deleted.
188 	 *
189 	 * @param dryRun
190 	 *            whether to do a dry run or not
191 	 * @return {@code this}
192 	 */
193 	public CleanCommand setDryRun(boolean dryRun) {
194 		this.dryRun = dryRun;
195 		return this;
196 	}
197 
198 	/**
199 	 * If dirs is set, in addition to files, also clean directories.
200 	 *
201 	 * @param dirs
202 	 *            whether to clean directories too, or only files.
203 	 * @return {@code this}
204 	 */
205 	public CleanCommand setCleanDirectories(boolean dirs) {
206 		directories = dirs;
207 		return this;
208 	}
209 
210 	/**
211 	 * If ignore is set, don't report/clean files/directories that are ignored
212 	 * by a .gitignore. otherwise do handle them.
213 	 *
214 	 * @param ignore
215 	 *            whether to respect .gitignore or not.
216 	 * @return {@code this}
217 	 */
218 	public CleanCommand setIgnore(boolean ignore) {
219 		this.ignore = ignore;
220 		return this;
221 	}
222 }