View Javadoc
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 java.io.IOException;
47  import java.io.InputStream;
48  import java.util.Collection;
49  import java.util.LinkedList;
50  
51  import org.eclipse.jgit.api.errors.GitAPIException;
52  import org.eclipse.jgit.api.errors.JGitInternalException;
53  import org.eclipse.jgit.api.errors.NoFilepatternException;
54  import org.eclipse.jgit.dircache.DirCache;
55  import org.eclipse.jgit.dircache.DirCacheBuildIterator;
56  import org.eclipse.jgit.dircache.DirCacheBuilder;
57  import org.eclipse.jgit.dircache.DirCacheEntry;
58  import org.eclipse.jgit.dircache.DirCacheIterator;
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.ObjectInserter;
63  import org.eclipse.jgit.lib.Repository;
64  import org.eclipse.jgit.treewalk.FileTreeIterator;
65  import org.eclipse.jgit.treewalk.TreeWalk;
66  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
67  import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
68  
69  /**
70   * A class used to execute a {@code Add} command. It has setters for all
71   * supported options and arguments of this command and a {@link #call()} method
72   * to finally execute the command. Each instance of this class should only be
73   * used for one invocation of the command (means: one call to {@link #call()})
74   *
75   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-add.html"
76   *      >Git documentation about Add</a>
77   */
78  public class AddCommand extends GitCommand<DirCache> {
79  
80  	private Collection<String> filepatterns;
81  
82  	private WorkingTreeIterator workingTreeIterator;
83  
84  	private boolean update = false;
85  
86  	/**
87  	 *
88  	 * @param repo
89  	 */
90  	public AddCommand(Repository repo) {
91  		super(repo);
92  		filepatterns = new LinkedList<String>();
93  	}
94  
95  	/**
96  	 * Add a path to a file/directory whose content should be added.
97  	 * <p>
98  	 * A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and
99  	 * <code>dir/file2</code>) can also be given to add all files in the
100 	 * directory, recursively. Fileglobs (e.g. *.c) are not yet supported.
101 	 *
102 	 * @param filepattern
103 	 *            repository-relative path of file/directory to add (with
104 	 *            <code>/</code> as separator)
105 	 * @return {@code this}
106 	 */
107 	public AddCommand addFilepattern(String filepattern) {
108 		checkCallable();
109 		filepatterns.add(filepattern);
110 		return this;
111 	}
112 
113 	/**
114 	 * Allow clients to provide their own implementation of a FileTreeIterator
115 	 * @param f
116 	 * @return {@code this}
117 	 */
118 	public AddCommand setWorkingTreeIterator(WorkingTreeIterator f) {
119 		workingTreeIterator = f;
120 		return this;
121 	}
122 
123 	/**
124 	 * Executes the {@code Add} command. Each instance of this class should only
125 	 * be used for one invocation of the command. Don't call this method twice
126 	 * on an instance.
127 	 *
128 	 * @return the DirCache after Add
129 	 */
130 	public DirCache call() throws GitAPIException, NoFilepatternException {
131 
132 		if (filepatterns.isEmpty())
133 			throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
134 		checkCallable();
135 		DirCache dc = null;
136 		boolean addAll = false;
137 		if (filepatterns.contains(".")) //$NON-NLS-1$
138 			addAll = true;
139 
140 		try (ObjectInserter inserter = repo.newObjectInserter();
141 				final TreeWalk tw = new TreeWalk(repo)) {
142 			dc = repo.lockDirCache();
143 			DirCacheIterator c;
144 
145 			DirCacheBuilder builder = dc.builder();
146 			tw.addTree(new DirCacheBuildIterator(builder));
147 			if (workingTreeIterator == null)
148 				workingTreeIterator = new FileTreeIterator(repo);
149 			tw.addTree(workingTreeIterator);
150 			tw.setRecursive(true);
151 			if (!addAll)
152 				tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
153 
154 			String lastAddedFile = null;
155 
156 			while (tw.next()) {
157 				String path = tw.getPathString();
158 
159 				WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class);
160 				if (tw.getTree(0, DirCacheIterator.class) == null &&
161 						f != null && f.isEntryIgnored()) {
162 					// file is not in index but is ignored, do nothing
163 				}
164 				// In case of an existing merge conflict the
165 				// DirCacheBuildIterator iterates over all stages of
166 				// this path, we however want to add only one
167 				// new DirCacheEntry per path.
168 				else if (!(path.equals(lastAddedFile))) {
169 					if (!(update && tw.getTree(0, DirCacheIterator.class) == null)) {
170 						c = tw.getTree(0, DirCacheIterator.class);
171 						if (f != null) { // the file exists
172 							long sz = f.getEntryLength();
173 							DirCacheEntry entry = new DirCacheEntry(path);
174 							if (c == null || c.getDirCacheEntry() == null
175 									|| !c.getDirCacheEntry().isAssumeValid()) {
176 								FileMode mode = f.getIndexFileMode(c);
177 								entry.setFileMode(mode);
178 
179 								if (FileMode.GITLINK != mode) {
180 									entry.setLength(sz);
181 									entry.setLastModified(f
182 											.getEntryLastModified());
183 									long contentSize = f
184 											.getEntryContentLength();
185 									InputStream in = f.openEntryStream();
186 									try {
187 										entry.setObjectId(inserter.insert(
188 												Constants.OBJ_BLOB, contentSize, in));
189 									} finally {
190 										in.close();
191 									}
192 								} else
193 									entry.setObjectId(f.getEntryObjectId());
194 								builder.add(entry);
195 								lastAddedFile = path;
196 							} else {
197 								builder.add(c.getDirCacheEntry());
198 							}
199 
200 						} else if (c != null
201 								&& (!update || FileMode.GITLINK == c
202 										.getEntryFileMode()))
203 							builder.add(c.getDirCacheEntry());
204 					}
205 				}
206 			}
207 			inserter.flush();
208 			builder.commit();
209 			setCallable(false);
210 		} catch (IOException e) {
211 			throw new JGitInternalException(
212 					JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e);
213 		} finally {
214 			if (dc != null)
215 				dc.unlock();
216 		}
217 
218 		return dc;
219 	}
220 
221 	/**
222 	 * @param update
223 	 *            If set to true, the command only matches {@code filepattern}
224 	 *            against already tracked files in the index rather than the
225 	 *            working tree. That means that it will never stage new files,
226 	 *            but that it will stage modified new contents of tracked files
227 	 *            and that it will remove files from the index if the
228 	 *            corresponding files in the working tree have been removed.
229 	 *            In contrast to the git command line a {@code filepattern} must
230 	 *            exist also if update is set to true as there is no
231 	 *            concept of a working directory here.
232 	 *
233 	 * @return {@code this}
234 	 */
235 	public AddCommand setUpdate(boolean update) {
236 		this.update = update;
237 		return this;
238 	}
239 
240 	/**
241 	 * @return is the parameter update is set
242 	 */
243 	public boolean isUpdate() {
244 		return update;
245 	}
246 }