1 /******************************************************************************
2 * Copyright (c) 2012 GitHub Inc.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License 2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-2.0/
8 * SPDX-License-Identifier: EPL-2.0
11 * Kevin Sawicki (GitHub Inc.) - initial API and implementation
12 *****************************************************************************/
13 package org
.eclipse
.egit
.ui
.internal
.branch
;
16 import java
.io
.IOException
;
17 import java
.lang
.reflect
.InvocationTargetException
;
18 import java
.util
.ArrayList
;
19 import java
.util
.Collections
;
20 import java
.util
.LinkedHashSet
;
21 import java
.util
.List
;
22 import java
.util
.Objects
;
24 import java
.util
.stream
.Stream
;
26 import org
.eclipse
.core
.resources
.IProject
;
27 import org
.eclipse
.core
.resources
.IProjectDescription
;
28 import org
.eclipse
.core
.runtime
.CoreException
;
29 import org
.eclipse
.core
.runtime
.IPath
;
30 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
31 import org
.eclipse
.egit
.core
.internal
.util
.ProjectUtil
;
32 import org
.eclipse
.egit
.core
.internal
.util
.ResourceUtil
;
33 import org
.eclipse
.egit
.ui
.Activator
;
34 import org
.eclipse
.egit
.ui
.internal
.clone
.ProjectRecord
;
35 import org
.eclipse
.egit
.ui
.internal
.clone
.ProjectUtils
;
36 import org
.eclipse
.jgit
.annotations
.NonNull
;
37 import org
.eclipse
.jgit
.lib
.Repository
;
38 import org
.eclipse
.jgit
.util
.StringUtils
;
41 * Class to track which projects are imported for each branch.
43 * A unique preference is set for each repository/branch combination that is
44 * persisted that includes information to be able to restore projects when the
45 * branch is later checked out.
48 * The workflow is as follows:
51 * <li>Call {@link #snapshot()} to get the current projects for the currently
52 * checked out branch</li>
53 * <li>Call {@link #save(ProjectTrackerMemento)} after the new branch has been
54 * successfully checked out with the memento returned from step 1</li>
55 * <li>Call {@link #restore(IProgressMonitor)} to restore the projects for the
56 * newly checked out branch</li>
59 class BranchProjectTracker
{
61 private static final String REPO_ROOT
= "/"; //$NON-NLS-1$
63 private final Repository
[] repositories
;
66 * Create tracker for repositories
70 public BranchProjectTracker(final Repository
[] repositories
) {
71 this.repositories
= repositories
;
75 * Create tracker for repository
79 public BranchProjectTracker(final Repository repository
) {
80 this.repositories
= new Repository
[] { repository
};
83 private String
getBranch(Repository repo
) {
85 return repo
.getBranch();
86 } catch (IOException e
) {
92 * Snapshot the projects currently associated with the repository
94 * The memento returned can be later passed to
95 * {@link #save(ProjectTrackerMemento)} to persist it
97 * @see #save(ProjectTrackerMemento)
100 public ProjectTrackerMemento
snapshot() {
101 ProjectTrackerMemento memento
= new ProjectTrackerMemento();
102 Stream
.of(repositories
).map(this::takeSnapshot
).filter(Objects
::nonNull
)
103 .forEach(memento
::addSnapshot
);
107 private ProjectTrackerPreferenceSnapshot
takeSnapshot(Repository repo
) {
108 String branch
= getBranch(repo
);
109 if (StringUtils
.isEmptyOrNull(branch
)) {
112 List
<String
> projectPaths
= getAssociatedProjectsPaths(repo
);
113 if (projectPaths
.isEmpty()) {
116 return new ProjectTrackerPreferenceSnapshot(repo
, branch
,
121 private List
<String
> getAssociatedProjectsPaths(Repository repo
) {
122 IProject
[] projects
= getValidOpenProjects(repo
);
123 if (projects
== null) {
124 return Collections
.emptyList();
126 List
<String
> projectPaths
= new ArrayList
<>();
128 final String workDir
= repo
.getWorkTree().getAbsolutePath();
129 for (IProject project
: projects
) {
130 IPath path
= project
.getLocation();
134 // Only remember mapped projects
135 if (!ResourceUtil
.isSharedWithGit(project
)) {
138 String fullPath
= path
.toOSString();
139 if (fullPath
.startsWith(workDir
)) {
140 String relative
= fullPath
.substring(workDir
.length());
141 if (relative
.length() == 0) {
142 relative
= REPO_ROOT
;
144 projectPaths
.add(relative
);
150 private IProject
[] getValidOpenProjects(Repository repo
) {
152 return ProjectUtil
.getValidOpenProjects(repo
);
153 } catch (CoreException e
) {
159 * Associate projects with branch. The specified memento must the one
160 * previously returned from a call to {@link #snapshot()}.
164 * @return this tracker
166 public BranchProjectTracker
save(ProjectTrackerMemento snapshot
) {
167 snapshot
.getSnapshots().stream()
168 .forEach(BranchProjectTracker
::savePreference
);
172 private static void savePreference(ProjectTrackerPreferenceSnapshot snapshot
) {
173 Repository repo
= snapshot
.getRepository();
174 String branch
= snapshot
.getBranch();
175 List
<String
> projects
= snapshot
.getAssociatedProjects();
176 ProjectTrackerPreferenceHelper
.saveToPreferences(repo
, branch
,
181 * Restore projects associated with the currently checked out branch to the
186 public void restore(final IProgressMonitor monitor
) {
187 for (Repository repo
: repositories
) {
188 String branch
= getBranch(repo
);
189 if (branch
!= null) {
190 restore(repo
, branch
, monitor
);
196 * Restore projects associated with the given branch to the workspace
202 public void restore(Repository repo
, final String branch
,
203 final IProgressMonitor monitor
) {
204 List
<String
> paths
= ProjectTrackerPreferenceHelper
205 .restoreFromPreferences(repo
, branch
);
206 if (paths
.isEmpty()) {
209 Set
<ProjectRecord
> records
= new LinkedHashSet
<>();
210 File parent
= repo
.getWorkTree();
211 for (String path
: paths
) {
213 if (!REPO_ROOT
.equals(path
)) {
214 root
= new File(parent
, path
);
218 if (!root
.isDirectory()) {
221 File projectDescription
= new File(root
,
222 IProjectDescription
.DESCRIPTION_FILE_NAME
);
223 if (!projectDescription
.isFile()) {
226 ProjectRecord record
= new ProjectRecord(projectDescription
);
227 if (record
.getProjectDescription() == null) {
232 if (records
.isEmpty()) {
236 ProjectUtils
.createProjects(records
, true, null, monitor
);
237 } catch (InvocationTargetException e
) {
239 .logError("Error restoring branch-project associations", e
); //$NON-NLS-1$
240 } catch (InterruptedException ignored
) {