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.

CleanCommand.java 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. * Copyright (C) 2011, Chris Aniszczyk <zx@redhat.com>
  3. * Copyright (C) 2011, Abhishek Bhatnagar <abhatnag@redhat.com> and others
  4. *
  5. * This program and the accompanying materials are made available under the
  6. * terms of the Eclipse Distribution License v. 1.0 which is available at
  7. * https://www.eclipse.org/org/documents/edl-v10.php.
  8. *
  9. * SPDX-License-Identifier: BSD-3-Clause
  10. */
  11. package org.eclipse.jgit.api;
  12. import static org.eclipse.jgit.lib.Constants.DOT_GIT;
  13. import java.io.File;
  14. import java.io.IOException;
  15. import java.util.Collections;
  16. import java.util.Set;
  17. import java.util.TreeSet;
  18. import org.eclipse.jgit.api.errors.GitAPIException;
  19. import org.eclipse.jgit.api.errors.JGitInternalException;
  20. import org.eclipse.jgit.errors.NoWorkTreeException;
  21. import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
  22. import org.eclipse.jgit.lib.Repository;
  23. import org.eclipse.jgit.util.FS;
  24. import org.eclipse.jgit.util.FileUtils;
  25. /**
  26. * Remove untracked files from the working tree
  27. *
  28. * @see <a
  29. * href="http://www.kernel.org/pub/software/scm/git/docs/git-clean.html"
  30. * >Git documentation about Clean</a>
  31. */
  32. public class CleanCommand extends GitCommand<Set<String>> {
  33. private Set<String> paths = Collections.emptySet();
  34. private boolean dryRun;
  35. private boolean directories;
  36. private boolean ignore = true;
  37. private boolean force = false;
  38. /**
  39. * Constructor for CleanCommand
  40. *
  41. * @param repo
  42. * the {@link org.eclipse.jgit.lib.Repository}
  43. */
  44. protected CleanCommand(Repository repo) {
  45. super(repo);
  46. }
  47. /**
  48. * {@inheritDoc}
  49. * <p>
  50. * Executes the {@code clean} command with all the options and parameters
  51. * collected by the setter methods of this class. Each instance of this
  52. * class should only be used for one invocation of the command (means: one
  53. * call to {@link #call()})
  54. */
  55. @Override
  56. public Set<String> call() throws NoWorkTreeException, GitAPIException {
  57. Set<String> files = new TreeSet<>();
  58. try {
  59. StatusCommand command = new StatusCommand(repo);
  60. Status status = command.call();
  61. Set<String> untrackedFiles = new TreeSet<>(status.getUntracked());
  62. Set<String> untrackedDirs = new TreeSet<>(
  63. status.getUntrackedFolders());
  64. FS fs = getRepository().getFS();
  65. for (String p : status.getIgnoredNotInIndex()) {
  66. File f = new File(repo.getWorkTree(), p);
  67. if (fs.isFile(f) || fs.isSymLink(f)) {
  68. untrackedFiles.add(p);
  69. } else if (fs.isDirectory(f)) {
  70. untrackedDirs.add(p);
  71. }
  72. }
  73. Set<String> filtered = filterFolders(untrackedFiles, untrackedDirs);
  74. Set<String> notIgnoredFiles = filterIgnorePaths(filtered,
  75. status.getIgnoredNotInIndex(), true);
  76. Set<String> notIgnoredDirs = filterIgnorePaths(untrackedDirs,
  77. status.getIgnoredNotInIndex(), false);
  78. for (String file : notIgnoredFiles)
  79. if (paths.isEmpty() || paths.contains(file)) {
  80. files = cleanPath(file, files);
  81. }
  82. for (String dir : notIgnoredDirs)
  83. if (paths.isEmpty() || paths.contains(dir)) {
  84. files = cleanPath(dir, files);
  85. }
  86. } catch (IOException e) {
  87. throw new JGitInternalException(e.getMessage(), e);
  88. } finally {
  89. if (!dryRun && !files.isEmpty()) {
  90. repo.fireEvent(new WorkingTreeModifiedEvent(null, files));
  91. }
  92. }
  93. return files;
  94. }
  95. /**
  96. * When dryRun is false, deletes the specified path from disk. If dryRun
  97. * is true, no paths are actually deleted. In both cases, the paths that
  98. * would have been deleted are added to inFiles and returned.
  99. *
  100. * Paths that are directories are recursively deleted when
  101. * {@link #directories} is true.
  102. * Paths that are git repositories are recursively deleted when
  103. * {@link #directories} and {@link #force} are both true.
  104. *
  105. * @param path
  106. * The path to be cleaned
  107. * @param inFiles
  108. * A set of strings representing the files that have been cleaned
  109. * already, the path to be cleaned will be added to this set
  110. * before being returned.
  111. *
  112. * @return a set of strings with the cleaned path added to it
  113. * @throws IOException
  114. */
  115. private Set<String> cleanPath(String path, Set<String> inFiles)
  116. throws IOException {
  117. File curFile = new File(repo.getWorkTree(), path);
  118. if (curFile.isDirectory()) {
  119. if (directories) {
  120. // Is this directory a git repository?
  121. if (new File(curFile, DOT_GIT).exists()) {
  122. if (force) {
  123. if (!dryRun) {
  124. FileUtils.delete(curFile, FileUtils.RECURSIVE
  125. | FileUtils.SKIP_MISSING);
  126. }
  127. inFiles.add(path + "/"); //$NON-NLS-1$
  128. }
  129. } else {
  130. if (!dryRun) {
  131. FileUtils.delete(curFile,
  132. FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
  133. }
  134. inFiles.add(path + "/"); //$NON-NLS-1$
  135. }
  136. }
  137. } else {
  138. if (!dryRun) {
  139. FileUtils.delete(curFile, FileUtils.SKIP_MISSING);
  140. }
  141. inFiles.add(path);
  142. }
  143. return inFiles;
  144. }
  145. private Set<String> filterIgnorePaths(Set<String> inputPaths,
  146. Set<String> ignoredNotInIndex, boolean exact) {
  147. if (ignore) {
  148. Set<String> filtered = new TreeSet<>(inputPaths);
  149. for (String path : inputPaths)
  150. for (String ignored : ignoredNotInIndex)
  151. if ((exact && path.equals(ignored))
  152. || (!exact && path.startsWith(ignored))) {
  153. filtered.remove(path);
  154. break;
  155. }
  156. return filtered;
  157. }
  158. return inputPaths;
  159. }
  160. private Set<String> filterFolders(Set<String> untracked,
  161. Set<String> untrackedFolders) {
  162. Set<String> filtered = new TreeSet<>(untracked);
  163. for (String file : untracked)
  164. for (String folder : untrackedFolders)
  165. if (file.startsWith(folder)) {
  166. filtered.remove(file);
  167. break;
  168. }
  169. return filtered;
  170. }
  171. /**
  172. * If paths are set, only these paths are affected by the cleaning.
  173. *
  174. * @param paths
  175. * the paths to set (with <code>/</code> as separator)
  176. * @return {@code this}
  177. */
  178. public CleanCommand setPaths(Set<String> paths) {
  179. this.paths = paths;
  180. return this;
  181. }
  182. /**
  183. * If dryRun is set, the paths in question will not actually be deleted.
  184. *
  185. * @param dryRun
  186. * whether to do a dry run or not
  187. * @return {@code this}
  188. */
  189. public CleanCommand setDryRun(boolean dryRun) {
  190. this.dryRun = dryRun;
  191. return this;
  192. }
  193. /**
  194. * If force is set, directories that are git repositories will also be
  195. * deleted.
  196. *
  197. * @param force
  198. * whether or not to delete git repositories
  199. * @return {@code this}
  200. * @since 4.5
  201. */
  202. public CleanCommand setForce(boolean force) {
  203. this.force = force;
  204. return this;
  205. }
  206. /**
  207. * If dirs is set, in addition to files, also clean directories.
  208. *
  209. * @param dirs
  210. * whether to clean directories too, or only files.
  211. * @return {@code this}
  212. */
  213. public CleanCommand setCleanDirectories(boolean dirs) {
  214. directories = dirs;
  215. return this;
  216. }
  217. /**
  218. * If ignore is set, don't report/clean files/directories that are ignored
  219. * by a .gitignore. otherwise do handle them.
  220. *
  221. * @param ignore
  222. * whether to respect .gitignore or not.
  223. * @return {@code this}
  224. */
  225. public CleanCommand setIgnore(boolean ignore) {
  226. this.ignore = ignore;
  227. return this;
  228. }
  229. }