View Javadoc
1   /*
2    * Copyright (C) 2011, Tomasz Zarna <Tomasz.Zarna@pl.ibm.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 static org.eclipse.jgit.lib.Constants.HEAD;
13  
14  import java.io.BufferedOutputStream;
15  import java.io.IOException;
16  import java.io.OutputStream;
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.NoHeadException;
22  import org.eclipse.jgit.diff.DiffEntry;
23  import org.eclipse.jgit.diff.DiffFormatter;
24  import org.eclipse.jgit.dircache.DirCacheIterator;
25  import org.eclipse.jgit.internal.JGitText;
26  import org.eclipse.jgit.lib.NullProgressMonitor;
27  import org.eclipse.jgit.lib.ObjectId;
28  import org.eclipse.jgit.lib.ObjectReader;
29  import org.eclipse.jgit.lib.ProgressMonitor;
30  import org.eclipse.jgit.lib.Repository;
31  import org.eclipse.jgit.treewalk.AbstractTreeIterator;
32  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
33  import org.eclipse.jgit.treewalk.FileTreeIterator;
34  import org.eclipse.jgit.treewalk.filter.TreeFilter;
35  import org.eclipse.jgit.util.io.NullOutputStream;
36  
37  /**
38   * Show changes between commits, commit and working tree, etc.
39   *
40   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-diff.html"
41   *      >Git documentation about diff</a>
42   */
43  public class DiffCommand extends GitCommand<List<DiffEntry>> {
44  	private AbstractTreeIterator oldTree;
45  
46  	private AbstractTreeIterator newTree;
47  
48  	private boolean cached;
49  
50  	private TreeFilter pathFilter = TreeFilter.ALL;
51  
52  	private boolean showNameAndStatusOnly;
53  
54  	private OutputStream out;
55  
56  	private int contextLines = -1;
57  
58  	private String sourcePrefix;
59  
60  	private String destinationPrefix;
61  
62  	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
63  
64  	/**
65  	 * Constructor for DiffCommand
66  	 *
67  	 * @param repo
68  	 *            a {@link org.eclipse.jgit.lib.Repository} object.
69  	 */
70  	protected DiffCommand(Repository repo) {
71  		super(repo);
72  	}
73  
74  	private DiffFormatter getDiffFormatter() {
75  		return out != null && !showNameAndStatusOnly
76  				? new DiffFormatter(new BufferedOutputStream(out))
77  				: new DiffFormatter(NullOutputStream.INSTANCE);
78  	}
79  
80  	/**
81  	 * {@inheritDoc}
82  	 * <p>
83  	 * Executes the {@code Diff} command with all the options and parameters
84  	 * collected by the setter methods (e.g. {@link #setCached(boolean)} of this
85  	 * class. Each instance of this class should only be used for one invocation
86  	 * of the command. Don't call this method twice on an instance.
87  	 */
88  	@Override
89  	public List<DiffEntry> call() throws GitAPIException {
90  		try (DiffFormatter diffFmt = getDiffFormatter()) {
91  			diffFmt.setRepository(repo);
92  			diffFmt.setProgressMonitor(monitor);
93  			if (cached) {
94  				if (oldTree == null) {
95  					ObjectId head = repo.resolve(HEAD + "^{tree}"); //$NON-NLS-1$
96  					if (head == null)
97  						throw new NoHeadException(JGitText.get().cannotReadTree);
98  					CanonicalTreeParser p = new CanonicalTreeParser();
99  					try (ObjectReader reader = repo.newObjectReader()) {
100 						p.reset(reader, head);
101 					}
102 					oldTree = p;
103 				}
104 				newTree = new DirCacheIterator(repo.readDirCache());
105 			} else {
106 				if (oldTree == null) {
107 					oldTree = new DirCacheIterator(repo.readDirCache());
108 				}
109 				if (newTree == null) {
110 					newTree = new FileTreeIterator(repo);
111 				}
112 			}
113 
114 			diffFmt.setPathFilter(pathFilter);
115 
116 			List<DiffEntry> result = diffFmt.scan(oldTree, newTree);
117 			if (showNameAndStatusOnly) {
118 				return result;
119 			}
120 			if (contextLines >= 0) {
121 				diffFmt.setContext(contextLines);
122 			}
123 			if (destinationPrefix != null) {
124 				diffFmt.setNewPrefix(destinationPrefix);
125 			}
126 			if (sourcePrefix != null) {
127 				diffFmt.setOldPrefix(sourcePrefix);
128 			}
129 			diffFmt.format(result);
130 			diffFmt.flush();
131 			return result;
132 		} catch (IOException e) {
133 			throw new JGitInternalException(e.getMessage(), e);
134 		}
135 	}
136 
137 	/**
138 	 * Whether to view the changes staged for the next commit
139 	 *
140 	 * @param cached
141 	 *            whether to view the changes staged for the next commit
142 	 * @return this instance
143 	 */
144 	public DiffCommand setCached(boolean cached) {
145 		this.cached = cached;
146 		return this;
147 	}
148 
149 	/**
150 	 * Set path filter
151 	 *
152 	 * @param pathFilter
153 	 *            parameter, used to limit the diff to the named path
154 	 * @return this instance
155 	 */
156 	public DiffCommand setPathFilter(TreeFilter pathFilter) {
157 		this.pathFilter = pathFilter;
158 		return this;
159 	}
160 
161 	/**
162 	 * Set old tree
163 	 *
164 	 * @param oldTree
165 	 *            the previous state
166 	 * @return this instance
167 	 */
168 	public DiffCommand setOldTree(AbstractTreeIterator oldTree) {
169 		this.oldTree = oldTree;
170 		return this;
171 	}
172 
173 	/**
174 	 * Set new tree
175 	 *
176 	 * @param newTree
177 	 *            the updated state
178 	 * @return this instance
179 	 */
180 	public DiffCommand setNewTree(AbstractTreeIterator newTree) {
181 		this.newTree = newTree;
182 		return this;
183 	}
184 
185 	/**
186 	 * Set whether to return only names and status of changed files
187 	 *
188 	 * @param showNameAndStatusOnly
189 	 *            whether to return only names and status of changed files
190 	 * @return this instance
191 	 */
192 	public DiffCommand setShowNameAndStatusOnly(boolean showNameAndStatusOnly) {
193 		this.showNameAndStatusOnly = showNameAndStatusOnly;
194 		return this;
195 	}
196 
197 	/**
198 	 * Set output stream
199 	 *
200 	 * @param out
201 	 *            the stream to write line data
202 	 * @return this instance
203 	 */
204 	public DiffCommand setOutputStream(OutputStream out) {
205 		this.out = out;
206 		return this;
207 	}
208 
209 	/**
210 	 * Set number of context lines instead of the usual three.
211 	 *
212 	 * @param contextLines
213 	 *            the number of context lines
214 	 * @return this instance
215 	 */
216 	public DiffCommand setContextLines(int contextLines) {
217 		this.contextLines = contextLines;
218 		return this;
219 	}
220 
221 	/**
222 	 * Set the given source prefix instead of "a/".
223 	 *
224 	 * @param sourcePrefix
225 	 *            the prefix
226 	 * @return this instance
227 	 */
228 	public DiffCommand setSourcePrefix(String sourcePrefix) {
229 		this.sourcePrefix = sourcePrefix;
230 		return this;
231 	}
232 
233 	/**
234 	 * Set the given destination prefix instead of "b/".
235 	 *
236 	 * @param destinationPrefix
237 	 *            the prefix
238 	 * @return this instance
239 	 */
240 	public DiffCommand setDestinationPrefix(String destinationPrefix) {
241 		this.destinationPrefix = destinationPrefix;
242 		return this;
243 	}
244 
245 	/**
246 	 * The progress monitor associated with the diff operation. By default, this
247 	 * is set to <code>NullProgressMonitor</code>
248 	 *
249 	 * @see NullProgressMonitor
250 	 * @param monitor
251 	 *            a progress monitor
252 	 * @return this instance
253 	 */
254 	public DiffCommand setProgressMonitor(ProgressMonitor monitor) {
255 		if (monitor == null) {
256 			monitor = NullProgressMonitor.INSTANCE;
257 		}
258 		this.monitor = monitor;
259 		return this;
260 	}
261 }