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 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 * 101 * @param repo 102 */ 103 public RmCommand(Repository repo) { 104 super(repo); 105 filepatterns = new LinkedList<>(); 106 } 107 108 /** 109 * @param filepattern 110 * repository-relative path of file to remove (with 111 * <code>/</code> as separator) 112 * @return {@code this} 113 */ 114 public RmCommand addFilepattern(String filepattern) { 115 checkCallable(); 116 filepatterns.add(filepattern); 117 return this; 118 } 119 120 /** 121 * Only remove the specified files from the index. 122 * 123 * @param cached 124 * true if files should only be removed from index, false if 125 * files should also be deleted from the working directory 126 * @return {@code this} 127 * @since 2.2 128 */ 129 public RmCommand setCached(boolean cached) { 130 checkCallable(); 131 this.cached = cached; 132 return this; 133 } 134 135 /** 136 * Executes the {@code Rm} command. Each instance of this class should only 137 * be used for one invocation of the command. Don't call this method twice 138 * on an instance. 139 * 140 * @return the DirCache after Rm 141 */ 142 @Override 143 public DirCache call() throws GitAPIException, 144 NoFilepatternException { 145 146 if (filepatterns.isEmpty()) 147 throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired); 148 checkCallable(); 149 DirCache dc = null; 150 151 List<String> actuallyDeletedFiles = new ArrayList<>(); 152 try (final TreeWalk tw = new TreeWalk(repo)) { 153 dc = repo.lockDirCache(); 154 DirCacheBuilder builder = dc.builder(); 155 tw.reset(); // drop the first empty tree, which we do not need here 156 tw.setRecursive(true); 157 tw.setFilter(PathFilterGroup.createFromStrings(filepatterns)); 158 tw.addTree(new DirCacheBuildIterator(builder)); 159 160 while (tw.next()) { 161 if (!cached) { 162 final FileMode mode = tw.getFileMode(0); 163 if (mode.getObjectType() == Constants.OBJ_BLOB) { 164 String relativePath = tw.getPathString(); 165 final File path = new File(repo.getWorkTree(), 166 relativePath); 167 // Deleting a blob is simply a matter of removing 168 // the file or symlink named by the tree entry. 169 if (delete(path)) { 170 actuallyDeletedFiles.add(relativePath); 171 } 172 } 173 } 174 } 175 builder.commit(); 176 setCallable(false); 177 } catch (IOException e) { 178 throw new JGitInternalException( 179 JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e); 180 } finally { 181 try { 182 if (dc != null) { 183 dc.unlock(); 184 } 185 } finally { 186 if (!actuallyDeletedFiles.isEmpty()) { 187 repo.fireEvent(new WorkingTreeModifiedEvent(null, 188 actuallyDeletedFiles)); 189 } 190 } 191 } 192 193 return dc; 194 } 195 196 private boolean delete(File p) { 197 boolean deleted = false; 198 while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) { 199 deleted = true; 200 p = p.getParentFile(); 201 } 202 return deleted; 203 } 204 205 }