import org.slf4j.Logger;\r
import org.slf4j.LoggerFactory;\r
\r
+import com.gitblit.Constants.MergeType;\r
import com.gitblit.GitBlitException;\r
import com.gitblit.models.GitNote;\r
import com.gitblit.models.PathModel;\r
public static enum MergeStatus {
NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED;
}
-
+\r
+\r
/**
* Determines if we can cleanly merge one branch into another. Returns true
* if we can merge without conflict, otherwise returns false.
*
* @param repository
* @param src
- * @param toBranch
+ * @param toBranch\r
+ * @param mergeType\r
+ * Defines the integration strategy to use for merging.
* @return true if we can merge without conflict
*/
- public static MergeStatus canMerge(Repository repository, String src, String toBranch) {
- RevWalk revWalk = null;
- try {
- revWalk = new RevWalk(repository);
- RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
- RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
- if (revWalk.isMergedInto(srcTip, branchTip)) {
- // already merged
- return MergeStatus.ALREADY_MERGED;
- } else if (revWalk.isMergedInto(branchTip, srcTip)) {
- // fast-forward
- return MergeStatus.MERGEABLE;
- }
- RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
- boolean canMerge = merger.merge(branchTip, srcTip);
- if (canMerge) {
- return MergeStatus.MERGEABLE;
- }
- } catch (IOException e) {
- LOGGER.error("Failed to determine canMerge", e);
- } finally {\r
- if (revWalk != null) {
- revWalk.release();\r
- }
- }
- return MergeStatus.NOT_MERGEABLE;
+ public static MergeStatus canMerge(Repository repository, String src, String toBranch, MergeType mergeType) {\r
+ IntegrationStrategy strategy = IntegrationStrategyFactory.create(mergeType, repository, src, toBranch);\r
+ return strategy.canMerge();
}
*
* @param repository
* @param src
- * @param toBranch
+ * @param toBranch\r
+ * @param mergeType
+ * Defines the integration strategy to use for merging.\r
* @param committer
* @param message
* @return the merge result
*/
- public static MergeResult merge(Repository repository, String src, String toBranch,
+ public static MergeResult merge(Repository repository, String src, String toBranch, MergeType mergeType,
PersonIdent committer, String message) {
if (!toBranch.startsWith(Constants.R_REFS)) {
// branch ref doesn't start with ref, assume this is a branch head
toBranch = Constants.R_HEADS + toBranch;
}
-
- RevWalk revWalk = null;
- try {
- revWalk = new RevWalk(repository);
- RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
- RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
- if (revWalk.isMergedInto(srcTip, branchTip)) {
- // already merged
- return new MergeResult(MergeStatus.ALREADY_MERGED, null);
- }
- RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
- boolean merged = merger.merge(branchTip, srcTip);
- if (merged) {
- // create a merge commit and a reference to track the merge commit
- ObjectId treeId = merger.getResultTreeId();
- ObjectInserter odi = repository.newObjectInserter();
- try {
- // Create a commit object
- CommitBuilder commitBuilder = new CommitBuilder();
- commitBuilder.setCommitter(committer);
- commitBuilder.setAuthor(committer);
- commitBuilder.setEncoding(Constants.CHARSET);
- if (StringUtils.isEmpty(message)) {
- message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());
- }
- commitBuilder.setMessage(message);
- commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());
- commitBuilder.setTreeId(treeId);
-
- // Insert the merge commit into the repository
- ObjectId mergeCommitId = odi.insert(commitBuilder);
- odi.flush();
-
- // set the merge ref to the merge commit
- RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId);
- RefUpdate mergeRefUpdate = repository.updateRef(toBranch);
- mergeRefUpdate.setNewObjectId(mergeCommitId);
- mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false);
- RefUpdate.Result rc = mergeRefUpdate.update();
- switch (rc) {
- case FAST_FORWARD:
- // successful, clean merge
- break;\r
- default:
- throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}",
- rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory()));
- }
-
- // return the merge commit id
- return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());
- } finally {
- odi.release();
- }
- }
- } catch (IOException e) {
- LOGGER.error("Failed to merge", e);
- } finally {\r
- if (revWalk != null) {
- revWalk.release();\r
- }
- }
- return new MergeResult(MergeStatus.FAILED, null);
+\r
+ IntegrationStrategy strategy = IntegrationStrategyFactory.create(mergeType, repository, src, toBranch);\r
+ MergeResult mergeResult = strategy.merge(committer, message);\r
+\r
+ if (mergeResult.status != MergeStatus.MERGED) {\r
+ return mergeResult;\r
+ }\r
+\r
+ try {\r
+ // Update the integration branch ref\r
+ RefUpdate mergeRefUpdate = repository.updateRef(toBranch);\r
+ mergeRefUpdate.setNewObjectId(strategy.getMergeCommit());\r
+ mergeRefUpdate.setRefLogMessage(strategy.getRefLogMessage(), false);\r
+ mergeRefUpdate.setExpectedOldObjectId(strategy.branchTip);\r
+ RefUpdate.Result rc = mergeRefUpdate.update();\r
+ switch (rc) {\r
+ case FAST_FORWARD:\r
+ // successful, clean merge\r
+ break;\r
+ default:\r
+ mergeResult = new MergeResult(MergeStatus.FAILED, null);\r
+ throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when {1} in {2}",\r
+ rc.name(), strategy.getOperationMessage(), repository.getDirectory()));\r
+ }\r
+ } catch (IOException e) {\r
+ LOGGER.error("Failed to merge", e);\r
+ }\r
+\r
+ return mergeResult;
+ }\r
+\r
+\r
+ private static abstract class IntegrationStrategy {\r
+ Repository repository;\r
+ String src;\r
+ String toBranch;\r
+\r
+ RevWalk revWalk;\r
+ RevCommit branchTip;\r
+ RevCommit srcTip;\r
+\r
+ RevCommit mergeCommit;\r
+ String refLogMessage;\r
+ String operationMessage;\r
+\r
+ RevCommit getMergeCommit() {\r
+ return mergeCommit;\r
+ }\r
+\r
+ String getRefLogMessage() {\r
+ return refLogMessage;\r
+ }\r
+\r
+ String getOperationMessage() {\r
+ return operationMessage;\r
+ }\r
+\r
+ IntegrationStrategy(Repository repository, String src, String toBranch) {\r
+ this.repository = repository;\r
+ this.src = src;\r
+ this.toBranch = toBranch;\r
+ }\r
+\r
+ void prepare() throws IOException {\r
+ if (revWalk == null) revWalk = new RevWalk(repository);\r
+ branchTip = revWalk.lookupCommit(repository.resolve(toBranch));\r
+ srcTip = revWalk.lookupCommit(repository.resolve(src));\r
+ }\r
+\r
+\r
+ abstract MergeStatus _canMerge() throws IOException;\r
+\r
+\r
+ MergeStatus canMerge() {\r
+ try {\r
+ prepare();\r
+ if (revWalk.isMergedInto(srcTip, branchTip)) {\r
+ // already merged\r
+ return MergeStatus.ALREADY_MERGED;\r
+ }\r
+ // determined by specific integration strategy\r
+ return _canMerge();\r
+\r
+ } catch (IOException e) {\r
+ LOGGER.error("Failed to determine canMerge", e);\r
+ } finally {\r
+ if (revWalk != null) {\r
+ revWalk.release();\r
+ }\r
+ }\r
+\r
+ return MergeStatus.NOT_MERGEABLE;\r
+ }\r
+\r
+\r
+ abstract MergeResult _merge(PersonIdent committer, String message) throws IOException;\r
+\r
+\r
+ MergeResult merge(PersonIdent committer, String message) {\r
+ try {\r
+ prepare();\r
+ if (revWalk.isMergedInto(srcTip, branchTip)) {\r
+ // already merged\r
+ return new MergeResult(MergeStatus.ALREADY_MERGED, null);\r
+ }\r
+ // determined by specific integration strategy\r
+ return _merge(committer, message);\r
+\r
+ } catch (IOException e) {\r
+ LOGGER.error("Failed to merge", e);\r
+ } finally {\r
+ if (revWalk != null) {\r
+ revWalk.release();\r
+ }\r
+ }\r
+\r
+ return new MergeResult(MergeStatus.FAILED, null);\r
+ }\r
+ }\r
+\r
+\r
+ private static class FastForwardOnly extends IntegrationStrategy {\r
+ FastForwardOnly(Repository repository, String src, String toBranch) {\r
+ super(repository, src, toBranch);\r
+ }\r
+\r
+ @Override\r
+ MergeStatus _canMerge() throws IOException {\r
+ if (revWalk.isMergedInto(branchTip, srcTip)) {\r
+ // fast-forward\r
+ return MergeStatus.MERGEABLE;\r
+ }\r
+\r
+ return MergeStatus.NOT_MERGEABLE;\r
+ }\r
+\r
+ @Override\r
+ MergeResult _merge(PersonIdent committer, String message) throws IOException {\r
+ if (! revWalk.isMergedInto(branchTip, srcTip)) {\r
+ // is not fast-forward\r
+ return new MergeResult(MergeStatus.FAILED, null);\r
+ }\r
+\r
+ mergeCommit = srcTip;\r
+ refLogMessage = "merge " + src + ": Fast-forward";\r
+ MessageFormat.format("fast-forwarding {0} to commit {1}", srcTip.getName(), branchTip.getName());\r
+\r
+ return new MergeResult(MergeStatus.MERGED, srcTip.getName());\r
+ }\r
}
+\r
+ private static class MergeIfNecessary extends IntegrationStrategy {\r
+ MergeIfNecessary(Repository repository, String src, String toBranch) {\r
+ super(repository, src, toBranch);\r
+ }\r
+\r
+ @Override\r
+ MergeStatus _canMerge() throws IOException {\r
+ if (revWalk.isMergedInto(branchTip, srcTip)) {\r
+ // fast-forward\r
+ return MergeStatus.MERGEABLE;\r
+ }\r
+\r
+ RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);\r
+ boolean canMerge = merger.merge(branchTip, srcTip);\r
+ if (canMerge) {\r
+ return MergeStatus.MERGEABLE;\r
+ }\r
+\r
+ return MergeStatus.NOT_MERGEABLE;\r
+ }\r
+\r
+ @Override\r
+ MergeResult _merge(PersonIdent committer, String message) throws IOException {\r
+ if (revWalk.isMergedInto(branchTip, srcTip)) {\r
+ // fast-forward\r
+ mergeCommit = srcTip;\r
+ refLogMessage = "merge " + src + ": Fast-forward";\r
+ MessageFormat.format("fast-forwarding {0} to commit {1}", branchTip.getName(), srcTip.getName());\r
+\r
+ return new MergeResult(MergeStatus.MERGED, srcTip.getName());\r
+ }\r
+\r
+ RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);\r
+ boolean merged = merger.merge(branchTip, srcTip);\r
+ if (merged) {\r
+ // create a merge commit and a reference to track the merge commit\r
+ ObjectId treeId = merger.getResultTreeId();\r
+ ObjectInserter odi = repository.newObjectInserter();\r
+ try {\r
+ // Create a commit object\r
+ CommitBuilder commitBuilder = new CommitBuilder();\r
+ commitBuilder.setCommitter(committer);\r
+ commitBuilder.setAuthor(committer);\r
+ commitBuilder.setEncoding(Constants.CHARSET);\r
+ if (StringUtils.isEmpty(message)) {\r
+ message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());\r
+ }\r
+ commitBuilder.setMessage(message);\r
+ commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());\r
+ commitBuilder.setTreeId(treeId);\r
+\r
+ // Insert the merge commit into the repository\r
+ ObjectId mergeCommitId = odi.insert(commitBuilder);\r
+ odi.flush();\r
+\r
+ mergeCommit = revWalk.parseCommit(mergeCommitId);\r
+ refLogMessage = "commit: " + mergeCommit.getShortMessage();\r
+ MessageFormat.format("merging commit {0} into {1}", srcTip.getName(), branchTip.getName());\r
+\r
+ // return the merge commit id\r
+ return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());\r
+ } finally {\r
+ odi.release();\r
+ }\r
+ }\r
+ return new MergeResult(MergeStatus.FAILED, null);\r
+ }\r
+ }\r
+\r
+ private static class MergeAlways extends IntegrationStrategy {\r
+ MergeAlways(Repository repository, String src, String toBranch) {\r
+ super(repository, src, toBranch);\r
+ }\r
+\r
+ @Override\r
+ MergeStatus _canMerge() throws IOException {\r
+ RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);\r
+ boolean canMerge = merger.merge(branchTip, srcTip);\r
+ if (canMerge) {\r
+ return MergeStatus.MERGEABLE;\r
+ }\r
+\r
+ return MergeStatus.NOT_MERGEABLE;\r
+ }\r
+\r
+ @Override\r
+ MergeResult _merge(PersonIdent committer, String message) throws IOException {\r
+ RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);\r
+ boolean merged = merger.merge(branchTip, srcTip);\r
+ if (merged) {\r
+ // create a merge commit and a reference to track the merge commit\r
+ ObjectId treeId = merger.getResultTreeId();\r
+ ObjectInserter odi = repository.newObjectInserter();\r
+ try {\r
+ // Create a commit object\r
+ CommitBuilder commitBuilder = new CommitBuilder();\r
+ commitBuilder.setCommitter(committer);\r
+ commitBuilder.setAuthor(committer);\r
+ commitBuilder.setEncoding(Constants.CHARSET);\r
+ if (StringUtils.isEmpty(message)) {\r
+ message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());\r
+ }\r
+ commitBuilder.setMessage(message);\r
+ commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());\r
+ commitBuilder.setTreeId(treeId);\r
+\r
+ // Insert the merge commit into the repository\r
+ ObjectId mergeCommitId = odi.insert(commitBuilder);\r
+ odi.flush();\r
+\r
+ mergeCommit = revWalk.parseCommit(mergeCommitId);\r
+ refLogMessage = "commit: " + mergeCommit.getShortMessage();\r
+ MessageFormat.format("merging commit {0} into {1}", srcTip.getName(), branchTip.getName());\r
+\r
+ // return the merge commit id\r
+ return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());\r
+ } finally {\r
+ odi.release();\r
+ }\r
+ }\r
+\r
+ return new MergeResult(MergeStatus.FAILED, null);\r
+ }\r
+ }\r
+\r
+ private static class IntegrationStrategyFactory {\r
+ static IntegrationStrategy create(MergeType mergeType, Repository repository, String src, String toBranch) {\r
+ switch(mergeType) {\r
+ case FAST_FORWARD_ONLY:\r
+ return new FastForwardOnly(repository, src, toBranch);\r
+ case MERGE_IF_NECESSARY:\r
+ return new MergeIfNecessary(repository, src, toBranch);\r
+ case MERGE_ALWAYS:\r
+ return new MergeAlways(repository, src, toBranch);\r
+ }\r
+ return null;\r
+ }\r
+ }\r
}\r