/* * Copyright 2000-2010 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.community.intellij.plugins.communitycase.checkout.branches; import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager; import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.util.EventDispatcher; import org.community.intellij.plugins.communitycase.Branch; import org.community.intellij.plugins.communitycase.Util; import org.community.intellij.plugins.communitycase.Vcs; import org.community.intellij.plugins.communitycase.commands.Command; import org.community.intellij.plugins.communitycase.commands.SimpleHandler; import org.community.intellij.plugins.communitycase.history.HistoryUtils; import org.community.intellij.plugins.communitycase.merge.MergeUtil; import org.community.intellij.plugins.communitycase.rebase.RebaseUtils; import org.community.intellij.plugins.communitycase.ui.UiUtil; import org.community.intellij.plugins.communitycase.vfs.ReferenceListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; /** * branch configurations settings and project level state */ @State( name = ".Branch.Configurations", storages = {@Storage( id = "ws", file = "$WORKSPACE_FILE$")}) public class BranchConfigurations implements PersistentStateComponent<BranchConfigurations.State>, Disposable { /** * The logger */ private static final Logger LOG = Logger.getInstance("#"+BranchConfigurations.class.getName()); /** * The comparator for branch configuration by name */ private static final Comparator<BranchConfiguration> CONFIGURATION_COMPARATOR = new Comparator<BranchConfiguration>() { @Override public int compare(BranchConfiguration o1, BranchConfiguration o2) { return o1.NAME.compareTo(o2.NAME); } }; /** * The comparator for branch information by root */ private static final Comparator<BranchInfo> BRANCH_INFO_COMPARATOR = new Comparator<BranchInfo>() { @Override public int compare(BranchInfo o1, BranchInfo o2) { return o1.ROOT.compareTo(o2.ROOT); } }; /** * The project */ private final Project myProject; /** * The vcs */ private final Vcs myVcs; /** * The shelve manager instance */ private final ShelveChangesManager myShelveManager; /** * The dirty scope manager */ private final VcsDirtyScopeManager myDirtyScopeManager; /** * Change manager */ private final ChangeListManagerEx myChangeManager; /** * Project manager */ private final ProjectManagerEx myProjectManager; /** * The state lock */ private final Object myStateLock = new Object(); /** * The set of configurations */ private final HashMap<String, org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration> myConfigurations = new HashMap<String, org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration>(); /** * Create event dispatcher for configuration events */ private final EventDispatcher<BranchConfigurationsListener> myListeners = EventDispatcher.create(BranchConfigurationsListener.class); /** * The current configuration */ private org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration myCurrentConfiguration; /** * The reference listener */ private final ReferenceListener myReferenceListener; /** * The current status (cached, invalidated when references change) */ private SpecialStatus myCurrentStatus; /** * The collection of roots */ private List<VirtualFile> myRoots = Collections.emptyList(); /** * If true, checkout background process is in progress */ private boolean myCheckoutIsInProgress = false; /** * The widget uninstall action (on deactivate) */ private Runnable myWidgetUninstall; /** * Listener for changes */ private final ChangeListAdapter myChangesListener; /** * If true, the widget is enabled */ private boolean myWidgetEnabled = true; /** * The constructor used to dependency injection * * @param project the project * @param shelveManager the shelve manager * @param dirtyScopeManager the dirty scope manager * @param changeManager the change manager * @param projectManager the project manager */ public BranchConfigurations(Project project, ShelveChangesManager shelveManager, VcsDirtyScopeManager dirtyScopeManager, ChangeListManagerEx changeManager, ProjectManagerEx projectManager) { myProject = project; myVcs = Vcs.getInstance(project); myShelveManager = shelveManager; myDirtyScopeManager = dirtyScopeManager; myChangeManager = changeManager; myProjectManager = projectManager; myReferenceListener = new ReferenceListener() { @Override public void referencesChanged(VirtualFile root) { BranchConfigurations.this.referencesChanged(); } }; Disposer.register(myProject, this); myChangesListener = new ChangeListAdapter() { @Override public void changesRemoved(Collection<Change> changes, ChangeList fromList) { BranchConfigurations.this.referencesChanged(); } @Override public void changesAdded(Collection<Change> changes, ChangeList toList) { BranchConfigurations.this.referencesChanged(); } }; } /** * Add listener * * @param l the listener */ public void addConfigurationListener(BranchConfigurationsListener l) { myListeners.addListener(l); } /** * Remove listener * * @param l the listener */ public void removeConfigurationListener(BranchConfigurationsListener l) { myListeners.removeListener(l); } /** * Handle reference change, also notified when roots changed. */ public void referencesChanged() { synchronized (myStateLock) { updateRootCollection(); updateSpecialStatus(); fireReferencesChanged(); } } /** * Fire that references changed */ private void fireReferencesChanged() { myListeners.getMulticaster().referencesChanged(); } /** * Update collections of roots */ private void updateRootCollection() { try { myRoots = Util.getRoots(myProject, myVcs); } catch (VcsException e) { LOG.warn("Empty list of roots is detected", e); myRoots = Collections.emptyList(); } } /** * Update special status */ private void updateSpecialStatus() { SpecialStatus p = myCurrentStatus; myCurrentStatus = calculateSpecialStatus(); if (p != myCurrentStatus) { myListeners.getMulticaster().specialStatusChanged(); } } /** * Activate component */ public void activate() { myVcs.addReferenceListener(myReferenceListener); myChangeManager.addChangeListListener(myChangesListener); synchronized (myStateLock) { updateRootCollection(); if (myCurrentConfiguration == null) { if (calculateSpecialStatus() == SpecialStatus.NORMAL) { try { detectLocals(); } catch (VcsException e) { LOG.info("Exception during detecting local configurations", e); UiUtil.checkExecutableAndShowNotification(myProject, e); } } } referencesChanged(); } if (isWidgetEnabled()) { installWidget(); } } /** * Install widget */ private void installWidget() { if (!ApplicationManager.getApplication().isHeadlessEnvironment()) { final Runnable r = BranchesWidget.install(myProject, this); synchronized (myStateLock) { myWidgetUninstall = r; } } } /** * Deactivate component */ public void deactivate() { myVcs.removeReferenceListener(myReferenceListener); myChangeManager.removeChangeListListener(myChangesListener); uninstallWidget(); } private void uninstallWidget() { final Runnable r; synchronized (myStateLock) { r = myWidgetUninstall; myWidgetUninstall = null; } if (r != null) { r.run(); } } /** * Get component instance * * @param project a context project * @return the settings */ public static BranchConfigurations getInstance(Project project) { return PeriodicalTasksCloser.getInstance().safeGetService(project, BranchConfigurations.class); } @Override public void dispose() { deactivate(); } /** * {@inheritDoc} */ @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"}) @Override public State getState() { synchronized (myStateLock) { State rc = new State(); rc.IS_WIDGET_ENABLED = myWidgetEnabled; rc.CURRENT = myCurrentConfiguration == null ? null : myCurrentConfiguration.getName(); ArrayList<BranchConfiguration> cs = new ArrayList<BranchConfiguration>(myConfigurations.size()); for (org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration ci : myConfigurations.values()) { BranchConfiguration c = new BranchConfiguration(); c.NAME = ci.getName(); Map<String, String> map = ci.getReferences(); ArrayList<BranchInfo> bs = new ArrayList<BranchInfo>(map.size()); for (Map.Entry<String, String> m : map.entrySet()) { BranchInfo b = new BranchInfo(); b.ROOT = m.getKey(); b.REFERENCE = m.getValue(); bs.add(b); } c.BRANCHES = bs.toArray(new BranchInfo[bs.size()]); Arrays.sort(c.BRANCHES, BRANCH_INFO_COMPARATOR); c.CHANGES = ci.getChanges(); c.IS_AUTO_DETECTED = ci.isAutoDetected(); cs.add(c); } rc.CONFIGURATIONS = cs.toArray(new BranchConfiguration[cs.size()]); Arrays.sort(rc.CONFIGURATIONS, CONFIGURATION_COMPARATOR); return rc; } } /** * {@inheritDoc} */ @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext"}) @Override public void loadState(State state) { synchronized (myStateLock) { if (!myConfigurations.isEmpty()) { return; } for (BranchConfiguration bc : state.CONFIGURATIONS) { org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration n = new org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration(this, bc.NAME); myConfigurations.put(n.getName(), n); for (BranchInfo bi : bc.BRANCHES) { n.setReference(bi.ROOT, bi.REFERENCE); } myConfigurations.put(bc.NAME, n); n.setAutoDetected(bc.IS_AUTO_DETECTED); } if (myCurrentConfiguration == null) { myCurrentConfiguration = myConfigurations.get(state.CURRENT); } else { myCurrentConfiguration = myConfigurations.get(myCurrentConfiguration.getName()); if (myCurrentConfiguration == null) { myCurrentConfiguration = myConfigurations.get(state.CURRENT); } } fireCurrentConfigurationChanged(); fireConfigurationsChanged(); if (state.IS_WIDGET_ENABLED != myWidgetEnabled) { myWidgetEnabled = state.IS_WIDGET_ENABLED; updateWidgetState(); } } } /** * Update widget state after update */ private void updateWidgetState() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (isWidgetEnabled()) { if (myVcs.isActivated() && myWidgetUninstall == null) { installWidget(); } } else { uninstallWidget(); } } }); } /** * @return true if widget is enabled */ public boolean isWidgetEnabled() { synchronized (myStateLock) { return myWidgetEnabled; } } /** * Update widget state * * @param value true to enable widget */ public void setWidgetEnabled(boolean value) { synchronized (myStateLock) { myWidgetEnabled = value; updateWidgetState(); } } /** * @return the candidate remote configurations */ List<String> getRemotesCandidates() { try { final List<VirtualFile> roots; synchronized (myStateLock) { roots = myRoots; } return detectConfigurations(false, roots); } catch (VcsException e) { return Collections.emptyList(); } } /** * @return the overall special status */ public SpecialStatus getSpecialStatus() { synchronized (myStateLock) { return myCurrentStatus; } } /** * @return the calculated overall special status */ private SpecialStatus calculateSpecialStatus() { synchronized (myStateLock) { if (myCheckoutIsInProgress) { return SpecialStatus.CHECKOUT_IN_PROGRESS; } for (VirtualFile root : myRoots) { if (RebaseUtils.isRebaseInTheProgress(root)) { return SpecialStatus.REBASING; } if (MergeUtil.isMergeInTheProgress(root)) { return SpecialStatus.MERGING; } } for (LocalChangeList changeList : myChangeManager.getChangeListsCopy()) { for (Change change : changeList.getChanges()) { if (change.getFileStatus() == FileStatus.MERGED_WITH_CONFLICTS) { return SpecialStatus.MERGING; } } } return myRoots.size() == 0 ? SpecialStatus.NON_ : SpecialStatus.NORMAL; } } /** * Detect local branch configurations * * @throws VcsException if there is a problem with detecting */ private void detectLocals() throws VcsException { synchronized (myStateLock) { final HashMap<VirtualFile, String> rootToCurrentBranch = new HashMap<VirtualFile, String>(); for (VirtualFile root : myRoots) { Branch current = Branch.current(myProject, root); rootToCurrentBranch.put(root, current == null ? "" : current.getName()); } if (myConfigurations.isEmpty()) { detectLocalConfigurations(true); updateCurrentConfiguration(rootToCurrentBranch); if (myCurrentConfiguration == null) { // the configuration does not matches any standard, there could be no configurations with spaces at this point // since it is not allowed branch name. String name = "untitled"; if (myConfigurations.containsKey(name)) { String p = name; name = null; for (int i = 0; i < Integer.MAX_VALUE; i++) { final String c = p + i; if (!myConfigurations.containsKey(c)) { name = c; break; } } if (name == null) { name = "untitled 1"; } } org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration c = createConfiguration(name); for (VirtualFile root : myRoots) { c.setReference(root.getPath(), describeRoot(root)); } } } else if (myCurrentConfiguration == null) { updateCurrentConfiguration(rootToCurrentBranch); } fireCurrentConfigurationChanged(); fireConfigurationsChanged(); } } /** * Updates myCurrentConfiguration (displayed in the BranchesWidget) to the proper value from the list * of all myConfigurations. * @param rootToCurrentBranch Mapping from a root to the name of current branch selected on this root. */ private void updateCurrentConfiguration(HashMap<VirtualFile, String> rootToCurrentBranch) { for (org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration configuration : myConfigurations.values()) { boolean currentsMatched = true; for (VirtualFile root : myRoots) { currentsMatched &= rootToCurrentBranch.get(root).equals(configuration.getReference(root.getPath())); } if (currentsMatched) { myCurrentConfiguration = configuration; break; } } } /** * Detect local configurations * * @param complete if complete configurations should be detected * @throws VcsException */ void detectLocalConfigurations(boolean complete) throws VcsException { synchronized (myStateLock) { if (complete) { List<String> locals = detectConfigurations(true, myRoots); if (locals.isEmpty()) { // no commits locals.add("master"); } locals.removeAll(myConfigurations.keySet()); for (String localName : locals) { org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration c = createConfiguration(localName); c.setAutoDetected(true); for (VirtualFile root : myRoots) { c.setReference(root.getPath(), localName); } } } else { HashSet<String> detected = new HashSet<String>(); HashSet<String> forRoot = new HashSet<String>(); for (VirtualFile root : myRoots) { forRoot.clear(); Branch.listAsStrings(myProject, root, false, true, forRoot, null); for (String b : forRoot) { org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration c; if (detected.contains(b)) { c = myConfigurations.get(b); } else if (!myConfigurations.containsKey(b)) { detected.add(b); c = createConfiguration(b); c.setAutoDetected(true); } else { continue; } c.setReference(root.getPath(), b); } } } } } /** * The configurations changed */ private void fireConfigurationsChanged() { myListeners.getMulticaster().configurationsChanged(); } /** * The current configuration changed */ private void fireCurrentConfigurationChanged() { myListeners.getMulticaster().currentConfigurationChanged(); } /** * Describe vcs root * * @param root the root to describe * @return the current reference * @throws VcsException if there is a problem with describing the root */ String describeRoot(VirtualFile root) throws VcsException { Branch current = Branch.current(myProject, root); if (current == null) { // It is on the tag or specific commit. In future, support for submodules should be added. return detectTag(root, "HEAD"); } else { return current.getName(); } } /** * Get tag name for the head * * @param root the root to describe * @param ref the ref to detect * @return the commit expression that describes root state */ String detectTag(VirtualFile root, final String ref) { try { SimpleHandler h = new SimpleHandler(myProject, root, Command.DESCRIBE); h.addParameters("--tags", "--exact", ref); h.setRemote(true); return h.run().trim(); } catch (VcsException e) { if (LOG.isDebugEnabled()) { LOG.debug("describe HEAD failed for root: " + root.getPath()); } try { return HistoryUtils.validateRevisionNumber(ref).asString(); } catch (VcsException e1) { throw new RuntimeException("Unexpected exception at this time, the failure should have been detected at current(): ", e1); } } } /** * Detect possible configurations * * @param roots the vcs roots used to detect configuraitons * @return a sorted list of branches * @throws VcsException if there is a problem with running */ private List<String> detectConfigurations(boolean local, final List<VirtualFile> roots) throws VcsException { HashSet<String> all = new HashSet<String>(); HashSet<String> forRoot = new HashSet<String>(); boolean isFirst = true; for (VirtualFile root : roots) { forRoot.clear(); Branch.listAsStrings(myProject, root, !local, local, forRoot, null); if (isFirst) { isFirst = false; all.addAll(forRoot); } else { all.retainAll(forRoot); } } ArrayList<String> rc = new ArrayList<String>(all); Collections.sort(rc); return rc; } /** * @return the used vcs */ Vcs getVcs() { return myVcs; } /** * @return the context project */ Project getProject() { return myProject; } /** * @return the current configuration * @throws VcsException if there is a problem with configurations */ org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration getCurrentConfiguration() throws VcsException { synchronized (myStateLock) { if (myCurrentConfiguration == null) { throw new VcsException("The current configuration is not yet detected"); } return myCurrentConfiguration; } } /** * @return the configuration names */ Set<String> getConfigurationNames() { synchronized (myStateLock) { return new HashSet<String>(myConfigurations.keySet()); } } /** * Create new branch configuration * * @param name the configuration name * @return the created configuration */ @NotNull org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration createConfiguration(String name) { synchronized (myStateLock) { if (myConfigurations.containsKey(name)) { throw new IllegalStateException("The name " + name + " is already used"); } org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration c = new org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration(this, name); myConfigurations.put(name, c); fireConfigurationsChanged(); return c; } } /** * Find configuration by name * * @param name the name to use * @return the configuration by name * @throws VcsException if there is an error in the state */ @Nullable org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration getConfiguration(String name) throws VcsException { synchronized (myStateLock) { return myConfigurations.get(name); } } /** * @return the state lock for branch configurations */ Object getStateLock() { return myStateLock; } /** * Set current configuration * * @param newConfiguration the new current configuration */ void setCurrentConfiguration(org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration newConfiguration) { synchronized (myStateLock) { assert myConfigurations.get(newConfiguration.getName()) == newConfiguration; myCurrentConfiguration = newConfiguration; fireCurrentConfigurationChanged(); } } /** * @return the shelve manager from project */ ShelveChangesManager getShelveManager() { return myShelveManager; } /** * Remove configuration * * @param toRemove the removed configuration */ public void removeConfiguration(@NotNull org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration toRemove) { synchronized (myStateLock) { if (toRemove == myCurrentConfiguration) { throw new IllegalArgumentException("Unable to remove the current configuration"); } myConfigurations.remove(toRemove.getName()); fireConfigurationsChanged(); } } /** * Start checkout process for selected configuration in the background * * @param configuration the selected configuration or null if new configuration is needed. * @param remote the remote pseudo configuration name * @param quick the quick checkout */ public void startCheckout(final org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration configuration, final String remote, final boolean quick) { if (remote != null && configuration != null) { throw new IllegalArgumentException("Either remote or configuration to checkout must be null"); } synchronized (myStateLock) { final SpecialStatus status = calculateSpecialStatus(); if (status != SpecialStatus.NORMAL) { throw new IllegalStateException("Checkout cannot be started due to special status (it must have been checked in UI): " + status); } myCheckoutIsInProgress = true; updateSpecialStatus(); } final String name = configuration != null ? configuration.getName() : remote == null ? "new configuration" : remote; final String title = "Checking out " + name; ProgressManager.getInstance().run(new Task.Backgroundable(myProject, title, false) { @Override public void run(@NotNull ProgressIndicator indicator) { try { final CheckoutProcess process = new CheckoutProcess(BranchConfigurations.this, myProject, myShelveManager, myDirtyScopeManager, myChangeManager, myProjectManager, indicator, configuration, remote, quick); process.run(); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { final List<VcsException> exceptions = process.getExceptions(); String op = (process.isModify() ? "Modification" : "Checkout") + " of " + name; if (!exceptions.isEmpty()) { UiUtil.showTabErrors(myProject, title, exceptions); ToolWindowManager.getInstance(myProject).notifyByBalloon( ChangesViewContentManager.TOOLWINDOW_ID, MessageType.ERROR, op + " failed."); } else if (process.isCancelled()) { ToolWindowManager.getInstance(myProject).notifyByBalloon( ChangesViewContentManager.TOOLWINDOW_ID, MessageType.WARNING, op + " was cancelled by user."); } else { ToolWindowManager.getInstance(myProject).notifyByBalloon( ChangesViewContentManager.TOOLWINDOW_ID, MessageType.INFO, op + " complete."); } } }); } catch (Throwable t) { LOG.error("Unexpected exception from checkout: ", t); } finally { synchronized (myStateLock) { myCheckoutIsInProgress = false; updateSpecialStatus(); } } } }); } /** * Internal notification about renamed configuration * * @param toRename configuration to rename * @param oldName the old name * @param newName the new name @return true if configuration actually renamed. */ boolean configurationRenamed(org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration toRename, String oldName, String newName) { synchronized (myStateLock) { final org.community.intellij.plugins.communitycase.checkout.branches.BranchConfiguration c = myConfigurations.get(oldName); if (c == toRename) { myConfigurations.remove(oldName); myConfigurations.put(newName, c); } return c == toRename; } } /** * The configuration state */ public static class State { /** * If true, branches widget is enabled */ public boolean IS_WIDGET_ENABLED = true; /** * The current configuration */ public String CURRENT; /** * The branch configuration */ public BranchConfiguration[] CONFIGURATIONS = new BranchConfiguration[0]; } /** * The branch configuration */ public static class BranchConfiguration { /** * If true, the configuration was auto-detected */ public boolean IS_AUTO_DETECTED; /** * The configuration name */ public String NAME; /** * The branch information */ public BranchInfo[] BRANCHES = new BranchInfo[0]; /** * The branch changes */ public BranchChanges CHANGES; } /** * Branch mapping information */ public static class BranchInfo { /** * The vcs root for which information is stored */ public String ROOT; /** * The local branch or specific commit */ public String REFERENCE; } /** * The changes associated with the branch state */ public static class BranchChanges { /** * The path to shelve that keeps changes */ public String SHELVE_PATH; /** * Change list information */ public ChangeListInfo[] CHANGE_LISTS = new ChangeListInfo[0]; /** * Information about distribution of changes among change lists */ public ChangeInfo[] CHANGES = new ChangeInfo[0]; } /** * The change list information */ public static class ChangeListInfo { /** * If true, the change list was a default change list */ public boolean IS_DEFAULT = false; /** * The change list name */ public String NAME; /** * The change list comment */ public String COMMENT; } /** * Change information. The change is identified by before path and after path (for deleted) */ public static class ChangeInfo { /** * The before path */ public String BEFORE_PATH; /** * The after path */ public String AFTER_PATH; /** * The name of change list to which change belong */ public String CHANGE_LIST_NAME; } /** * The special status for the roots */ public enum SpecialStatus { /** * Normal work tree, checkout is possible */ NORMAL, /** * Rebasing */ REBASING, /** * Merging */ MERGING, /** * Non project */ NON_, /** * The background checkout process is in progress */ CHECKOUT_IN_PROGRESS } }