1 /*******************************************************************************
2 * Copyright (C) 2015, Max Hohenegger <eclipse@hohenegger.eu>
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11 package org
.eclipse
.egit
.gitflow
.op
;
13 import static org
.eclipse
.egit
.gitflow
.Activator
.error
;
14 import static org
.eclipse
.jgit
.api
.MergeCommand
.FastForwardMode
.NO_FF
;
16 import java
.io
.IOException
;
17 import java
.lang
.reflect
.InvocationTargetException
;
18 import java
.net
.URISyntaxException
;
20 import org
.eclipse
.core
.runtime
.CoreException
;
21 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
22 import org
.eclipse
.core
.runtime
.SubMonitor
;
23 import org
.eclipse
.core
.runtime
.jobs
.ISchedulingRule
;
24 import org
.eclipse
.egit
.core
.internal
.job
.RuleUtil
;
25 import org
.eclipse
.egit
.core
.op
.BranchOperation
;
26 import org
.eclipse
.egit
.core
.op
.CreateLocalBranchOperation
;
27 import org
.eclipse
.egit
.core
.op
.DeleteBranchOperation
;
28 import org
.eclipse
.egit
.core
.op
.FetchOperation
;
29 import org
.eclipse
.egit
.core
.op
.IEGitOperation
;
30 import org
.eclipse
.egit
.core
.op
.MergeOperation
;
31 import org
.eclipse
.egit
.gitflow
.GitFlowRepository
;
32 import org
.eclipse
.egit
.gitflow
.internal
.CoreText
;
33 import org
.eclipse
.jgit
.annotations
.NonNull
;
34 import org
.eclipse
.jgit
.api
.CheckoutResult
;
35 import org
.eclipse
.jgit
.api
.CheckoutResult
.Status
;
36 import org
.eclipse
.jgit
.api
.MergeResult
;
37 import org
.eclipse
.jgit
.api
.errors
.GitAPIException
;
38 import org
.eclipse
.jgit
.lib
.Ref
;
39 import org
.eclipse
.jgit
.lib
.Repository
;
40 import org
.eclipse
.jgit
.revwalk
.RevCommit
;
41 import org
.eclipse
.jgit
.revwalk
.RevWalk
;
42 import org
.eclipse
.jgit
.revwalk
.RevWalkUtils
;
43 import org
.eclipse
.jgit
.revwalk
.filter
.RevFilter
;
44 import org
.eclipse
.jgit
.transport
.FetchResult
;
45 import org
.eclipse
.jgit
.transport
.RemoteConfig
;
46 import org
.eclipse
.osgi
.util
.NLS
;
49 * Common logic for Git Flow operations.
51 // TODO: This class should be called AbstractGitFlowOperation for consistency
52 abstract public class GitFlowOperation
implements IEGitOperation
{
56 public static final String SEP
= "/"; //$NON-NLS-1$
59 * repository that is operated on.
61 protected GitFlowRepository repository
;
64 * the status of the latest merge from this operation
66 // TODO: Remove from this class. Not all GitFlow operations involve a merge
67 protected MergeResult mergeResult
;
72 public GitFlowOperation(GitFlowRepository repository
) {
73 this.repository
= repository
;
77 public ISchedulingRule
getSchedulingRule() {
78 return RuleUtil
.getRule(repository
.getRepository());
84 * @return operation constructed from parameters
86 protected CreateLocalBranchOperation
createBranchFromHead(
87 String branchName
, RevCommit sourceCommit
) {
88 return new CreateLocalBranchOperation(repository
.getRepository(),
89 branchName
, sourceCommit
);
98 * @throws CoreException
100 protected void start(IProgressMonitor monitor
, String branchName
,
101 RevCommit sourceCommit
) throws CoreException
{
102 SubMonitor progress
= SubMonitor
.convert(monitor
, 2);
103 CreateLocalBranchOperation branchOperation
= createBranchFromHead(
104 branchName
, sourceCommit
);
105 branchOperation
.execute(progress
.newChild(1));
106 BranchOperation checkoutOperation
= new BranchOperation(
107 repository
.getRepository(), branchName
);
108 checkoutOperation
.execute(progress
.newChild(1));
117 * @param fastForwardSingleCommit Has no effect if {@code squash} is true.
119 * @throws CoreException
122 protected void finish(IProgressMonitor monitor
, String branchName
,
123 boolean squash
, boolean keepBranch
, boolean fastForwardSingleCommit
)
124 throws CoreException
{
126 SubMonitor progress
= SubMonitor
.convert(monitor
, 2);
127 mergeResult
= mergeTo(progress
.newChild(1), branchName
,
128 repository
.getConfig()
129 .getDevelop(), squash
, fastForwardSingleCommit
);
130 if (!mergeResult
.getMergeStatus().isSuccessful()) {
134 Ref branch
= repository
.findBranch(branchName
);
135 if (branch
== null) {
136 throw new IllegalStateException(NLS
.bind(
137 CoreText
.GitFlowOperation_branchMissing
, branchName
));
139 boolean forceDelete
= squash
;
142 new DeleteBranchOperation(repository
.getRepository(), branch
,
143 forceDelete
).execute(progress
.newChild(1));
145 } catch (IOException e
) {
146 throw new RuntimeException(e
);
151 * Finish without squash, NO_FF and keep for single commit branches:
152 * {@link org.eclipse.egit.gitflow.op.GitFlowOperation#finish(IProgressMonitor, String, boolean, boolean, boolean)}
156 * @throws CoreException
158 protected void finish(IProgressMonitor monitor
, String branchName
)
159 throws CoreException
{
160 finish(monitor
, branchName
, false, false, false);
166 * @param targetBranchName
168 * @param fastForwardSingleCommit Has no effect if {@code squash} is true.
169 * @return result of merging back to targetBranchName
170 * @throws CoreException
173 protected @NonNull MergeResult
mergeTo(IProgressMonitor monitor
, String branchName
,
174 String targetBranchName
, boolean squash
, boolean fastForwardSingleCommit
) throws CoreException
{
176 if (!repository
.hasBranch(targetBranchName
)) {
177 throw new RuntimeException(NLS
.bind(
178 CoreText
.GitFlowOperation_branchNotFound
,
181 SubMonitor progress
= SubMonitor
.convert(monitor
, 2);
182 boolean dontCloseProjects
= false;
183 Repository gitRepo
= repository
.getRepository();
184 BranchOperation branchOperation
= new BranchOperation(gitRepo
,
185 targetBranchName
, dontCloseProjects
);
186 branchOperation
.execute(progress
.newChild(1));
187 Status status
= branchOperation
.getResult(gitRepo
).getStatus();
188 if (!CheckoutResult
.Status
.OK
.equals(status
)) {
189 throw new CoreException(error(NLS
.bind(
190 CoreText
.GitFlowOperation_unableToCheckout
, branchName
,
191 status
.toString())));
193 MergeOperation mergeOperation
= new MergeOperation(gitRepo
,
195 mergeOperation
.setSquash(squash
);
197 mergeOperation
.setCommit(true);
199 if (!squash
&& (!fastForwardSingleCommit
|| hasMultipleCommits(branchName
))) {
200 mergeOperation
.setFastForwardMode(NO_FF
);
202 mergeOperation
.execute(progress
.newChild(1));
204 MergeResult result
= mergeOperation
.getResult();
205 if (result
== null) {
206 throw new CoreException(error(NLS
.bind(
207 CoreText
.GitFlowOperation_unableToMerge
, branchName
,
212 } catch (GitAPIException
| IOException e
) {
213 throw new RuntimeException(e
);
217 private boolean hasMultipleCommits(String branchName
) throws IOException
{
218 return getAheadOfDevelopCount(branchName
) > 1;
221 private int getAheadOfDevelopCount(String branchName
) throws IOException
{
222 String parentBranch
= repository
.getConfig().getDevelop();
224 Ref develop
= repository
.findBranch(parentBranch
);
225 Ref branch
= repository
.findBranch(branchName
);
227 try (RevWalk walk
= new RevWalk(repository
.getRepository())) {
228 RevCommit branchCommit
= walk
.parseCommit(branch
.getObjectId());
229 RevCommit developCommit
= walk
.parseCommit(develop
.getObjectId());
231 RevCommit mergeBase
= findCommonBase(walk
, branchCommit
,
235 walk
.setRevFilter(RevFilter
.ALL
);
236 int aheadCount
= RevWalkUtils
.count(walk
, branchCommit
, mergeBase
);
242 private RevCommit
findCommonBase(RevWalk walk
, RevCommit branchCommit
,
243 RevCommit developCommit
) throws IOException
{
244 walk
.setRevFilter(RevFilter
.MERGE_BASE
);
245 walk
.markStart(branchCommit
);
246 walk
.markStart(developCommit
);
251 * Merge without squash and NO_FF for single commit branches:
252 * {@link org.eclipse.egit.gitflow.op.GitFlowOperation#mergeTo(IProgressMonitor, String, String, boolean, boolean)}
256 * @param targetBranchName
257 * @return result of merging back to targetBranchName
258 * @throws CoreException
260 protected MergeResult
mergeTo(IProgressMonitor monitor
, String branchName
,
261 String targetBranchName
) throws CoreException
{
262 return mergeTo(monitor
, branchName
, targetBranchName
, false, false);
266 * Fetch using the default remote configuration
271 * @return result of fetching from remote
272 * @throws URISyntaxException
273 * @throws InvocationTargetException
277 protected FetchResult
fetch(IProgressMonitor monitor
, int timeout
)
278 throws URISyntaxException
, InvocationTargetException
{
279 RemoteConfig config
= repository
.getConfig().getDefaultRemoteConfig();
280 FetchOperation fetchOperation
= new FetchOperation(
281 repository
.getRepository(), config
, timeout
, false);
282 fetchOperation
.run(monitor
);
283 return fetchOperation
.getOperationResult();
287 * @return The result of the merge this operation performs. May be null, if
288 * no merge was performed.
290 public MergeResult
getMergeResult() {