1 /* 2 * Copyright (C) 2010, 2012 Chris Aniszczyk <caniszczyk@gmail.com> 3 * and other copyright owners as documented in the project's IP log. 4 * 5 * This program and the accompanying materials are made available 6 * under the terms of the Eclipse Distribution License v1.0 which 7 * accompanies this distribution, is reproduced below, and is 8 * available at http://www.eclipse.org/org/documents/edl-v10.php 9 * 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or 13 * without modification, are permitted provided that the following 14 * conditions are met: 15 * 16 * - Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * - Redistributions in binary form must reproduce the above 20 * copyright notice, this list of conditions and the following 21 * disclaimer in the documentation and/or other materials provided 22 * with the distribution. 23 * 24 * - Neither the name of the Eclipse Foundation, Inc. nor the 25 * names of its contributors may be used to endorse or promote 26 * products derived from this software without specific prior 27 * written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 package org.eclipse.jgit.api; 44 45 import java.io.File; 46 import java.io.IOException; 47 import java.util.ArrayList; 48 import java.util.Collection; 49 import java.util.LinkedList; 50 import java.util.List; 51 52 import org.eclipse.jgit.api.errors.GitAPIException; 53 import org.eclipse.jgit.api.errors.JGitInternalException; 54 import org.eclipse.jgit.api.errors.NoFilepatternException; 55 import org.eclipse.jgit.dircache.DirCache; 56 import org.eclipse.jgit.dircache.DirCacheBuildIterator; 57 import org.eclipse.jgit.dircache.DirCacheBuilder; 58 import org.eclipse.jgit.events.WorkingTreeModifiedEvent; 59 import org.eclipse.jgit.internal.JGitText; 60 import org.eclipse.jgit.lib.Constants; 61 import org.eclipse.jgit.lib.FileMode; 62 import org.eclipse.jgit.lib.Repository; 63 import org.eclipse.jgit.treewalk.TreeWalk; 64 import org.eclipse.jgit.treewalk.filter.PathFilterGroup; 65 66 /** 67 * Remove files from the index and working directory (or optionally only from 68 * the index). 69 * <p> 70 * It has setters for all supported options and arguments of this command and a 71 * {@link #call()} method to finally execute the command. Each instance of this 72 * class should only be used for one invocation of the command (means: one call 73 * to {@link #call()}). 74 * <p> 75 * Examples (<code>git</code> is a {@link org.eclipse.jgit.api.Git} instance): 76 * <p> 77 * Remove file "test.txt" from both index and working directory: 78 * 79 * <pre> 80 * git.rm().addFilepattern("test.txt").call(); 81 * </pre> 82 * <p> 83 * Remove file "new.txt" from the index (but not from the working directory): 84 * 85 * <pre> 86 * git.rm().setCached(true).addFilepattern("new.txt").call(); 87 * </pre> 88 * 89 * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-rm.html" 90 * >Git documentation about Rm</a> 91 */ 92 public class RmCommand extends GitCommand<DirCache> { 93 94 private Collection<String> filepatterns; 95 96 /** Only remove files from index, not from working directory */ 97 private boolean cached = false; 98 99 /** 100 * Constructor for RmCommand. 101 * 102 * @param repo 103 * the {@link org.eclipse.jgit.lib.Repository} 104 */ 105 public RmCommand(Repository repo) { 106 super(repo); 107 filepatterns = new LinkedList<>(); 108 } 109 110 /** 111 * Add file name pattern of files to be removed 112 * 113 * @param filepattern 114 * repository-relative path of file to remove (with 115 * <code>/</code> as separator) 116 * @return {@code this} 117 */ 118 public RmCommand addFilepattern(String filepattern) { 119 checkCallable(); 120 filepatterns.add(filepattern); 121 return this; 122 } 123 124 /** 125 * Only remove the specified files from the index. 126 * 127 * @param cached 128 * {@code true} if files should only be removed from index, 129 * {@code false} if files should also be deleted from the working 130 * directory 131 * @return {@code this} 132 * @since 2.2 133 */ 134 public RmCommand setCached(boolean cached) { 135 checkCallable(); 136 this.cached = cached; 137 return this; 138 } 139 140 /** 141 * {@inheritDoc} 142 * <p> 143 * Executes the {@code Rm} command. Each instance of this class should only 144 * be used for one invocation of the command. Don't call this method twice 145 * on an instance. 146 */ 147 @Override 148 public DirCache call() throws GitAPIException, 149 NoFilepatternException { 150 151 if (filepatterns.isEmpty()) 152 throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired); 153 checkCallable(); 154 DirCache dc = null; 155 156 List<String> actuallyDeletedFiles = new ArrayList<>(); 157 try (TreeWalk tw = new TreeWalk(repo)) { 158 dc = repo.lockDirCache(); 159 DirCacheBuilder builder = dc.builder(); 160 tw.reset(); // drop the first empty tree, which we do not need here 161 tw.setRecursive(true); 162 tw.setFilter(PathFilterGroup.createFromStrings(filepatterns)); 163 tw.addTree(new DirCacheBuildIterator(builder)); 164 165 while (tw.next()) { 166 if (!cached) { 167 final FileMode mode = tw.getFileMode(0); 168 if (mode.getObjectType() == Constants.OBJ_BLOB) { 169 String relativePath = tw.getPathString(); 170 final File path = new File(repo.getWorkTree(), 171 relativePath); 172 // Deleting a blob is simply a matter of removing 173 // the file or symlink named by the tree entry. 174 if (delete(path)) { 175 actuallyDeletedFiles.add(relativePath); 176 } 177 } 178 } 179 } 180 builder.commit(); 181 setCallable(false); 182 } catch (IOException e) { 183 throw new JGitInternalException( 184 JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e); 185 } finally { 186 try { 187 if (dc != null) { 188 dc.unlock(); 189 } 190 } finally { 191 if (!actuallyDeletedFiles.isEmpty()) { 192 repo.fireEvent(new WorkingTreeModifiedEvent(null, 193 actuallyDeletedFiles)); 194 } 195 } 196 } 197 198 return dc; 199 } 200 201 private boolean delete(File p) { 202 boolean deleted = false; 203 while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) { 204 deleted = true; 205 p = p.getParentFile(); 206 } 207 return deleted; 208 } 209 210 }