View Javadoc
1   /*
2    * Copyright (C) 2010, 2012 Chris Aniszczyk <caniszczyk@gmail.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.api;
11  
12  import java.io.File;
13  import java.io.IOException;
14  import java.util.ArrayList;
15  import java.util.Collection;
16  import java.util.LinkedList;
17  import java.util.List;
18  
19  import org.eclipse.jgit.api.errors.GitAPIException;
20  import org.eclipse.jgit.api.errors.JGitInternalException;
21  import org.eclipse.jgit.api.errors.NoFilepatternException;
22  import org.eclipse.jgit.dircache.DirCache;
23  import org.eclipse.jgit.dircache.DirCacheBuildIterator;
24  import org.eclipse.jgit.dircache.DirCacheBuilder;
25  import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
26  import org.eclipse.jgit.internal.JGitText;
27  import org.eclipse.jgit.lib.Constants;
28  import org.eclipse.jgit.lib.FileMode;
29  import org.eclipse.jgit.lib.Repository;
30  import org.eclipse.jgit.treewalk.TreeWalk;
31  import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
32  
33  /**
34   * Remove files from the index and working directory (or optionally only from
35   * the index).
36   * <p>
37   * It has setters for all supported options and arguments of this command and a
38   * {@link #call()} method to finally execute the command. Each instance of this
39   * class should only be used for one invocation of the command (means: one call
40   * to {@link #call()}).
41   * <p>
42   * Examples (<code>git</code> is a {@link org.eclipse.jgit.api.Git} instance):
43   * <p>
44   * Remove file "test.txt" from both index and working directory:
45   *
46   * <pre>
47   * git.rm().addFilepattern(&quot;test.txt&quot;).call();
48   * </pre>
49   * <p>
50   * Remove file "new.txt" from the index (but not from the working directory):
51   *
52   * <pre>
53   * git.rm().setCached(true).addFilepattern(&quot;new.txt&quot;).call();
54   * </pre>
55   *
56   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-rm.html"
57   *      >Git documentation about Rm</a>
58   */
59  public class RmCommand extends GitCommand<DirCache> {
60  
61  	private Collection<String> filepatterns;
62  
63  	/** Only remove files from index, not from working directory */
64  	private boolean cached = false;
65  
66  	/**
67  	 * Constructor for RmCommand.
68  	 *
69  	 * @param repo
70  	 *            the {@link org.eclipse.jgit.lib.Repository}
71  	 */
72  	public RmCommand(Repository repo) {
73  		super(repo);
74  		filepatterns = new LinkedList<>();
75  	}
76  
77  	/**
78  	 * Add file name pattern of files to be removed
79  	 *
80  	 * @param filepattern
81  	 *            repository-relative path of file to remove (with
82  	 *            <code>/</code> as separator)
83  	 * @return {@code this}
84  	 */
85  	public RmCommand addFilepattern(String filepattern) {
86  		checkCallable();
87  		filepatterns.add(filepattern);
88  		return this;
89  	}
90  
91  	/**
92  	 * Only remove the specified files from the index.
93  	 *
94  	 * @param cached
95  	 *            {@code true} if files should only be removed from index,
96  	 *            {@code false} if files should also be deleted from the working
97  	 *            directory
98  	 * @return {@code this}
99  	 * @since 2.2
100 	 */
101 	public RmCommand setCached(boolean cached) {
102 		checkCallable();
103 		this.cached = cached;
104 		return this;
105 	}
106 
107 	/**
108 	 * {@inheritDoc}
109 	 * <p>
110 	 * Executes the {@code Rm} command. Each instance of this class should only
111 	 * be used for one invocation of the command. Don't call this method twice
112 	 * on an instance.
113 	 */
114 	@Override
115 	public DirCache call() throws GitAPIException,
116 			NoFilepatternException {
117 
118 		if (filepatterns.isEmpty())
119 			throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
120 		checkCallable();
121 		DirCache dc = null;
122 
123 		List<String> actuallyDeletedFiles = new ArrayList<>();
124 		try (TreeWalkTreeWalk.html#TreeWalk">TreeWalk tw = new TreeWalk(repo)) {
125 			dc = repo.lockDirCache();
126 			DirCacheBuilder builder = dc.builder();
127 			tw.reset(); // drop the first empty tree, which we do not need here
128 			tw.setRecursive(true);
129 			tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
130 			tw.addTree(new DirCacheBuildIterator(builder));
131 
132 			while (tw.next()) {
133 				if (!cached) {
134 					final FileMode mode = tw.getFileMode(0);
135 					if (mode.getObjectType() == Constants.OBJ_BLOB) {
136 						String relativePath = tw.getPathString();
137 						final File path = new File(repo.getWorkTree(),
138 								relativePath);
139 						// Deleting a blob is simply a matter of removing
140 						// the file or symlink named by the tree entry.
141 						if (delete(path)) {
142 							actuallyDeletedFiles.add(relativePath);
143 						}
144 					}
145 				}
146 			}
147 			builder.commit();
148 			setCallable(false);
149 		} catch (IOException e) {
150 			throw new JGitInternalException(
151 					JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e);
152 		} finally {
153 			try {
154 				if (dc != null) {
155 					dc.unlock();
156 				}
157 			} finally {
158 				if (!actuallyDeletedFiles.isEmpty()) {
159 					repo.fireEvent(new WorkingTreeModifiedEvent(null,
160 							actuallyDeletedFiles));
161 				}
162 			}
163 		}
164 
165 		return dc;
166 	}
167 
168 	private boolean delete(File p) {
169 		boolean deleted = false;
170 		while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) {
171 			deleted = true;
172 			p = p.getParentFile();
173 		}
174 		return deleted;
175 	}
176 
177 }