PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/nmi-uk/jgit-flow
Java | 329 lines | 173 code | 38 blank | 118 comment | 17 complexity | f9288692819937d126cc9f53c281691c MD5 | raw file
  1. package com.atlassian.jgitflow.core;
  2. import com.atlassian.jgitflow.core.exception.*;
  3. import com.atlassian.jgitflow.core.util.GitHelper;
  4. import org.eclipse.jgit.api.Git;
  5. import org.eclipse.jgit.api.MergeCommand;
  6. import org.eclipse.jgit.api.MergeResult;
  7. import org.eclipse.jgit.api.errors.GitAPIException;
  8. import org.eclipse.jgit.lib.Constants;
  9. import org.eclipse.jgit.lib.ObjectId;
  10. import org.eclipse.jgit.lib.Ref;
  11. import org.eclipse.jgit.merge.MergeStrategy;
  12. import org.eclipse.jgit.revwalk.RevCommit;
  13. import org.eclipse.jgit.transport.RefSpec;
  14. import org.eclipse.jgit.util.StringUtils;
  15. import static com.atlassian.jgitflow.core.util.Preconditions.checkState;
  16. /**
  17. * Finish a release.
  18. * <p>
  19. * This will merge the release into both master and develop and create a tag for the release
  20. * </p>
  21. * <p>
  22. * Examples (<code>flow</code> is a {@link JGitFlow} instance):
  23. * <p>
  24. * Finish a release:
  25. *
  26. * <pre>
  27. * flow.releaseFinish(&quot;1.0&quot;).call();
  28. * </pre>
  29. * <p>
  30. * Don't delete the local release branch
  31. *
  32. * <pre>
  33. * flow.releaseFinish(&quot;1.0&quot;).setKeepBranch(true).call();
  34. * </pre>
  35. * <p>
  36. * Squash all commits on the release branch into one before merging
  37. *
  38. * <pre>
  39. * flow.releaseFinish(&quot;1.0&quot;).setSquash(true).call();
  40. * </pre>
  41. * <p>
  42. * Push changes to the remote origin
  43. *
  44. * <pre>
  45. * flow.releaseFinish(&quot;1.0&quot;).setPush(true).call();
  46. * </pre>
  47. * <p>
  48. * Don't create a tag for the release
  49. *
  50. * <pre>
  51. * flow.releaseFinish(&quot;1.0&quot;).setNoTag(true).call();
  52. * </pre>
  53. */
  54. public class ReleaseFinishCommand extends AbstractGitFlowCommand<ReleaseMergeResult>
  55. {
  56. private final String releaseName;
  57. private boolean fetch;
  58. private String message;
  59. private boolean push;
  60. private boolean keepBranch;
  61. private boolean noTag;
  62. private boolean squash;
  63. private ExternalAction preDevelopMergeAction;
  64. /**
  65. * Create a new release finish command instance.
  66. * <p></p>
  67. * An instance of this class is usually obtained by calling {@link JGitFlow#releaseFinish(String)}
  68. * @param releaseName The name/version of the release
  69. * @param git The git instance to use
  70. * @param gfConfig The GitFlowConfiguration to use
  71. */
  72. public ReleaseFinishCommand(String releaseName, Git git, GitFlowConfiguration gfConfig)
  73. {
  74. super(git, gfConfig);
  75. checkState(!StringUtils.isEmptyOrNull(releaseName));
  76. this.releaseName = releaseName;
  77. this.fetch = false;
  78. this.message = "tagging release " + releaseName;
  79. this.push = false;
  80. this.keepBranch = false;
  81. this.noTag = false;
  82. this.squash = false;
  83. this.preDevelopMergeAction = null;
  84. }
  85. /**
  86. *
  87. * @return nothing
  88. * @throws JGitFlowGitAPIException
  89. * @throws LocalBranchMissingException
  90. * @throws DirtyWorkingTreeException
  91. * @throws JGitFlowIOException
  92. * @throws BranchOutOfDateException
  93. */
  94. @Override
  95. public ReleaseMergeResult call() throws JGitFlowGitAPIException, LocalBranchMissingException, DirtyWorkingTreeException, JGitFlowIOException, BranchOutOfDateException, ExternalActionFailedException
  96. {
  97. String prefixedReleaseName = gfConfig.getPrefixValue(JGitFlowConstants.PREFIXES.RELEASE.configKey()) + releaseName;
  98. requireLocalBranchExists(prefixedReleaseName);
  99. requireCleanWorkingTree();
  100. MergeResult masterResult = new MergeResult(null,null,new ObjectId[] { null, null }, MergeResult.MergeStatus.ALREADY_UP_TO_DATE,MergeStrategy.RESOLVE,null);
  101. try
  102. {
  103. if (fetch)
  104. {
  105. RefSpec developSpec = new RefSpec("+" + Constants.R_HEADS + gfConfig.getDevelop() + ":" + Constants.R_REMOTES + "origin/" + gfConfig.getDevelop());
  106. RefSpec masterSpec = new RefSpec("+" + Constants.R_HEADS + gfConfig.getMaster() + ":" + Constants.R_REMOTES + "origin/" + gfConfig.getMaster());
  107. git.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(masterSpec).call();
  108. git.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(developSpec).call();
  109. git.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).call();
  110. }
  111. if (GitHelper.remoteBranchExists(git, gfConfig.getMaster()))
  112. {
  113. requireLocalBranchNotBehindRemote(gfConfig.getMaster());
  114. }
  115. if (GitHelper.remoteBranchExists(git, gfConfig.getDevelop()))
  116. {
  117. requireLocalBranchNotBehindRemote(gfConfig.getDevelop());
  118. }
  119. Ref releaseBranch = GitHelper.getLocalBranch(git, prefixedReleaseName);
  120. RevCommit releaseCommit = GitHelper.getLatestCommit(git, prefixedReleaseName);
  121. /*
  122. try to merge into master
  123. in case a previous attempt to finish this release branch has failed,
  124. but the merge into master was successful, we skip it now
  125. */
  126. if (!GitHelper.isMergedInto(git, prefixedReleaseName, gfConfig.getMaster()))
  127. {
  128. git.checkout().setName(gfConfig.getMaster()).call();
  129. if (squash)
  130. {
  131. masterResult = git.merge().setSquash(true).include(releaseBranch).call();
  132. git.commit().call();
  133. }
  134. else
  135. {
  136. masterResult = git.merge().setFastForward(MergeCommand.FastForwardMode.NO_FF).include(releaseBranch).call();
  137. }
  138. }
  139. if (!noTag)
  140. {
  141. /*
  142. try to tag the release
  143. in case a previous attempt to finish this release branch has failed,
  144. but the tag was successful, we skip it now
  145. */
  146. String tagName = gfConfig.getPrefixValue(JGitFlowConstants.PREFIXES.VERSIONTAG.configKey()) + releaseName;
  147. if (!GitHelper.tagExists(git, tagName))
  148. {
  149. git.tag().setName(tagName).setMessage(message).setObjectId(releaseCommit).call();
  150. }
  151. }
  152. /**
  153. * if we have specified an action to carry out before the merge to develop, do it now
  154. */
  155. if (preDevelopMergeAction != null) {
  156. preDevelopMergeAction.call();
  157. }
  158. /**
  159. * reload the release branch in case it has changed
  160. */
  161. releaseBranch = GitHelper.getLocalBranch(git, prefixedReleaseName);
  162. /*
  163. try to merge into develop
  164. in case a previous attempt to finish this release branch has failed,
  165. but the merge into develop was successful, we skip it now
  166. */
  167. MergeResult developResult = new MergeResult(null,null,new ObjectId[] { null, null }, MergeResult.MergeStatus.ALREADY_UP_TO_DATE, MergeStrategy.RESOLVE,null);
  168. if (!GitHelper.isMergedInto(git, prefixedReleaseName, gfConfig.getDevelop()))
  169. {
  170. git.checkout().setName(gfConfig.getDevelop()).call();
  171. if (squash)
  172. {
  173. developResult = git.merge().setSquash(true).include(releaseBranch).call();
  174. git.commit().call();
  175. }
  176. else
  177. {
  178. developResult = git.merge().setFastForward(MergeCommand.FastForwardMode.NO_FF).include(releaseBranch).call();
  179. }
  180. }
  181. if (push)
  182. {
  183. //push to develop
  184. RefSpec developSpec = new RefSpec(gfConfig.getDevelop());
  185. git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(developSpec).call();
  186. //push to master
  187. RefSpec masterSpec = new RefSpec(gfConfig.getMaster());
  188. git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(masterSpec).call();
  189. if (!noTag)
  190. {
  191. git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setPushTags().call();
  192. }
  193. if (GitHelper.remoteBranchExists(git, prefixedReleaseName))
  194. {
  195. RefSpec branchSpec = new RefSpec(prefixedReleaseName);
  196. git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(branchSpec).call();
  197. }
  198. }
  199. if (!keepBranch)
  200. {
  201. git.checkout().setName(gfConfig.getDevelop()).call();
  202. git.branchDelete().setForce(true).setBranchNames(prefixedReleaseName).call();
  203. if (push && GitHelper.remoteBranchExists(git, prefixedReleaseName))
  204. {
  205. RefSpec deleteSpec = new RefSpec(":" + Constants.R_HEADS + prefixedReleaseName);
  206. git.push().setRemote(Constants.DEFAULT_REMOTE_NAME).setRefSpecs(deleteSpec).call();
  207. }
  208. }
  209. git.checkout().setName(gfConfig.getDevelop()).call();
  210. return new ReleaseMergeResult(masterResult,developResult);
  211. }
  212. catch (GitAPIException e)
  213. {
  214. throw new JGitFlowGitAPIException(e);
  215. }
  216. }
  217. /**
  218. * Set an action to perform between the merge to master and the merge to develop
  219. * @param action the action to perform.
  220. * @return {@code this}
  221. */
  222. public ReleaseFinishCommand setPreDevelopMergeAction(ExternalAction action) {
  223. this.preDevelopMergeAction = action;
  224. return this;
  225. }
  226. /**
  227. * Set whether to perform a git fetch of the remote branches before doing the merge
  228. * @param fetch
  229. * <code>true</code> to do the fetch, <code>false</code>(default) otherwise
  230. * @return {@code this}
  231. */
  232. public ReleaseFinishCommand setFetch(boolean fetch)
  233. {
  234. this.fetch = fetch;
  235. return this;
  236. }
  237. /**
  238. * Set the commit message for the tag creation
  239. * @param message
  240. * @return {@code this}
  241. */
  242. public ReleaseFinishCommand setMessage(String message)
  243. {
  244. this.message = message;
  245. return this;
  246. }
  247. /**
  248. * Set whether to push the changes to the remote repository
  249. * @param push
  250. * <code>true</code> to do the push, <code>false</code>(default) otherwise
  251. * @return {@code this}
  252. */
  253. public ReleaseFinishCommand setPush(boolean push)
  254. {
  255. this.push = push;
  256. return this;
  257. }
  258. /**
  259. * Set whether to keep the local release branch after the merge
  260. * @param keepBranch
  261. * <code>true</code> to keep the branch, <code>false</code>(default) otherwise
  262. * @return {@code this}
  263. */
  264. public ReleaseFinishCommand setKeepBranch(boolean keepBranch)
  265. {
  266. this.keepBranch = keepBranch;
  267. return this;
  268. }
  269. /**
  270. * Set whether to turn off tagging
  271. * @param noTag
  272. * <code>true</code> to turn off tagging, <code>false</code>(default) otherwise
  273. * @return {@code this}
  274. */
  275. public ReleaseFinishCommand setNoTag(boolean noTag)
  276. {
  277. this.noTag = noTag;
  278. return this;
  279. }
  280. /**
  281. * Set whether to squash all commits into a single commit before the merge
  282. * @param squash
  283. * <code>true</code> to squash, <code>false</code>(default) otherwise
  284. * @return {@code this}
  285. */
  286. public ReleaseFinishCommand setSquash(boolean squash)
  287. {
  288. this.squash = squash;
  289. return this;
  290. }
  291. }