/* * Copyright (C) 2011, 2015 François Rey and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.pgm; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeSet; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.StatusCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.IndexDiff.StageState; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.opt.UntrackedFilesHandler; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; import org.kohsuke.args4j.spi.RestOfArgumentsHandler; /** * Status command */ @Command(usage = "usage_Status", common = true) class Status extends TextBuiltin { protected final String statusFileListFormat = CLIText.get().statusFileListFormat; protected final String statusFileListFormatWithPrefix = CLIText.get().statusFileListFormatWithPrefix; protected final String statusFileListFormatUnmerged = CLIText.get().statusFileListFormatUnmerged; @Option(name = "--porcelain", usage = "usage_machineReadableOutput") protected boolean porcelain; @Option(name = "--untracked-files", aliases = { "-u", "-uno", "-uall" }, usage = "usage_untrackedFilesMode", handler = UntrackedFilesHandler.class) protected String untrackedFilesMode = "all"; // default value //$NON-NLS-1$ @Argument(required = false, index = 0, metaVar = "metaVar_paths") @Option(name = "--", metaVar = "metaVar_paths", handler = RestOfArgumentsHandler.class) protected List filterPaths; @Override protected void run() { try (Git git = new Git(db)) { StatusCommand statusCommand = git.status(); if (filterPaths != null) { for (String path : filterPaths) { statusCommand.addPath(path); } } org.eclipse.jgit.api.Status status = statusCommand.call(); printStatus(status); } catch (GitAPIException | NoWorkTreeException | IOException e) { throw die(e.getMessage(), e); } } private void printStatus(org.eclipse.jgit.api.Status status) throws IOException { if (porcelain) printPorcelainStatus(status); else printLongStatus(status); } private void printPorcelainStatus(org.eclipse.jgit.api.Status status) throws IOException { Collection added = status.getAdded(); Collection changed = status.getChanged(); Collection removed = status.getRemoved(); Collection modified = status.getModified(); Collection missing = status.getMissing(); Map conflicting = status.getConflictingStageState(); // build a sorted list of all paths except untracked and ignored TreeSet sorted = new TreeSet<>(); sorted.addAll(added); sorted.addAll(changed); sorted.addAll(removed); sorted.addAll(modified); sorted.addAll(missing); sorted.addAll(conflicting.keySet()); // list each path for (String path : sorted) { char x = ' '; char y = ' '; if (added.contains(path)) x = 'A'; else if (changed.contains(path)) x = 'M'; else if (removed.contains(path)) x = 'D'; if (modified.contains(path)) y = 'M'; else if (missing.contains(path)) y = 'D'; if (conflicting.containsKey(path)) { StageState stageState = conflicting.get(path); switch (stageState) { case BOTH_DELETED: x = 'D'; y = 'D'; break; case ADDED_BY_US: x = 'A'; y = 'U'; break; case DELETED_BY_THEM: x = 'U'; y = 'D'; break; case ADDED_BY_THEM: x = 'U'; y = 'A'; break; case DELETED_BY_US: x = 'D'; y = 'U'; break; case BOTH_ADDED: x = 'A'; y = 'A'; break; case BOTH_MODIFIED: x = 'U'; y = 'U'; break; default: throw new IllegalArgumentException("Unknown StageState: " //$NON-NLS-1$ + stageState); } } printPorcelainLine(x, y, path); } // untracked are always at the end of the list if ("all".equals(untrackedFilesMode)) { //$NON-NLS-1$ TreeSet untracked = new TreeSet<>( status.getUntracked()); for (String path : untracked) printPorcelainLine('?', '?', path); } } private void printPorcelainLine(char x, char y, String path) throws IOException { StringBuilder lineBuilder = new StringBuilder(); lineBuilder.append(x).append(y).append(' ').append(path); outw.println(lineBuilder.toString()); } private void printLongStatus(org.eclipse.jgit.api.Status status) throws IOException { // Print current branch name final Ref head = db.exactRef(Constants.HEAD); if (head != null && head.isSymbolic()) { String branch = Repository.shortenRefName(head.getLeaf().getName()); outw.println(CLIText.formatLine(MessageFormat.format( CLIText.get().onBranch, branch))); } else outw.println(CLIText.formatLine(CLIText.get().notOnAnyBranch)); // List changes boolean firstHeader = true; Collection added = status.getAdded(); Collection changed = status.getChanged(); Collection removed = status.getRemoved(); Collection modified = status.getModified(); Collection missing = status.getMissing(); Collection untracked = status.getUntracked(); Map unmergedStates = status .getConflictingStageState(); Collection toBeCommitted = new ArrayList<>(added); toBeCommitted.addAll(changed); toBeCommitted.addAll(removed); int nbToBeCommitted = toBeCommitted.size(); if (nbToBeCommitted > 0) { printSectionHeader(CLIText.get().changesToBeCommitted); printList(CLIText.get().statusNewFile, CLIText.get().statusModified, CLIText.get().statusRemoved, toBeCommitted, added, changed, removed); firstHeader = false; } Collection notStagedForCommit = new ArrayList<>(modified); notStagedForCommit.addAll(missing); int nbNotStagedForCommit = notStagedForCommit.size(); if (nbNotStagedForCommit > 0) { if (!firstHeader) printSectionHeader(""); //$NON-NLS-1$ printSectionHeader(CLIText.get().changesNotStagedForCommit); printList(CLIText.get().statusModified, CLIText.get().statusRemoved, null, notStagedForCommit, modified, missing, null); firstHeader = false; } int nbUnmerged = unmergedStates.size(); if (nbUnmerged > 0) { if (!firstHeader) printSectionHeader(""); //$NON-NLS-1$ printSectionHeader(CLIText.get().unmergedPaths); printUnmerged(unmergedStates); firstHeader = false; } int nbUntracked = untracked.size(); if (nbUntracked > 0 && "all".equals(untrackedFilesMode)) { //$NON-NLS-1$ if (!firstHeader) printSectionHeader(""); //$NON-NLS-1$ printSectionHeader(CLIText.get().untrackedFiles); printList(untracked); } } /** * Print section header * * @param pattern * a {@link String} object. * @param arguments * a {@link Object} object. * @throws IOException * if an IO error occurred */ protected void printSectionHeader(String pattern, Object... arguments) throws IOException { if (!porcelain) { outw.println(CLIText.formatLine(MessageFormat.format(pattern, arguments))); if (!pattern.isEmpty()) outw.println(CLIText.formatLine("")); //$NON-NLS-1$ outw.flush(); } } /** * Print String list * * @param list * a {@link Collection} object. * @return size of the list * @throws IOException * if an IO error occurred */ protected int printList(Collection list) throws IOException { if (!list.isEmpty()) { List sortedList = new ArrayList<>(list); java.util.Collections.sort(sortedList); for (String filename : sortedList) { outw.println(CLIText.formatLine(String.format( statusFileListFormat, filename))); } outw.flush(); return list.size(); } return 0; } /** * Print String list * * @param status1 * a {@link String} object. * @param status2 * a {@link String} object. * @param status3 * a {@link String} object. * @param list * a {@link Collection} object. * @param set1 * a {@link Collection} object. * @param set2 * a {@link Collection} object. * @param set3 * a {@link Collection} object. * @return a int. * @throws IOException * if an IO error occurred */ protected int printList(String status1, String status2, String status3, Collection list, Collection set1, Collection set2, Collection set3) throws IOException { List sortedList = new ArrayList<>(list); java.util.Collections.sort(sortedList); for (String filename : sortedList) { String prefix; if (set1.contains(filename)) prefix = status1; else if (set2.contains(filename)) prefix = status2; else // if (set3.contains(filename)) prefix = status3; outw.println(CLIText.formatLine(String.format( statusFileListFormatWithPrefix, prefix, filename))); outw.flush(); } return list.size(); } private void printUnmerged(Map unmergedStates) throws IOException { List paths = new ArrayList<>(unmergedStates.keySet()); Collections.sort(paths); for (String path : paths) { StageState state = unmergedStates.get(path); String stateDescription = getStageStateDescription(state); outw.println(CLIText.formatLine(String.format( statusFileListFormatUnmerged, stateDescription, path))); outw.flush(); } } private static String getStageStateDescription(StageState stageState) { CLIText text = CLIText.get(); switch (stageState) { case BOTH_DELETED: return text.statusBothDeleted; case ADDED_BY_US: return text.statusAddedByUs; case DELETED_BY_THEM: return text.statusDeletedByThem; case ADDED_BY_THEM: return text.statusAddedByThem; case DELETED_BY_US: return text.statusDeletedByUs; case BOTH_ADDED: return text.statusBothAdded; case BOTH_MODIFIED: return text.statusBothModified; default: throw new IllegalArgumentException("Unknown StageState: " //$NON-NLS-1$ + stageState); } } }