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