| /****************************************************************************** |
| * Copyright (c) 2012 GitHub Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Kevin Sawicki (GitHub Inc.) - initial API and implementation |
| *****************************************************************************/ |
| package org.eclipse.egit.ui.internal.branch; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IProjectDescription; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.egit.core.internal.util.ProjectUtil; |
| import org.eclipse.egit.core.internal.util.ResourceUtil; |
| import org.eclipse.egit.ui.Activator; |
| import org.eclipse.egit.ui.internal.clone.ProjectRecord; |
| import org.eclipse.egit.ui.internal.clone.ProjectUtils; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.ui.IMemento; |
| import org.eclipse.ui.WorkbenchException; |
| import org.eclipse.ui.XMLMemento; |
| |
| /** |
| * Class to track which projects are imported for each branch. |
| * <p> |
| * A unique preference is set for each repository/branch combination that is |
| * persisted that includes information to be able to restore projects when the |
| * branch is later checked out. |
| * |
| * <p> |
| * The workflow is as follows: |
| * <p> |
| * 1. Call {@link #snapshot()} to get the current projects for the currently |
| * checked out branch |
| * <p> |
| * 2. Call {@link #save(IMemento)} after the new branch has been successfully |
| * checked out with the memento returned from step 1 |
| * <p> |
| * 3. Call {@link #restore(IProgressMonitor)} to restore the projects for the |
| * newly checked out branch |
| * |
| */ |
| class BranchProjectTracker { |
| |
| private static final String PREFIX = "BranchProjectTracker_"; //$NON-NLS-1$ |
| |
| private static final String KEY_PROJECTS = "projects"; //$NON-NLS-1$ |
| |
| private static final String KEY_PROJECT = "project"; //$NON-NLS-1$ |
| |
| private static final String KEY_BRANCH = "branch"; //$NON-NLS-1$ |
| |
| private static final String REPO_ROOT = "/"; //$NON-NLS-1$ |
| |
| private final Repository repository; |
| |
| /** |
| * Create tracker for repository |
| * |
| * @param repository |
| */ |
| public BranchProjectTracker(final Repository repository) { |
| this.repository = repository; |
| } |
| |
| private String getBranch() { |
| try { |
| return repository.getBranch(); |
| } catch (IOException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Get preference key for branch. This will be unique to the repository and |
| * branch. |
| * |
| * @param branch |
| * @return key |
| */ |
| public String getPreference(final String branch) { |
| if (branch == null) |
| throw new IllegalArgumentException("Branch cannot be null"); //$NON-NLS-1$ |
| if (branch.length() == 0) |
| throw new IllegalArgumentException("Branch cannot be empty"); //$NON-NLS-1$ |
| |
| return PREFIX + '_' + repository.getDirectory().getAbsolutePath() + '_' |
| + branch; |
| } |
| |
| /** |
| * Snapshot the projects currently associated with the repository |
| * <p> |
| * The memento returned can be later passed to {@link #save(IMemento)} to |
| * persist it |
| * |
| * @see #save(IMemento) |
| * @return memento, will be null on failures |
| */ |
| public IMemento snapshot() { |
| String branch = getBranch(); |
| if (branch == null) |
| return null; |
| |
| IProject[] projects; |
| try { |
| projects = ProjectUtil.getValidOpenProjects(repository); |
| } catch (CoreException e) { |
| return null; |
| } |
| XMLMemento memento = XMLMemento.createWriteRoot(KEY_PROJECTS); |
| memento.putString(KEY_BRANCH, branch); |
| final String workDir = repository.getWorkTree().getAbsolutePath(); |
| for (IProject project : projects) { |
| IPath path = project.getLocation(); |
| if (path == null) { |
| continue; |
| } |
| // Only remember mapped projects |
| if (!ResourceUtil.isSharedWithGit(project)) { |
| continue; |
| } |
| String fullPath = path.toOSString(); |
| if (fullPath.startsWith(workDir)) { |
| String relative = fullPath.substring(workDir.length()); |
| if (relative.length() == 0) { |
| relative = REPO_ROOT; |
| } |
| IMemento child = memento.createChild(KEY_PROJECT); |
| child.putTextData(relative); |
| } |
| } |
| return memento; |
| } |
| |
| /** |
| * Associate projects with branch. The specified memento must the one |
| * previously returned from a call to {@link #snapshot()}. |
| * |
| * @see #snapshot() |
| * @param memento |
| * @return this tracker |
| */ |
| public BranchProjectTracker save(final IMemento memento) { |
| if (!(memento instanceof XMLMemento)) |
| throw new IllegalArgumentException("Invalid memento"); //$NON-NLS-1$ |
| |
| String branch = memento.getString(KEY_BRANCH); |
| IPreferenceStore store = Activator.getDefault().getPreferenceStore(); |
| String pref = getPreference(branch); |
| StringWriter writer = new StringWriter(); |
| try { |
| ((XMLMemento) memento).save(writer); |
| store.setValue(pref, writer.toString()); |
| } catch (IOException e) { |
| Activator.logError("Error writing branch-project associations", e); //$NON-NLS-1$ |
| } |
| return this; |
| } |
| |
| /** |
| * Load the project paths associated with the currently checked out branch. |
| * These paths will be relative to the repository root. |
| * |
| * @return non-null but possibly empty array of projects |
| */ |
| public String[] getProjectPaths() { |
| String branch = getBranch(); |
| if (branch == null) |
| return new String[0]; |
| return getProjectPaths(branch); |
| } |
| |
| /** |
| * Load the project paths associated with the given branch. These paths will |
| * be relative to the repository root. |
| * |
| * @param branch |
| * @return non-null but possibly empty array of projects |
| */ |
| public String[] getProjectPaths(final String branch) { |
| String pref = getPreference(branch); |
| String value = Activator.getDefault().getPreferenceStore() |
| .getString(pref); |
| if (value.length() == 0) |
| return new String[0]; |
| XMLMemento memento; |
| try { |
| memento = XMLMemento.createReadRoot(new StringReader(value)); |
| } catch (WorkbenchException e) { |
| Activator.logError("Error reading branch-project associations", e); //$NON-NLS-1$ |
| return new String[0]; |
| } |
| IMemento[] children = memento.getChildren(KEY_PROJECT); |
| if (children.length == 0) |
| return new String[0]; |
| List<String> projects = new ArrayList<>(children.length); |
| for (int i = 0; i < children.length; i++) { |
| String path = children[i].getTextData(); |
| if (path != null && path.length() > 0) |
| projects.add(path); |
| } |
| return projects.toArray(new String[0]); |
| } |
| |
| /** |
| * Restore projects associated with the currently checked out branch to the |
| * workspace |
| * |
| * @param monitor |
| */ |
| public void restore(final IProgressMonitor monitor) { |
| String branch = getBranch(); |
| if (branch != null) |
| restore(branch, monitor); |
| } |
| |
| /** |
| * Restore projects associated with the given branch to the workspace |
| * |
| * @param branch |
| * @param monitor |
| */ |
| public void restore(final String branch, final IProgressMonitor monitor) { |
| String[] paths = getProjectPaths(branch); |
| if (paths.length == 0) |
| return; |
| |
| Set<ProjectRecord> records = new LinkedHashSet<>(); |
| File parent = repository.getWorkTree(); |
| for (String path : paths) { |
| File root; |
| if (!REPO_ROOT.equals(path)) { |
| root = new File(parent, path); |
| } else { |
| root = parent; |
| } |
| if (!root.isDirectory()) { |
| continue; |
| } |
| File projectDescription = new File(root, |
| IProjectDescription.DESCRIPTION_FILE_NAME); |
| if (!projectDescription.isFile()) { |
| continue; |
| } |
| ProjectRecord record = new ProjectRecord(projectDescription); |
| if (record.getProjectDescription() == null) { |
| continue; |
| } |
| records.add(record); |
| } |
| if (records.isEmpty()) { |
| return; |
| } |
| try { |
| ProjectUtils.createProjects(records, true, null, monitor); |
| } catch (InvocationTargetException e) { |
| Activator |
| .logError("Error restoring branch-project associations", e); //$NON-NLS-1$ |
| } catch (InterruptedException ignored) { |
| // Ignored |
| } |
| } |
| } |