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