SelectionUtils now contains methods to return multiple repositories
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / actions / RepositoryActionHandler.java
blob8e788eb3ded6173de895fe7bf4da0a2e985d37e1
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, Mathias Kinzler <mathias.kinzler@sap.com>
6 * Copyright (C) 2011, Dariusz Luksza <dariusz@luksza.org>
7 * Copyright (C) 2012, 2013 Robin Stocker <robin@nibor.org>
8 * Copyright (C) 2012, 2013 François Rey <eclipse.org_@_francois_._rey_._name>
9 * Copyright (C) 2013 Laurent Goubet <laurent.goubet@obeo.fr>
10 * Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>)
11 * Copyright (C) 2016, Stefan Dirix <sdirix@eclipsesource.com>
13 * All rights reserved. This program and the accompanying materials
14 * are made available under the terms of the Eclipse Public License 2.0
15 * which accompanies this distribution, and is available at
16 * https://www.eclipse.org/legal/epl-2.0/
18 * SPDX-License-Identifier: EPL-2.0
19 *******************************************************************************/
20 package org.eclipse.egit.ui.internal.actions;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Set;
30 import org.eclipse.core.commands.AbstractHandler;
31 import org.eclipse.core.commands.ExecutionEvent;
32 import org.eclipse.core.commands.ExecutionException;
33 import org.eclipse.core.expressions.IEvaluationContext;
34 import org.eclipse.core.resources.IProject;
35 import org.eclipse.core.resources.IResource;
36 import org.eclipse.core.runtime.IPath;
37 import org.eclipse.egit.core.internal.CompareCoreUtils;
38 import org.eclipse.egit.core.project.RepositoryMapping;
39 import org.eclipse.egit.ui.internal.selection.SelectionUtils;
40 import org.eclipse.jface.viewers.ISelection;
41 import org.eclipse.jface.viewers.IStructuredSelection;
42 import org.eclipse.jface.viewers.StructuredSelection;
43 import org.eclipse.jgit.diff.DiffConfig;
44 import org.eclipse.jgit.diff.DiffEntry;
45 import org.eclipse.jgit.lib.Constants;
46 import org.eclipse.jgit.lib.ObjectReader;
47 import org.eclipse.jgit.lib.Ref;
48 import org.eclipse.jgit.lib.Repository;
49 import org.eclipse.jgit.revwalk.FollowFilter;
50 import org.eclipse.jgit.revwalk.RevCommit;
51 import org.eclipse.jgit.revwalk.RevSort;
52 import org.eclipse.jgit.revwalk.RevWalk;
53 import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
54 import org.eclipse.jgit.treewalk.filter.TreeFilter;
55 import org.eclipse.swt.widgets.Shell;
56 import org.eclipse.ui.IWorkbenchPage;
57 import org.eclipse.ui.IWorkbenchPart;
58 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
59 import org.eclipse.ui.handlers.HandlerUtil;
61 /**
62 * A helper class for Team Actions on Git controlled projects
64 abstract class RepositoryActionHandler extends AbstractHandler {
66 private IEvaluationContext evaluationContext;
68 IStructuredSelection mySelection;
70 /**
71 * Set the selection when used by {@link RepositoryAction} as
72 * {@link IWorkbenchWindowActionDelegate}
74 * @param selection
75 * the new selection
77 public void setSelection(ISelection selection) {
78 mySelection = SelectionUtils.getStructuredSelection(selection);
82 /**
83 * Retrieve the list of projects that contains the selected resources. All
84 * resources must actually map to a project shared with egit, otherwise an
85 * empty array is returned. In case of a linked resource, the project
86 * returned is the one that contains the link target and is shared with
87 * egit, if any, otherwise an empty array is also returned.
89 * @param event
90 * @return the projects hosting the selected resources
91 * @throws ExecutionException
93 protected IProject[] getProjectsForSelectedResources(ExecutionEvent event)
94 throws ExecutionException {
95 IStructuredSelection selection = getSelection(event);
96 return SelectionUtils.getSelectedProjects(selection);
99 /**
100 * Retrieve the list of projects that contains the selected resources. All
101 * resources must actually map to a project shared with egit, otherwise an
102 * empty array is returned. In case of a linked resource, the project
103 * returned is the one that contains the link target and is shared with
104 * egit, if any, otherwise an empty array is also returned.
106 * @return the projects hosting the selected resources
108 protected IProject[] getProjectsForSelectedResources() {
109 IStructuredSelection selection = getSelection();
110 return SelectionUtils.getSelectedProjects(selection);
114 * @param projects
115 * a list of projects
116 * @return the repositories that projects map to if all projects are mapped
118 protected Repository[] getRepositoriesFor(final IProject[] projects) {
119 Set<Repository> ret = new LinkedHashSet<>();
120 for (IProject project : projects) {
121 RepositoryMapping repositoryMapping = RepositoryMapping
122 .getMapping(project);
123 if (repositoryMapping == null)
124 return new Repository[0];
125 ret.add(repositoryMapping.getRepository());
127 return ret.toArray(new Repository[0]);
131 * Determines whether the selection contains only resources that are in some
132 * git repository.
134 * @return {@code true} if all resources in the selection belong to a git
135 * repository known to EGit.
137 protected boolean haveSelectedResourcesWithRepository() {
138 IStructuredSelection selection = getSelection();
139 if (selection != null) {
140 IResource[] resources = SelectionUtils
141 .getSelectedResources(selection);
142 if (resources.length > 0) {
143 for (IResource resource : resources) {
144 if (resource == null
145 || RepositoryMapping.getMapping(resource) == null) {
146 return false;
149 return true;
152 return false;
156 * Figure out which repository to use. All selected resources must map to
157 * the same Git repository.
159 * @param warn
160 * Put up a message dialog to warn why a resource was not
161 * selected
162 * @param event
163 * @return repository for current project, or null
164 * @throws ExecutionException
166 protected Repository getRepository(boolean warn, ExecutionEvent event)
167 throws ExecutionException {
168 IStructuredSelection selection = getSelection(event);
169 if (warn) {
170 Shell shell = getShell(event);
171 return SelectionUtils.getRepositoryOrWarn(selection, shell);
172 } else {
173 return SelectionUtils.getRepository(selection);
178 * Figure out which repository to use. All selected resources must map to
179 * the same Git repository.
181 * @return repository for current project, or null
183 protected Repository getRepository() {
184 IStructuredSelection selection = getSelection();
185 return SelectionUtils.getRepository(selection);
189 * Figure out which repositories to use. All selected resources must map to
190 * a Git repository.
192 * @param event
194 * @return repositories for selection, or an empty array
195 * @throws ExecutionException
197 protected Repository[] getRepositories(ExecutionEvent event)
198 throws ExecutionException {
199 IStructuredSelection selection = getSelection(event);
200 return SelectionUtils.getRepositories(selection);
204 * Get the currently selected repositories. All selected projects must map
205 * to a repository.
207 * @return repositories for selection, or an empty array
209 public Repository[] getRepositories() {
210 IStructuredSelection selection = getSelection();
211 return SelectionUtils.getRepositories(selection);
215 * @param event
216 * the execution event, must not be null
217 * @return the current selection
218 * @throws ExecutionException
219 * if the selection can't be determined
221 protected static IStructuredSelection getSelection(ExecutionEvent event)
222 throws ExecutionException {
223 if (event == null)
224 throw new IllegalArgumentException("event must not be NULL"); //$NON-NLS-1$
225 Object context = event.getApplicationContext();
226 if (context instanceof IEvaluationContext)
227 return SelectionUtils.getSelection((IEvaluationContext) context);
228 return StructuredSelection.EMPTY;
232 * @return the current selection
234 protected IStructuredSelection getSelection() {
235 // if the selection was set explicitly, use it
236 if (mySelection != null)
237 return mySelection;
238 return SelectionUtils.getSelection(evaluationContext);
241 @Override
242 public void setEnabled(Object evaluationContext) {
243 this.evaluationContext = (IEvaluationContext) evaluationContext;
247 * @param event
248 * @return the resources in the selection
249 * @throws ExecutionException
251 protected IResource[] getSelectedResources(ExecutionEvent event)
252 throws ExecutionException {
253 IStructuredSelection selection = getSelection(event);
254 return SelectionUtils.getSelectedResources(selection);
257 protected IPath[] getSelectedLocations(ExecutionEvent event)
258 throws ExecutionException {
259 IStructuredSelection selection = getSelection(event);
260 return SelectionUtils.getSelectedLocations(selection);
264 * @return the resources in the selection
266 protected IResource[] getSelectedResources() {
267 IStructuredSelection selection = getSelection();
268 return SelectionUtils.getSelectedResources(selection);
272 * @return the locations in the selection
274 protected IPath[] getSelectedLocations() {
275 IStructuredSelection selection = getSelection();
276 return SelectionUtils.getSelectedLocations(selection);
280 * @return true if all selected items map to the same repository, false otherwise.
282 protected boolean selectionMapsToSingleRepository() {
283 return getRepository() != null;
287 * @param event
288 * @return the shell
289 * @throws ExecutionException
291 protected Shell getShell(ExecutionEvent event) throws ExecutionException {
292 return HandlerUtil.getActiveShellChecked(event);
296 * @param event
297 * @return the page
298 * @throws ExecutionException
300 protected IWorkbenchPage getPartPage(ExecutionEvent event)
301 throws ExecutionException {
302 return getPart(event).getSite().getPage();
306 * @param event
307 * @return the page
308 * @throws ExecutionException
310 protected IWorkbenchPart getPart(ExecutionEvent event)
311 throws ExecutionException {
312 return HandlerUtil.getActivePartChecked(event);
318 * @param repository
319 * the repository to check
320 * @return {@code true} when {@link Constants#HEAD} can be resolved,
321 * {@code false} otherwise
323 protected boolean containsHead(Repository repository) {
324 try {
325 return repository != null ? repository.resolve(Constants.HEAD) != null
326 : false;
327 } catch (Exception e) {
328 // do nothing
331 return false;
334 protected boolean isLocalBranchCheckedout(Repository repository) {
335 try {
336 String fullBranch = repository.getFullBranch();
337 return fullBranch != null
338 && fullBranch.startsWith(Constants.R_HEADS);
339 } catch (Exception e) {
340 // do nothing
343 return false;
346 protected String getPreviousPath(Repository repository,
347 ObjectReader reader, RevCommit headCommit,
348 RevCommit previousCommit, String path) throws IOException {
349 DiffEntry diffEntry = CompareCoreUtils.getChangeDiffEntry(repository, path,
350 headCommit, previousCommit, reader);
351 if (diffEntry != null)
352 return diffEntry.getOldPath();
353 else
354 return path;
357 protected RevCommit getHeadCommit(IResource resource) throws IOException {
358 Repository repository = getRepository();
359 if (resource == null) {
360 return null;
362 RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
363 if (mapping == null) {
364 return null;
366 String path = mapping.getRepoRelativePath(resource);
367 if (path == null) {
368 return null;
370 try (RevWalk rw = new RevWalk(repository)) {
371 rw.sort(RevSort.COMMIT_TIME_DESC, true);
372 rw.sort(RevSort.BOUNDARY, true);
373 if (path.length() > 0) {
374 DiffConfig diffConfig = repository.getConfig().get(
375 DiffConfig.KEY);
376 FollowFilter filter = FollowFilter.create(path, diffConfig);
377 rw.setTreeFilter(filter);
380 Ref head = repository.findRef(Constants.HEAD);
381 if (head == null) {
382 return null;
384 RevCommit headCommit = rw.parseCommit(head.getObjectId());
385 rw.close();
386 return headCommit;
391 * Returns the previous commit of the given resources.
393 * @param resources
394 * The {@link IResource} for which the previous commit shall be
395 * determined.
396 * @return The second to last commit which touched any of the given
397 * resources.
398 * @throws IOException
399 * When the commit can not be parsed.
401 protected List<RevCommit> findPreviousCommits(
402 Collection<IResource> resources) throws IOException {
403 List<RevCommit> result = new ArrayList<>();
404 Repository repository = getRepository();
405 RepositoryMapping mapping = RepositoryMapping.getMapping(resources
406 .iterator().next()
407 .getProject());
408 if (mapping == null) {
409 return result;
411 try (RevWalk rw = new RevWalk(repository)) {
412 rw.sort(RevSort.COMMIT_TIME_DESC, true);
413 rw.sort(RevSort.BOUNDARY, true);
415 List<TreeFilter> filters = new ArrayList<>();
416 DiffConfig diffConfig = repository.getConfig().get(DiffConfig.KEY);
417 for (IResource resource : resources) {
418 String path = mapping.getRepoRelativePath(resource);
420 if (path != null && path.length() > 0) {
421 filters.add(FollowFilter.create(path, diffConfig));
425 if (filters.size() >= 2) {
426 TreeFilter filter = OrTreeFilter.create(filters);
427 rw.setTreeFilter(filter);
428 } else if (filters.size() == 1) {
429 rw.setTreeFilter(filters.get(0));
432 Ref head = repository.findRef(Constants.HEAD);
433 if (head == null) {
434 return result;
436 RevCommit headCommit = rw.parseCommit(head.getObjectId());
437 rw.markStart(headCommit);
438 headCommit = rw.next();
440 if (headCommit == null)
441 return result;
442 List<RevCommit> directParents = Arrays.asList(headCommit
443 .getParents());
445 RevCommit previousCommit = rw.next();
446 while (previousCommit != null && result.size() < directParents.size()) {
447 if (directParents.contains(previousCommit)) {
448 result.add(previousCommit);
450 previousCommit = rw.next();
452 rw.dispose();
454 return result;
457 // keep track of the path of an ancestor (for following renames)
458 protected static final class PreviousCommit {
459 final RevCommit commit;
460 final String path;
461 PreviousCommit(final RevCommit commit, final String path) {
462 this.commit = commit;
463 this.path = path;
468 * By default egit operates only on resources that map to a project shared
469 * with egit. For linked resources the project that contains the link
470 * target, if any, must be shared with egit.
472 * @return the projects hosting the selected resources
474 @Override
475 public boolean isEnabled() {
476 return getProjectsForSelectedResources().length > 0;
480 * Determines whether the enablement state shall always be recomputed or
481 * only when the selection changes. This default implementation returns
482 * {@code false}.
484 * @return {@code false} if the enablement state depends solely on the
485 * selection, {@code true} if the enablement must be recomputed even
486 * if the selection did not change.
488 protected boolean alwaysCheckEnabled() {
489 return false;