View Javadoc
1   /*
2    * Copyright (C) 2011, 2015 François Rey <eclipse.org_@_francois_._rey_._name>
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  
44  package org.eclipse.jgit.pgm;
45  
46  import java.io.IOException;
47  import java.text.MessageFormat;
48  import java.util.ArrayList;
49  import java.util.Collection;
50  import java.util.Collections;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.TreeSet;
54  
55  import org.eclipse.jgit.api.Git;
56  import org.eclipse.jgit.api.StatusCommand;
57  import org.eclipse.jgit.api.errors.GitAPIException;
58  import org.eclipse.jgit.errors.NoWorkTreeException;
59  import org.eclipse.jgit.lib.Constants;
60  import org.eclipse.jgit.lib.IndexDiff.StageState;
61  import org.eclipse.jgit.lib.Ref;
62  import org.eclipse.jgit.lib.Repository;
63  import org.eclipse.jgit.pgm.internal.CLIText;
64  import org.eclipse.jgit.pgm.opt.UntrackedFilesHandler;
65  import org.kohsuke.args4j.Argument;
66  import org.kohsuke.args4j.Option;
67  import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
68  
69  /**
70   * Status command
71   */
72  @Command(usage = "usage_Status", common = true)
73  class Status extends TextBuiltin {
74  
75  	protected final String statusFileListFormat = CLIText.get().statusFileListFormat;
76  
77  	protected final String statusFileListFormatWithPrefix = CLIText.get().statusFileListFormatWithPrefix;
78  
79  	protected final String statusFileListFormatUnmerged = CLIText.get().statusFileListFormatUnmerged;
80  
81  	@Option(name = "--porcelain", usage = "usage_machineReadableOutput")
82  	protected boolean porcelain;
83  
84  	@Option(name = "--untracked-files", aliases = { "-u", "-uno", "-uall" }, usage = "usage_untrackedFilesMode", handler = UntrackedFilesHandler.class)
85  	protected String untrackedFilesMode = "all"; // default value //$NON-NLS-1$
86  
87  	@Argument(required = false, index = 0, metaVar = "metaVar_paths")
88  	@Option(name = "--", metaVar = "metaVar_paths", handler = RestOfArgumentsHandler.class)
89  	protected List<String> filterPaths;
90  
91  	/** {@inheritDoc} */
92  	@Override
93  	protected void run() {
94  		try (Git git = new Git(db)) {
95  			StatusCommand statusCommand = git.status();
96  			if (filterPaths != null && filterPaths.size() > 0) {
97  				for (String path : filterPaths) {
98  					statusCommand.addPath(path);
99  				}
100 			}
101 			org.eclipse.jgit.api.Status status = statusCommand.call();
102 			printStatus(status);
103 		} catch (GitAPIException | NoWorkTreeException | IOException e) {
104 			throw die(e.getMessage(), e);
105 		}
106 	}
107 
108 	private void printStatus(org.eclipse.jgit.api.Status status)
109 			throws IOException {
110 		if (porcelain)
111 			printPorcelainStatus(status);
112 		else
113 			printLongStatus(status);
114 	}
115 
116 	private void printPorcelainStatus(org.eclipse.jgit.api.Status status)
117 			throws IOException {
118 
119 		Collection<String> added = status.getAdded();
120 		Collection<String> changed = status.getChanged();
121 		Collection<String> removed = status.getRemoved();
122 		Collection<String> modified = status.getModified();
123 		Collection<String> missing = status.getMissing();
124 		Map<String, StageState> conflicting = status.getConflictingStageState();
125 
126 		// build a sorted list of all paths except untracked and ignored
127 		TreeSet<String> sorted = new TreeSet<>();
128 		sorted.addAll(added);
129 		sorted.addAll(changed);
130 		sorted.addAll(removed);
131 		sorted.addAll(modified);
132 		sorted.addAll(missing);
133 		sorted.addAll(conflicting.keySet());
134 
135 		// list each path
136 		for (String path : sorted) {
137 			char x = ' ';
138 			char y = ' ';
139 
140 			if (added.contains(path))
141 				x = 'A';
142 			else if (changed.contains(path))
143 				x = 'M';
144 			else if (removed.contains(path))
145 				x = 'D';
146 
147 			if (modified.contains(path))
148 				y = 'M';
149 			else if (missing.contains(path))
150 				y = 'D';
151 
152 			if (conflicting.containsKey(path)) {
153 				StageState stageState = conflicting.get(path);
154 
155 				switch (stageState) {
156 				case BOTH_DELETED:
157 					x = 'D';
158 					y = 'D';
159 					break;
160 				case ADDED_BY_US:
161 					x = 'A';
162 					y = 'U';
163 					break;
164 				case DELETED_BY_THEM:
165 					x = 'U';
166 					y = 'D';
167 					break;
168 				case ADDED_BY_THEM:
169 					x = 'U';
170 					y = 'A';
171 					break;
172 				case DELETED_BY_US:
173 					x = 'D';
174 					y = 'U';
175 					break;
176 				case BOTH_ADDED:
177 					x = 'A';
178 					y = 'A';
179 					break;
180 				case BOTH_MODIFIED:
181 					x = 'U';
182 					y = 'U';
183 					break;
184 				default:
185 					throw new IllegalArgumentException("Unknown StageState: " //$NON-NLS-1$
186 							+ stageState);
187 				}
188 			}
189 
190 			printPorcelainLine(x, y, path);
191 		}
192 
193 		// untracked are always at the end of the list
194 		if ("all".equals(untrackedFilesMode)) { //$NON-NLS-1$
195 			TreeSet<String> untracked = new TreeSet<>(
196 					status.getUntracked());
197 			for (String path : untracked)
198 				printPorcelainLine('?', '?', path);
199 		}
200 	}
201 
202 	private void printPorcelainLine(char x, char y, String path)
203 			throws IOException {
204 		StringBuilder lineBuilder = new StringBuilder();
205 		lineBuilder.append(x).append(y).append(' ').append(path);
206 		outw.println(lineBuilder.toString());
207 	}
208 
209 	private void printLongStatus(org.eclipse.jgit.api.Status status)
210 			throws IOException {
211 		// Print current branch name
212 		final Ref head = db.exactRef(Constants.HEAD);
213 		if (head != null && head.isSymbolic()) {
214 			String branch = Repository.shortenRefName(head.getLeaf().getName());
215 			outw.println(CLIText.formatLine(MessageFormat.format(
216 					CLIText.get().onBranch, branch)));
217 		} else
218 			outw.println(CLIText.formatLine(CLIText.get().notOnAnyBranch));
219 
220 		// List changes
221 		boolean firstHeader = true;
222 
223 		Collection<String> added = status.getAdded();
224 		Collection<String> changed = status.getChanged();
225 		Collection<String> removed = status.getRemoved();
226 		Collection<String> modified = status.getModified();
227 		Collection<String> missing = status.getMissing();
228 		Collection<String> untracked = status.getUntracked();
229 		Map<String, StageState> unmergedStates = status
230 				.getConflictingStageState();
231 		Collection<String> toBeCommitted = new ArrayList<>(added);
232 		toBeCommitted.addAll(changed);
233 		toBeCommitted.addAll(removed);
234 		int nbToBeCommitted = toBeCommitted.size();
235 		if (nbToBeCommitted > 0) {
236 			printSectionHeader(CLIText.get().changesToBeCommitted);
237 			printList(CLIText.get().statusNewFile,
238 					CLIText.get().statusModified, CLIText.get().statusRemoved,
239 					toBeCommitted, added, changed, removed);
240 			firstHeader = false;
241 		}
242 		Collection<String> notStagedForCommit = new ArrayList<>(modified);
243 		notStagedForCommit.addAll(missing);
244 		int nbNotStagedForCommit = notStagedForCommit.size();
245 		if (nbNotStagedForCommit > 0) {
246 			if (!firstHeader)
247 				printSectionHeader(""); //$NON-NLS-1$
248 			printSectionHeader(CLIText.get().changesNotStagedForCommit);
249 			printList(CLIText.get().statusModified,
250 					CLIText.get().statusRemoved, null, notStagedForCommit,
251 					modified, missing, null);
252 			firstHeader = false;
253 		}
254 		int nbUnmerged = unmergedStates.size();
255 		if (nbUnmerged > 0) {
256 			if (!firstHeader)
257 				printSectionHeader(""); //$NON-NLS-1$
258 			printSectionHeader(CLIText.get().unmergedPaths);
259 			printUnmerged(unmergedStates);
260 			firstHeader = false;
261 		}
262 		int nbUntracked = untracked.size();
263 		if (nbUntracked > 0 && ("all".equals(untrackedFilesMode))) { //$NON-NLS-1$
264 			if (!firstHeader)
265 				printSectionHeader(""); //$NON-NLS-1$
266 			printSectionHeader(CLIText.get().untrackedFiles);
267 			printList(untracked);
268 		}
269 	}
270 
271 	/**
272 	 * Print section header
273 	 *
274 	 * @param pattern
275 	 *            a {@link java.lang.String} object.
276 	 * @param arguments
277 	 *            a {@link java.lang.Object} object.
278 	 * @throws java.io.IOException
279 	 */
280 	protected void printSectionHeader(String pattern, Object... arguments)
281 			throws IOException {
282 		if (!porcelain) {
283 			outw.println(CLIText.formatLine(MessageFormat.format(pattern,
284 					arguments)));
285 			if (!pattern.equals("")) //$NON-NLS-1$
286 				outw.println(CLIText.formatLine("")); //$NON-NLS-1$
287 			outw.flush();
288 		}
289 	}
290 
291 	/**
292 	 * Print String list
293 	 *
294 	 * @param list
295 	 *            a {@link java.util.Collection} object.
296 	 * @return a int.
297 	 * @throws java.io.IOException
298 	 */
299 	protected int printList(Collection<String> list) throws IOException {
300 		if (!list.isEmpty()) {
301 			List<String> sortedList = new ArrayList<>(list);
302 			java.util.Collections.sort(sortedList);
303 			for (String filename : sortedList) {
304 				outw.println(CLIText.formatLine(String.format(
305 						statusFileListFormat, filename)));
306 			}
307 			outw.flush();
308 			return list.size();
309 		} else
310 			return 0;
311 	}
312 
313 	/**
314 	 * Print String list
315 	 *
316 	 * @param status1
317 	 *            a {@link java.lang.String} object.
318 	 * @param status2
319 	 *            a {@link java.lang.String} object.
320 	 * @param status3
321 	 *            a {@link java.lang.String} object.
322 	 * @param list
323 	 *            a {@link java.util.Collection} object.
324 	 * @param set1
325 	 *            a {@link java.util.Collection} object.
326 	 * @param set2
327 	 *            a {@link java.util.Collection} object.
328 	 * @param set3
329 	 *            a {@link java.util.Collection} object.
330 	 * @return a int.
331 	 * @throws java.io.IOException
332 	 */
333 	protected int printList(String status1, String status2, String status3,
334 			Collection<String> list, Collection<String> set1,
335 			Collection<String> set2,
336 			Collection<String> set3)
337 			throws IOException {
338 		List<String> sortedList = new ArrayList<>(list);
339 		java.util.Collections.sort(sortedList);
340 		for (String filename : sortedList) {
341 			String prefix;
342 			if (set1.contains(filename))
343 				prefix = status1;
344 			else if (set2.contains(filename))
345 				prefix = status2;
346 			else
347 				// if (set3.contains(filename))
348 				prefix = status3;
349 			outw.println(CLIText.formatLine(String.format(
350 					statusFileListFormatWithPrefix, prefix, filename)));
351 			outw.flush();
352 		}
353 		return list.size();
354 	}
355 
356 	private void printUnmerged(Map<String, StageState> unmergedStates)
357 			throws IOException {
358 		List<String> paths = new ArrayList<>(unmergedStates.keySet());
359 		Collections.sort(paths);
360 		for (String path : paths) {
361 			StageState state = unmergedStates.get(path);
362 			String stateDescription = getStageStateDescription(state);
363 			outw.println(CLIText.formatLine(String.format(
364 					statusFileListFormatUnmerged, stateDescription, path)));
365 			outw.flush();
366 		}
367 	}
368 
369 	private static String getStageStateDescription(StageState stageState) {
370 		CLIText text = CLIText.get();
371 		switch (stageState) {
372 		case BOTH_DELETED:
373 			return text.statusBothDeleted;
374 		case ADDED_BY_US:
375 			return text.statusAddedByUs;
376 		case DELETED_BY_THEM:
377 			return text.statusDeletedByThem;
378 		case ADDED_BY_THEM:
379 			return text.statusAddedByThem;
380 		case DELETED_BY_US:
381 			return text.statusDeletedByUs;
382 		case BOTH_ADDED:
383 			return text.statusBothAdded;
384 		case BOTH_MODIFIED:
385 			return text.statusBothModified;
386 		default:
387 			throw new IllegalArgumentException("Unknown StageState: " //$NON-NLS-1$
388 					+ stageState);
389 		}
390 	}
391 }