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
;
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
;
62 * A helper class for Team Actions on Git controlled projects
64 abstract class RepositoryActionHandler
extends AbstractHandler
{
66 private IEvaluationContext evaluationContext
;
68 IStructuredSelection mySelection
;
71 * Set the selection when used by {@link RepositoryAction} as
72 * {@link IWorkbenchWindowActionDelegate}
77 public void setSelection(ISelection selection
) {
78 mySelection
= SelectionUtils
.getStructuredSelection(selection
);
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.
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
);
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
);
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
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
) {
145 || RepositoryMapping
.getMapping(resource
) == null) {
156 * Figure out which repository to use. All selected resources must map to
157 * the same Git repository.
160 * Put up a message dialog to warn why a resource was not
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
);
170 Shell shell
= getShell(event
);
171 return SelectionUtils
.getRepositoryOrWarn(selection
, shell
);
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
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
207 * @return repositories for selection, or an empty array
209 public Repository
[] getRepositories() {
210 IStructuredSelection selection
= getSelection();
211 return SelectionUtils
.getRepositories(selection
);
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
{
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)
238 return SelectionUtils
.getSelection(evaluationContext
);
242 public void setEnabled(Object evaluationContext
) {
243 this.evaluationContext
= (IEvaluationContext
) evaluationContext
;
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;
289 * @throws ExecutionException
291 protected Shell
getShell(ExecutionEvent event
) throws ExecutionException
{
292 return HandlerUtil
.getActiveShellChecked(event
);
298 * @throws ExecutionException
300 protected IWorkbenchPage
getPartPage(ExecutionEvent event
)
301 throws ExecutionException
{
302 return getPart(event
).getSite().getPage();
308 * @throws ExecutionException
310 protected IWorkbenchPart
getPart(ExecutionEvent event
)
311 throws ExecutionException
{
312 return HandlerUtil
.getActivePartChecked(event
);
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
) {
325 return repository
!= null ? repository
.resolve(Constants
.HEAD
) != null
327 } catch (Exception e
) {
334 protected boolean isLocalBranchCheckedout(Repository repository
) {
336 String fullBranch
= repository
.getFullBranch();
337 return fullBranch
!= null
338 && fullBranch
.startsWith(Constants
.R_HEADS
);
339 } catch (Exception e
) {
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();
357 protected RevCommit
getHeadCommit(IResource resource
) throws IOException
{
358 Repository repository
= getRepository();
359 if (resource
== null) {
362 RepositoryMapping mapping
= RepositoryMapping
.getMapping(resource
);
363 if (mapping
== null) {
366 String path
= mapping
.getRepoRelativePath(resource
);
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(
376 FollowFilter filter
= FollowFilter
.create(path
, diffConfig
);
377 rw
.setTreeFilter(filter
);
380 Ref head
= repository
.findRef(Constants
.HEAD
);
384 RevCommit headCommit
= rw
.parseCommit(head
.getObjectId());
391 * Returns the previous commit of the given resources.
394 * The {@link IResource} for which the previous commit shall be
396 * @return The second to last commit which touched any of the given
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
408 if (mapping
== null) {
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
);
436 RevCommit headCommit
= rw
.parseCommit(head
.getObjectId());
437 rw
.markStart(headCommit
);
438 headCommit
= rw
.next();
440 if (headCommit
== null)
442 List
<RevCommit
> directParents
= Arrays
.asList(headCommit
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();
457 // keep track of the path of an ancestor (for following renames)
458 protected static final class PreviousCommit
{
459 final RevCommit commit
;
461 PreviousCommit(final RevCommit commit
, final String path
) {
462 this.commit
= commit
;
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
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
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() {