PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

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

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