/* * Copyright (C) 2011, Chris Aniszczyk * Copyright (C) 2011, Abhishek Bhatnagar and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.DOT_GIT; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Set; import java.util.TreeSet; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; /** * Remove untracked files from the working tree * * @see Git documentation about Clean */ public class CleanCommand extends GitCommand> { private Set paths = Collections.emptySet(); private boolean dryRun; private boolean directories; private boolean ignore = true; private boolean force = false; /** * Constructor for CleanCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected CleanCommand(Repository repo) { super(repo); } /** * {@inheritDoc} *

* Executes the {@code clean} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) */ @Override public Set call() throws NoWorkTreeException, GitAPIException { Set files = new TreeSet<>(); try { StatusCommand command = new StatusCommand(repo); Status status = command.call(); Set untrackedFiles = new TreeSet<>(status.getUntracked()); Set untrackedDirs = new TreeSet<>( status.getUntrackedFolders()); FS fs = getRepository().getFS(); for (String p : status.getIgnoredNotInIndex()) { File f = new File(repo.getWorkTree(), p); if (fs.isFile(f) || fs.isSymLink(f)) { untrackedFiles.add(p); } else if (fs.isDirectory(f)) { untrackedDirs.add(p); } } Set filtered = filterFolders(untrackedFiles, untrackedDirs); Set notIgnoredFiles = filterIgnorePaths(filtered, status.getIgnoredNotInIndex(), true); Set notIgnoredDirs = filterIgnorePaths(untrackedDirs, status.getIgnoredNotInIndex(), false); for (String file : notIgnoredFiles) if (paths.isEmpty() || paths.contains(file) || isFileInPaths(file) ) { files = cleanPath(file, files); } for (String dir : notIgnoredDirs) if (paths.isEmpty() || paths.contains(dir) || isFileInPaths(dir) ) { files = cleanPath(dir, files); } } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } finally { if (!dryRun && !files.isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent(null, files)); } } return files; } protected boolean isFileInPaths(final String file) { if(paths.isEmpty()){ return false; } String path=file; final Set dirs = new HashSet(); while(true){ int idx = path.lastIndexOf("/"); if( idx> 0 ){ path=file.substring(0,idx); if(paths.contains(path)){ paths.addAll(dirs); return true; } dirs.add(path); }else{ break; } return idx == -1 ? false : ; } return false; } /** * When dryRun is false, deletes the specified path from disk. If dryRun * is true, no paths are actually deleted. In both cases, the paths that * would have been deleted are added to inFiles and returned. * * Paths that are directories are recursively deleted when * {@link #directories} is true. * Paths that are git repositories are recursively deleted when * {@link #directories} and {@link #force} are both true. * * @param path * The path to be cleaned * @param inFiles * A set of strings representing the files that have been cleaned * already, the path to be cleaned will be added to this set * before being returned. * * @return a set of strings with the cleaned path added to it * @throws IOException */ private Set cleanPath(String path, Set inFiles) throws IOException { File curFile = new File(repo.getWorkTree(), path); if (curFile.isDirectory()) { if (directories) { // Is this directory a git repository? if (new File(curFile, DOT_GIT).exists()) { if (force) { if (!dryRun) { FileUtils.delete(curFile, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); } inFiles.add(path + "/"); //$NON-NLS-1$ } } else { if (!dryRun) { FileUtils.delete(curFile, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); } inFiles.add(path + "/"); //$NON-NLS-1$ } } } else { if (!dryRun) { FileUtils.delete(curFile, FileUtils.SKIP_MISSING); } inFiles.add(path); } return inFiles; } private Set filterIgnorePaths(Set inputPaths, Set ignoredNotInIndex, boolean exact) { if (ignore) { Set filtered = new TreeSet<>(inputPaths); for (String path : inputPaths) for (String ignored : ignoredNotInIndex) if ((exact && path.equals(ignored)) || (!exact && path.startsWith(ignored))) { filtered.remove(path); break; } return filtered; } return inputPaths; } private Set filterFolders(Set untracked, Set untrackedFolders) { Set filtered = new TreeSet<>(untracked); for (String file : untracked) for (String folder : untrackedFolders) if (file.startsWith(folder)) { filtered.remove(file); break; } return filtered; } /** * If paths are set, only these paths are affected by the cleaning. * * @param paths * the paths to set (with / as separator) * @return {@code this} */ public CleanCommand setPaths(Set paths) { this.paths = paths; return this; } /** * If dryRun is set, the paths in question will not actually be deleted. * * @param dryRun * whether to do a dry run or not * @return {@code this} */ public CleanCommand setDryRun(boolean dryRun) { this.dryRun = dryRun; return this; } /** * If force is set, directories that are git repositories will also be * deleted. * * @param force * whether or not to delete git repositories * @return {@code this} * @since 4.5 */ public CleanCommand setForce(boolean force) { this.force = force; return this; } /** * If dirs is set, in addition to files, also clean directories. * * @param dirs * whether to clean directories too, or only files. * @return {@code this} */ public CleanCommand setCleanDirectories(boolean dirs) { directories = dirs; return this; } /** * If ignore is set, don't report/clean files/directories that are ignored * by a .gitignore. otherwise do handle them. * * @param ignore * whether to respect .gitignore or not. * @return {@code this} */ public CleanCommand setIgnore(boolean ignore) { this.ignore = ignore; return this; } }