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