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