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