PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/jgit-flow-core/src/main/java/com/atlassian/jgitflow/core/command/FeatureFinishCommand.java

https://bitbucket.org/lukejackson/jgit-flow
Java | 256 lines | 160 code | 34 blank | 62 comment | 11 complexity | cca8cdb2ea98d7f0e37cd3e7283c0ca6 MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.atlassian.jgitflow.core.command;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.util.List;
  5. import com.atlassian.jgitflow.core.GitFlowConfiguration;
  6. import com.atlassian.jgitflow.core.JGitFlowConstants;
  7. import com.atlassian.jgitflow.core.JGitFlowReporter;
  8. import com.atlassian.jgitflow.core.exception.*;
  9. import com.atlassian.jgitflow.core.extension.FeatureFinishExtension;
  10. import com.atlassian.jgitflow.core.extension.impl.EmptyFeatureFinishExtension;
  11. import com.atlassian.jgitflow.core.extension.impl.MergeProcessExtensionWrapper;
  12. import com.atlassian.jgitflow.core.util.FileHelper;
  13. import com.atlassian.jgitflow.core.util.GitHelper;
  14. import com.atlassian.jgitflow.core.util.IterableHelper;
  15. import org.eclipse.jgit.api.Git;
  16. import org.eclipse.jgit.api.MergeCommand;
  17. import org.eclipse.jgit.api.MergeResult;
  18. import org.eclipse.jgit.api.errors.GitAPIException;
  19. import org.eclipse.jgit.lib.Ref;
  20. import org.eclipse.jgit.revwalk.RevCommit;
  21. import org.eclipse.jgit.util.FileUtils;
  22. import org.eclipse.jgit.util.StringUtils;
  23. import static com.atlassian.jgitflow.core.util.Preconditions.checkState;
  24. /**
  25. * Finish a feature.
  26. * <p>
  27. * This will merge the feature into develop and set the local branch to develop.
  28. * </p>
  29. * <p/>
  30. * Examples (<code>flow</code> is a {@link com.atlassian.jgitflow.core.JGitFlow} instance):
  31. * <p/>
  32. * Finish a feature:
  33. * <p/>
  34. * <pre>
  35. * flow.featureFinish(&quot;feature&quot;).call();
  36. * </pre>
  37. * <p/>
  38. * Don't delete the local feature branch
  39. * <p/>
  40. * <pre>
  41. * flow.featureFinish(&quot;feature&quot;).setKeepBranch(true).call();
  42. * </pre>
  43. * <p/>
  44. * Squash all commits on the feature branch into one before merging
  45. * <p/>
  46. * <pre>
  47. * flow.featureFinish(&quot;feature&quot;).setSquash(true).call();
  48. * </pre>
  49. */
  50. public class FeatureFinishCommand extends AbstractBranchMergingCommand<FeatureFinishCommand, MergeResult>
  51. {
  52. private static final String SHORT_NAME = "feature-finish";
  53. private boolean rebase;
  54. private boolean squash;
  55. private boolean noMerge;
  56. private FeatureFinishExtension extension;
  57. /**
  58. * Create a new feature finish command instance.
  59. * <p></p>
  60. * An instance of this class is usually obtained by calling {@link com.atlassian.jgitflow.core.JGitFlow#featureFinish(String)}
  61. *
  62. * @param name The name of the feature
  63. * @param git The git instance to use
  64. * @param gfConfig The GitFlowConfiguration to use
  65. * @param reporter
  66. */
  67. public FeatureFinishCommand(String branchName, Git git, GitFlowConfiguration gfConfig, JGitFlowReporter reporter)
  68. {
  69. super(branchName, git, gfConfig, reporter);
  70. checkState(!StringUtils.isEmptyOrNull(branchName));
  71. this.rebase = false;
  72. this.squash = false;
  73. this.noMerge = false;
  74. this.extension = new EmptyFeatureFinishExtension();
  75. }
  76. /**
  77. * @return nothing
  78. * @throws com.atlassian.jgitflow.core.exception.NotInitializedException
  79. * @throws com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException
  80. * @throws com.atlassian.jgitflow.core.exception.LocalBranchMissingException
  81. * @throws com.atlassian.jgitflow.core.exception.JGitFlowIOException
  82. * @throws com.atlassian.jgitflow.core.exception.DirtyWorkingTreeException
  83. * @throws com.atlassian.jgitflow.core.exception.MergeConflictsNotResolvedException
  84. * @throws com.atlassian.jgitflow.core.exception.BranchOutOfDateException
  85. */
  86. @Override
  87. public MergeResult call() throws NotInitializedException, JGitFlowGitAPIException, LocalBranchMissingException, JGitFlowIOException, DirtyWorkingTreeException, MergeConflictsNotResolvedException, BranchOutOfDateException, JGitFlowExtensionException, GitAPIException
  88. {
  89. MergeResult mergeResult = createEmptyMergeResult();
  90. String prefixedBranchName = runBeforeAndGetPrefixedBranchName(extension.before(), JGitFlowConstants.PREFIXES.FEATURE);
  91. enforcer().requireGitFlowInitialized();
  92. enforcer().requireLocalBranchExists(prefixedBranchName);
  93. //check to see if we're restoring from a merge conflict
  94. File flowDir = new File(git.getRepository().getDirectory(), JGitFlowConstants.GITFLOW_DIR);
  95. File mergeBase = new File(flowDir, JGitFlowConstants.MERGE_BASE);
  96. if (!noMerge && mergeBase.exists())
  97. {
  98. reporter.debugText(getCommandName(), "restoring from merge conflict. base: " + mergeBase.getAbsolutePath());
  99. if (GitHelper.workingTreeIsClean(git, isAllowUntracked(), reporter).isClean())
  100. {
  101. //check to see if the merge was done
  102. String finishBase = FileHelper.readFirstLine(mergeBase);
  103. if (GitHelper.isMergedInto(git, prefixedBranchName, finishBase))
  104. {
  105. mergeBase.delete();
  106. cleanupBranchesIfNeeded(gfConfig.getDevelop(), prefixedBranchName);
  107. reporter.endCommand();
  108. return null;
  109. }
  110. else
  111. {
  112. mergeBase.delete();
  113. }
  114. }
  115. else
  116. {
  117. reporter.errorText(getCommandName(), "Merge conflicts are not resolved");
  118. reporter.endCommand();
  119. throw new MergeConflictsNotResolvedException("Merge conflicts are not resolved");
  120. }
  121. }
  122. //not restoring a merge, continue
  123. enforcer().requireCleanWorkingTree(isAllowUntracked());
  124. try
  125. {
  126. doFetchIfNeeded(extension);
  127. ensureLocalBranchesNotBehindRemotes(prefixedBranchName, prefixedBranchName, gfConfig.getDevelop());
  128. //checkout the branch to merge just so we can run any extensions that need to be on this branch
  129. checkoutTopicBranch(prefixedBranchName,extension);
  130. if (rebase)
  131. {
  132. runExtensionCommands(extension.beforeRebase());
  133. FeatureRebaseCommand rebaseCommand = new FeatureRebaseCommand(getBranchName(), git, gfConfig, reporter);
  134. rebaseCommand.setAllowUntracked(isAllowUntracked()).call();
  135. runExtensionCommands(extension.afterRebase());
  136. }
  137. if (!noMerge)
  138. {
  139. RevCommit developCommit = GitHelper.getLatestCommit(git, gfConfig.getDevelop());
  140. RevCommit featureCommit = GitHelper.getLatestCommit(git, prefixedBranchName);
  141. List<RevCommit> commitList = IterableHelper.asList(git.log().setMaxCount(2).addRange(developCommit, featureCommit).call());
  142. MergeProcessExtensionWrapper developExtension = new MergeProcessExtensionWrapper(extension.beforeDevelopCheckout(), extension.afterDevelopCheckout(), extension.beforeDevelopMerge(), extension.afterDevelopMerge());
  143. if (commitList.size() < 2)
  144. {
  145. mergeResult = doMerge(prefixedBranchName, gfConfig.getDevelop(), developExtension, false, MergeCommand.FastForwardMode.FF);
  146. }
  147. else
  148. {
  149. mergeResult = doMerge(prefixedBranchName, gfConfig.getDevelop(), developExtension, squash);
  150. }
  151. if (null == mergeResult || mergeResult.getMergeStatus().equals(MergeResult.MergeStatus.FAILED) || mergeResult.getMergeStatus().equals(MergeResult.MergeStatus.CONFLICTING))
  152. {
  153. FileHelper.createParentDirs(mergeBase);
  154. FileUtils.createNewFile(mergeBase);
  155. FileHelper.writeStringToFile(gfConfig.getDevelop(), mergeBase);
  156. reporter.endCommand();
  157. reporter.flush();
  158. throw new MergeConflictsNotResolvedException("merge conflicts exist, please resolve!");
  159. }
  160. }
  161. doPushIfNeeded(extension, false, gfConfig.getDevelop(), prefixedBranchName);
  162. cleanupBranchesIfNeeded(gfConfig.getDevelop(), prefixedBranchName);
  163. reporter.infoText(getCommandName(), "checking out '" + gfConfig.getDevelop() + "'");
  164. git.checkout().setName(gfConfig.getDevelop()).call();
  165. reporter.endCommand();
  166. runExtensionCommands(extension.after());
  167. return mergeResult;
  168. }
  169. catch (GitAPIException e)
  170. {
  171. reporter.endCommand();
  172. throw new JGitFlowGitAPIException(e);
  173. }
  174. catch (IOException e)
  175. {
  176. reporter.endCommand();
  177. throw new JGitFlowIOException(e);
  178. }
  179. finally
  180. {
  181. reporter.endCommand();
  182. reporter.flush();
  183. }
  184. }
  185. /**
  186. * Set whether to perform a git rebase on the feature before doing the merge
  187. *
  188. * @param rebase <code>true</code> to do a rebase, <code>false</code>(default) otherwise
  189. * @return {@code this}
  190. */
  191. public FeatureFinishCommand setRebase(boolean rebase)
  192. {
  193. this.rebase = rebase;
  194. return this;
  195. }
  196. /**
  197. * Set whether to squash all commits into a single commit before the merge
  198. *
  199. * @param squash <code>true</code> to squash, <code>false</code>(default) otherwise
  200. * @return {@code this}
  201. */
  202. public FeatureFinishCommand setSquash(boolean squash)
  203. {
  204. this.squash = squash;
  205. return this;
  206. }
  207. public FeatureFinishCommand setNoMerge(boolean noMerge)
  208. {
  209. this.noMerge = noMerge;
  210. return this;
  211. }
  212. public FeatureFinishCommand setExtension(FeatureFinishExtension extension)
  213. {
  214. this.extension = extension;
  215. return this;
  216. }
  217. @Override
  218. protected String getCommandName()
  219. {
  220. return SHORT_NAME;
  221. }
  222. }