refactor: simplify collection.toArray()
[egit/eclipse.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / op / BranchOperation.java
blob562f2b69f1175d244be76de2c80e4a06da58edd8
1 /*******************************************************************************
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4 * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
5 * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com>
6 * Copyright (C) 2010, 2011, Mathias Kinzler <mathias.kinzler@sap.com>
7 * Copyright (C) 2015, Stephan Hackstedt <stephan.hackstedt@googlemail.com>
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License 2.0
11 * which accompanies this distribution, and is available at
12 * https://www.eclipse.org/legal/epl-2.0/
14 * SPDX-License-Identifier: EPL-2.0
15 *******************************************************************************/
16 package org.eclipse.egit.core.op;
18 import java.io.File;
19 import java.io.IOException;
20 import java.text.MessageFormat;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.resources.IProjectDescription;
28 import org.eclipse.core.resources.IWorkspace;
29 import org.eclipse.core.resources.IWorkspaceRunnable;
30 import org.eclipse.core.resources.ResourcesPlugin;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IPath;
33 import org.eclipse.core.runtime.IProgressMonitor;
34 import org.eclipse.core.runtime.SubMonitor;
35 import org.eclipse.core.runtime.jobs.ISchedulingRule;
36 import org.eclipse.egit.core.Activator;
37 import org.eclipse.egit.core.EclipseGitProgressTransformer;
38 import org.eclipse.egit.core.internal.CoreText;
39 import org.eclipse.egit.core.internal.job.RuleUtil;
40 import org.eclipse.egit.core.internal.util.ProjectUtil;
41 import org.eclipse.jgit.annotations.NonNull;
42 import org.eclipse.jgit.api.CheckoutCommand;
43 import org.eclipse.jgit.api.CheckoutResult;
44 import org.eclipse.jgit.api.CheckoutResult.Status;
45 import org.eclipse.jgit.api.Git;
46 import org.eclipse.jgit.api.errors.CheckoutConflictException;
47 import org.eclipse.jgit.api.errors.GitAPIException;
48 import org.eclipse.jgit.api.errors.JGitInternalException;
49 import org.eclipse.jgit.lib.Constants;
50 import org.eclipse.jgit.lib.ObjectId;
51 import org.eclipse.jgit.lib.Ref;
52 import org.eclipse.jgit.lib.Repository;
53 import org.eclipse.jgit.revwalk.RevCommit;
54 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
55 import org.eclipse.jgit.treewalk.FileTreeIterator;
56 import org.eclipse.jgit.treewalk.TreeWalk;
57 import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
58 import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
59 import org.eclipse.jgit.treewalk.filter.TreeFilter;
60 import org.eclipse.jgit.util.FileUtils;
61 import org.eclipse.osgi.util.NLS;
63 /**
64 * This class implements checkouts of a specific revision. A check is made that
65 * this can be done without data loss.
67 public class BranchOperation extends BaseOperation {
69 private final String target;
71 private @NonNull CheckoutResult result = CheckoutResult.NOT_TRIED_RESULT;
73 private boolean delete;
75 /**
76 * Construct a {@link BranchOperation} object for a {@link Ref}.
78 * @param repository
79 * @param target
80 * a {@link Ref} name or {@link RevCommit} id
82 public BranchOperation(Repository repository, String target) {
83 this(repository, target, true);
86 /**
87 * Construct a {@link BranchOperation} object for a {@link Ref}.
89 * @param repository
90 * @param target
91 * a {@link Ref} name or {@link RevCommit} id
92 * @param delete
93 * true to delete missing projects on new branch, false to close
94 * them
96 public BranchOperation(Repository repository, String target, boolean delete) {
97 super(repository);
98 this.target = target;
99 this.delete = delete;
102 @Override
103 public void execute(IProgressMonitor m) throws CoreException {
104 IWorkspaceRunnable action = new IWorkspaceRunnable() {
106 @Override
107 public void run(IProgressMonitor pm) throws CoreException {
108 SubMonitor progress = SubMonitor.convert(pm, 4);
109 preExecute(progress.newChild(1));
111 closeProjectsMissingAfterCheckout(progress);
113 try (Git git = new Git(repository)) {
114 CheckoutCommand co = git.checkout().setProgressMonitor(
115 new EclipseGitProgressTransformer(
116 progress.newChild(1)));
117 co.setName(target);
119 try {
120 co.call();
121 } catch (CheckoutConflictException e) {
122 return;
123 } catch (JGitInternalException e) {
124 throw new CoreException(
125 Activator.error(e.getMessage(), e));
126 } catch (GitAPIException e) {
127 throw new CoreException(
128 Activator.error(e.getMessage(), e));
129 } finally {
130 result = co.getResult();
132 if (result.getStatus() == Status.NONDELETED) {
133 retryDelete(result.getUndeletedList());
135 refreshAffectedProjects(progress);
137 postExecute(progress.newChild(1));
141 private void closeProjectsMissingAfterCheckout(SubMonitor progress)
142 throws CoreException {
143 IProject[] missing = getMissingProjects(target, ProjectUtil
144 .getValidOpenProjects(repository));
146 progress.setTaskName(NLS.bind(
147 CoreText.BranchOperation_performingBranch, target));
148 progress.setWorkRemaining(missing.length > 0 ? 4 : 3);
150 if (missing.length > 0) {
151 SubMonitor closeMonitor = progress.newChild(1);
152 closeMonitor.setWorkRemaining(missing.length);
153 for (IProject project : missing) {
154 closeMonitor.subTask(MessageFormat.format(
155 CoreText.BranchOperation_closingMissingProject,
156 project.getName()));
157 project.close(closeMonitor.newChild(1));
162 private void refreshAffectedProjects(SubMonitor progress)
163 throws CoreException {
164 List<String> pathsToHandle = new ArrayList<>();
165 pathsToHandle.addAll(result.getModifiedList());
166 pathsToHandle.addAll(result.getRemovedList());
167 pathsToHandle.addAll(result.getConflictList());
168 IProject[] refreshProjects = ProjectUtil
169 .getProjectsContaining(repository, pathsToHandle);
170 ProjectUtil.refreshValidProjects(refreshProjects, delete,
171 progress.newChild(1));
174 // lock workspace to protect working tree changes
175 ResourcesPlugin.getWorkspace().run(action, getSchedulingRule(),
176 IWorkspace.AVOID_UPDATE, m);
179 @Override
180 public ISchedulingRule getSchedulingRule() {
181 return RuleUtil.getRule(repository);
185 * @return the result of the operation
187 @NonNull
188 public CheckoutResult getResult() {
189 return result;
192 void retryDelete(List<String> pathList) {
193 // try to delete, but for a short time only
194 long startTime = System.currentTimeMillis();
195 for (String path : pathList) {
196 if (System.currentTimeMillis() - startTime > 1000)
197 break;
198 File fileToDelete = new File(repository.getWorkTree(), path);
199 if (fileToDelete.exists())
200 try {
201 // Only files should be passed here, thus
202 // we ignore attempt to delete submodules when
203 // we switch to a branch without a submodule
204 if (!fileToDelete.isFile())
205 FileUtils.delete(fileToDelete, FileUtils.RETRY);
206 } catch (IOException e) {
207 // ignore here
213 * Compute the current projects that will be missing after the given branch
214 * is checked out
216 * @param branch
217 * @param currentProjects
218 * @return non-null but possibly empty array of missing projects
220 private IProject[] getMissingProjects(String branch,
221 IProject[] currentProjects) {
222 if (delete || currentProjects.length == 0)
223 return new IProject[0];
225 ObjectId targetTreeId;
226 ObjectId currentTreeId;
227 try {
228 targetTreeId = repository.resolve(branch + "^{tree}"); //$NON-NLS-1$
229 currentTreeId = repository.resolve(Constants.HEAD + "^{tree}"); //$NON-NLS-1$
230 } catch (IOException e) {
231 return new IProject[0];
233 if (targetTreeId == null || currentTreeId == null)
234 return new IProject[0];
236 Map<File, IProject> locations = new HashMap<>();
237 for (IProject project : currentProjects) {
238 IPath location = project.getLocation();
239 if (location == null)
240 continue;
241 location = location
242 .append(IProjectDescription.DESCRIPTION_FILE_NAME);
243 locations.put(location.toFile(), project);
246 List<IProject> toBeClosed = new ArrayList<>();
247 File root = repository.getWorkTree();
248 try (TreeWalk walk = new TreeWalk(repository)) {
249 walk.addTree(targetTreeId);
250 walk.addTree(currentTreeId);
251 walk.addTree(new FileTreeIterator(repository));
252 walk.setRecursive(true);
253 walk.setFilter(AndTreeFilter.create(PathSuffixFilter
254 .create(IProjectDescription.DESCRIPTION_FILE_NAME),
255 TreeFilter.ANY_DIFF));
256 while (walk.next()) {
257 AbstractTreeIterator targetIter = walk.getTree(0,
258 AbstractTreeIterator.class);
259 if (targetIter != null)
260 continue;
262 AbstractTreeIterator currentIter = walk.getTree(1,
263 AbstractTreeIterator.class);
264 AbstractTreeIterator workingIter = walk.getTree(2,
265 AbstractTreeIterator.class);
266 if (currentIter == null || workingIter == null)
267 continue;
269 IProject project = locations.get(new File(root, walk
270 .getPathString()));
271 if (project != null)
272 toBeClosed.add(project);
274 } catch (IOException e) {
275 return new IProject[0];
277 return toBeClosed.toArray(new IProject[0]);