Multi branch checkout operations are performed as a single eclipse job
[egit/eclipse.git] / org.eclipse.egit.gitflow / src / org / eclipse / egit / gitflow / op / GitFlowOperation.java
blobb50fe8a0f2a8136839cee7f946e0c97ef3b67c7c
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;
48 /**
49 * Common logic for Git Flow operations.
51 // TODO: This class should be called AbstractGitFlowOperation for consistency
52 abstract public class GitFlowOperation implements IEGitOperation {
53 /**
54 * git path separator
56 public static final String SEP = "/"; //$NON-NLS-1$
58 /**
59 * repository that is operated on.
61 protected GitFlowRepository repository;
63 /**
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;
69 /**
70 * @param repository
72 public GitFlowOperation(GitFlowRepository repository) {
73 this.repository = repository;
76 @Override
77 public ISchedulingRule getSchedulingRule() {
78 return RuleUtil.getRule(repository.getRepository());
81 /**
82 * @param branchName
83 * @param sourceCommit
84 * @return operation constructed from parameters
86 protected CreateLocalBranchOperation createBranchFromHead(
87 String branchName, RevCommit sourceCommit) {
88 return new CreateLocalBranchOperation(repository.getRepository(),
89 branchName, sourceCommit);
92 /**
93 * git flow * start
95 * @param monitor
96 * @param branchName
97 * @param 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));
112 * git flow * finish
114 * @param monitor
115 * @param branchName
116 * @param squash
117 * @param fastForwardSingleCommit Has no effect if {@code squash} is true.
118 * @param keepBranch
119 * @throws CoreException
120 * @since 4.1
122 protected void finish(IProgressMonitor monitor, String branchName,
123 boolean squash, boolean keepBranch, boolean fastForwardSingleCommit)
124 throws CoreException {
125 try {
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()) {
131 return;
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;
141 if (!keepBranch) {
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)}
154 * @param monitor
155 * @param branchName
156 * @throws CoreException
158 protected void finish(IProgressMonitor monitor, String branchName)
159 throws CoreException {
160 finish(monitor, branchName, false, false, false);
164 * @param monitor
165 * @param branchName
166 * @param targetBranchName
167 * @param squash
168 * @param fastForwardSingleCommit Has no effect if {@code squash} is true.
169 * @return result of merging back to targetBranchName
170 * @throws CoreException
171 * @since 4.1
173 protected @NonNull MergeResult mergeTo(IProgressMonitor monitor, String branchName,
174 String targetBranchName, boolean squash, boolean fastForwardSingleCommit) throws CoreException {
175 try {
176 if (!repository.hasBranch(targetBranchName)) {
177 throw new RuntimeException(NLS.bind(
178 CoreText.GitFlowOperation_branchNotFound,
179 targetBranchName));
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,
194 branchName);
195 mergeOperation.setSquash(squash);
196 if (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,
208 targetBranchName)));
211 return result;
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,
232 developCommit);
234 walk.reset();
235 walk.setRevFilter(RevFilter.ALL);
236 int aheadCount = RevWalkUtils.count(walk, branchCommit, mergeBase);
238 return aheadCount;
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);
247 return walk.next();
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)}
254 * @param monitor
255 * @param branchName
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
268 * @param monitor
269 * @param timeout
270 * timeout in seconds
271 * @return result of fetching from remote
272 * @throws URISyntaxException
273 * @throws InvocationTargetException
275 * @since 4.2
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() {
291 return mergeResult;