1 /*
2 * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
3 * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>
4 * and other copyright owners as documented in the project's IP log.
5 *
6 * This program and the accompanying materials are made available
7 * under the terms of the Eclipse Distribution License v1.0 which
8 * accompanies this distribution, is reproduced below, and is
9 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 *
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or
14 * without modification, are permitted provided that the following
15 * conditions are met:
16 *
17 * - Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * - Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials provided
23 * with the distribution.
24 *
25 * - Neither the name of the Eclipse Foundation, Inc. nor the
26 * names of its contributors may be used to endorse or promote
27 * products derived from this software without specific prior
28 * written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 */
44 package org.eclipse.jgit.api;
45
46 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
47 import static org.eclipse.jgit.lib.FileMode.GITLINK;
48 import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
49 import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
50
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.time.Instant;
54 import java.util.Collection;
55 import java.util.LinkedList;
56
57 import org.eclipse.jgit.api.errors.FilterFailedException;
58 import org.eclipse.jgit.api.errors.GitAPIException;
59 import org.eclipse.jgit.api.errors.JGitInternalException;
60 import org.eclipse.jgit.api.errors.NoFilepatternException;
61 import org.eclipse.jgit.dircache.DirCache;
62 import org.eclipse.jgit.dircache.DirCacheBuildIterator;
63 import org.eclipse.jgit.dircache.DirCacheBuilder;
64 import org.eclipse.jgit.dircache.DirCacheEntry;
65 import org.eclipse.jgit.dircache.DirCacheIterator;
66 import org.eclipse.jgit.internal.JGitText;
67 import org.eclipse.jgit.lib.FileMode;
68 import org.eclipse.jgit.lib.ObjectId;
69 import org.eclipse.jgit.lib.ObjectInserter;
70 import org.eclipse.jgit.lib.Repository;
71 import org.eclipse.jgit.treewalk.FileTreeIterator;
72 import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
73 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
74 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
75 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
76
77 /**
78 * A class used to execute a {@code Add} command. It has setters for all
79 * supported options and arguments of this command and a {@link #call()} method
80 * to finally execute the command. Each instance of this class should only be
81 * used for one invocation of the command (means: one call to {@link #call()})
82 *
83 * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-add.html"
84 * >Git documentation about Add</a>
85 */
86 public class AddCommand extends GitCommand<DirCache> {
87
88 private Collection<String> filepatterns;
89
90 private WorkingTreeIterator workingTreeIterator;
91
92 private boolean update = false;
93
94 /**
95 * Constructor for AddCommand
96 *
97 * @param repo
98 * the {@link org.eclipse.jgit.lib.Repository}
99 */
100 public AddCommand(Repository repo) {
101 super(repo);
102 filepatterns = new LinkedList<>();
103 }
104
105 /**
106 * Add a path to a file/directory whose content should be added.
107 * <p>
108 * A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and
109 * <code>dir/file2</code>) can also be given to add all files in the
110 * directory, recursively. Fileglobs (e.g. *.c) are not yet supported.
111 *
112 * @param filepattern
113 * repository-relative path of file/directory to add (with
114 * <code>/</code> as separator)
115 * @return {@code this}
116 */
117 public AddCommand addFilepattern(String filepattern) {
118 checkCallable();
119 filepatterns.add(filepattern);
120 return this;
121 }
122
123 /**
124 * Allow clients to provide their own implementation of a FileTreeIterator
125 *
126 * @param f
127 * a {@link org.eclipse.jgit.treewalk.WorkingTreeIterator}
128 * object.
129 * @return {@code this}
130 */
131 public AddCommand setWorkingTreeIterator(WorkingTreeIterator f) {
132 workingTreeIterator = f;
133 return this;
134 }
135
136 /**
137 * {@inheritDoc}
138 * <p>
139 * Executes the {@code Add} command. Each instance of this class should only
140 * be used for one invocation of the command. Don't call this method twice
141 * on an instance.
142 */
143 @Override
144 public DirCache call() throws GitAPIException, NoFilepatternException {
145
146 if (filepatterns.isEmpty())
147 throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
148 checkCallable();
149 DirCache dc = null;
150 boolean addAll = filepatterns.contains("."); //$NON-NLS-1$
151
152 try (ObjectInserter inserter = repo.newObjectInserter();
153 NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
154 tw.setOperationType(OperationType.CHECKIN_OP);
155 dc = repo.lockDirCache();
156
157 DirCacheBuilder builder = dc.builder();
158 tw.addTree(new DirCacheBuildIterator(builder));
159 if (workingTreeIterator == null)
160 workingTreeIterator = new FileTreeIterator(repo);
161 workingTreeIterator.setDirCacheIterator(tw, 0);
162 tw.addTree(workingTreeIterator);
163 if (!addAll)
164 tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
165
166 byte[] lastAdded = null;
167
168 while (tw.next()) {
169 DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
170 WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class);
171 if (c == null && f != null && f.isEntryIgnored()) {
172 // file is not in index but is ignored, do nothing
173 continue;
174 } else if (c == null && update) {
175 // Only update of existing entries was requested.
176 continue;
177 }
178
179 DirCacheEntry entry = c != null ? c.getDirCacheEntry() : null;
180 if (entry != null && entry.getStage() > 0
181 && lastAdded != null
182 && lastAdded.length == tw.getPathLength()
183 && tw.isPathPrefix(lastAdded, lastAdded.length) == 0) {
184 // In case of an existing merge conflict the
185 // DirCacheBuildIterator iterates over all stages of
186 // this path, we however want to add only one
187 // new DirCacheEntry per path.
188 continue;
189 }
190
191 if (tw.isSubtree() && !tw.isDirectoryFileConflict()) {
192 tw.enterSubtree();
193 continue;
194 }
195
196 if (f == null) { // working tree file does not exist
197 if (entry != null
198 && (!update || GITLINK == entry.getFileMode())) {
199 builder.add(entry);
200 }
201 continue;
202 }
203
204 if (entry != null && entry.isAssumeValid()) {
205 // Index entry is marked assume valid. Even though
206 // the user specified the file to be added JGit does
207 // not consider the file for addition.
208 builder.add(entry);
209 continue;
210 }
211
212 if ((f.getEntryRawMode() == TYPE_TREE
213 && f.getIndexFileMode(c) != FileMode.GITLINK) ||
214 (f.getEntryRawMode() == TYPE_GITLINK
215 && f.getIndexFileMode(c) == FileMode.TREE)) {
216 // Index entry exists and is symlink, gitlink or file,
217 // otherwise the tree would have been entered above.
218 // Replace the index entry by diving into tree of files.
219 tw.enterSubtree();
220 continue;
221 }
222
223 byte[] path = tw.getRawPath();
224 if (entry == null || entry.getStage() > 0) {
225 entry = new DirCacheEntry(path);
226 }
227 FileMode mode = f.getIndexFileMode(c);
228 entry.setFileMode(mode);
229
230 if (GITLINK != mode) {
231 entry.setLength(f.getEntryLength());
232 entry.setLastModified(f.getEntryLastModifiedInstant());
233 long len = f.getEntryContentLength();
234 // We read and filter the content multiple times.
235 // f.getEntryContentLength() reads and filters the input and
236 // inserter.insert(...) does it again. That's because an
237 // ObjectInserter needs to know the length before it starts
238 // inserting. TODO: Fix this by using Buffers.
239 try (InputStream in = f.openEntryStream()) {
240 ObjectId id = inserter.insert(OBJ_BLOB, len, in);
241 entry.setObjectId(id);
242 }
243 } else {
244 entry.setLength(0);
245 entry.setLastModified(Instant.ofEpochSecond(0));
246 entry.setObjectId(f.getEntryObjectId());
247 }
248 builder.add(entry);
249 lastAdded = path;
250 }
251 inserter.flush();
252 builder.commit();
253 setCallable(false);
254 } catch (IOException e) {
255 Throwable cause = e.getCause();
256 if (cause != null && cause instanceof FilterFailedException)
257 throw (FilterFailedException) cause;
258 throw new JGitInternalException(
259 JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
260 } finally {
261 if (dc != null)
262 dc.unlock();
263 }
264
265 return dc;
266 }
267
268 /**
269 * Set whether to only match against already tracked files
270 *
271 * @param update
272 * If set to true, the command only matches {@code filepattern}
273 * against already tracked files in the index rather than the
274 * working tree. That means that it will never stage new files,
275 * but that it will stage modified new contents of tracked files
276 * and that it will remove files from the index if the
277 * corresponding files in the working tree have been removed. In
278 * contrast to the git command line a {@code filepattern} must
279 * exist also if update is set to true as there is no concept of
280 * a working directory here.
281 * @return {@code this}
282 */
283 public AddCommand setUpdate(boolean update) {
284 this.update = update;
285 return this;
286 }
287
288 /**
289 * Whether to only match against already tracked files
290 *
291 * @return whether to only match against already tracked files
292 */
293 public boolean isUpdate() {
294 return update;
295 }
296 }