diff options
author | Jukka Jokiniva <jukka.jokiniva@qt.io> | 2019-04-09 22:22:52 +0300 |
---|---|---|
committer | Jukka Jokiniva <jukka.jokiniva@qt.io> | 2019-04-10 13:52:26 +0000 |
commit | d6c284088e8bd946cd5c62f5c5ac73fdb2b893d0 (patch) | |
tree | 2ed5cfee41f4d9fc92140c6f7d71f37f48e4f42f | |
parent | 66f4f8365032567692cbb2e72016c9f0dda2d009 (diff) |
Handle merge conflict when rebuilding the staging ref
Fixes: QTBI-1641
Change-Id: I3f8abd56dd4991a28acd97e6360232a935ab48c7
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
3 files changed, 172 insertions, 50 deletions
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java index b2da710..b94190b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java +++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtUtil.java @@ -18,17 +18,22 @@ package com.googlesource.gerrit.plugins.qtcodereview; import com.google.gerrit.common.FooterConstants; import com.google.common.collect.Lists; +import com.google.gerrit.extensions.restapi.RestApiException; import com.google.common.flogger.FluentLogger; import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.project.NoSuchRefException; import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.submit.IntegrationException; +import com.google.gerrit.server.update.BatchUpdate; +import com.google.gerrit.server.update.UpdateException; +import com.google.gerrit.server.util.time.TimeUtil; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; @@ -72,17 +77,26 @@ public class QtUtil { public static final String R_STAGING = "refs/staging/"; public static final String R_BUILDS = "refs/builds/"; + private final Provider<ReviewDb> dbProvider; private final Provider<InternalChangeQuery> queryProvider; private final GitReferenceUpdated referenceUpdated; + private final BatchUpdate.Factory updateFactory; private final QtCherryPickPatch qtCherryPickPatch; + private final QtChangeUpdateOp.Factory qtUpdateFactory; @Inject - QtUtil(Provider<InternalChangeQuery> queryProvider, + QtUtil(Provider<ReviewDb> dbProvider, + Provider<InternalChangeQuery> queryProvider, GitReferenceUpdated referenceUpdated, - QtCherryPickPatch qtCherryPickPatch) { + BatchUpdate.Factory updateFactory, + QtCherryPickPatch qtCherryPickPatch, + QtChangeUpdateOp.Factory qtUpdateFactory) { + this.dbProvider = dbProvider; this.queryProvider = queryProvider; this.referenceUpdated = referenceUpdated; + this.updateFactory = updateFactory; this.qtCherryPickPatch = qtCherryPickPatch; + this.qtUpdateFactory = qtUpdateFactory; } public static class MergeConflictException extends Exception { @@ -307,7 +321,7 @@ public class QtUtil { return results; } - private ObjectId pickChangestoStagingRef(Repository git, + private ObjectId pickChangesToStagingRef(Repository git, final Project.NameKey projectKey, List<ChangeData> changes, ObjectId tipObj) @@ -338,53 +352,101 @@ public class QtUtil { final Branch.NameKey stagingBranchKey, final Branch.NameKey destBranchShortKey) throws MergeConflictException { - try { - ObjectId oldStageRefObjId = git.resolve(stagingBranchKey.get()); - ObjectId branchObjId = git.resolve(destBranchShortKey.get()); - - InternalChangeQuery query = queryProvider.get(); - List<ChangeData> changes_integrating = query.byBranchStatus(destBranchShortKey, Change.Status.INTEGRATING); - - query = queryProvider.get(); - List<ChangeData> changes_staged = query.byBranchStatus(destBranchShortKey, Change.Status.STAGED); - - List<ChangeData> changes_allowed = new ArrayList<ChangeData>(); - changes_allowed.addAll(changes_integrating); - changes_allowed.addAll(changes_staged); - List<ChangeData> changes = arrangeOrderLikeInRef(git, oldStageRefObjId, branchObjId, changes_allowed); - - logger.atInfo().log("qtcodereview: rebuild reset %s back to %s", stagingBranchKey, destBranchShortKey); - Result result = QtUtil.createStagingBranch(git, destBranchShortKey); - if (result == null) throw new NoSuchRefException("Cannot create staging ref: " + stagingBranchKey.get()); - logger.atInfo().log("qtcodereview: rebuild reset result %s",result); - - ObjectId newId = pickChangestoStagingRef(git, - projectKey, - changes, - git.resolve(stagingBranchKey.get())); - - RefUpdate refUpdate = git.updateRef(stagingBranchKey.get()); - refUpdate.setNewObjectId(newId); - refUpdate.update(); - - // send ref updated event only if it changed - if (!newId.equals(oldStageRefObjId)) { - referenceUpdated.fire(projectKey, stagingBranchKey.get(), oldStageRefObjId, newId, user.state()); - } - - } catch (OrmException e) { - logger.atSevere().log("qtcodereview: rebuild %s failed. Failed to access database %s", stagingBranchKey, e); - throw new MergeConflictException("fatal: Failed to access database"); - } catch (IOException e) { - logger.atSevere().log("qtcodereview: rebuild %s failed. IOException %s", stagingBranchKey, e); - throw new MergeConflictException("fatal: IOException"); - } catch (NoSuchRefException e) { - logger.atSevere().log("qtcodereview: rebuild %s failed. No such ref %s", stagingBranchKey, e); - throw new MergeConflictException("fatal: NoSuchRefException"); - } catch(IntegrationException e) { - logger.atSevere().log("qtcodereview: rebuild %s failed. IntegrationException %s", stagingBranchKey, e); - throw new MergeConflictException("fatal: IntegrationException"); - } + InternalChangeQuery query = null; + List<ChangeData> changes_integrating = null; + List<ChangeData> changes_staged = null; + ObjectId oldStageRefObjId = null; + ObjectId branchObjId = null; + ObjectId newStageRefObjId = null; + ObjectId newObjId = null; + String branchName = null; + + try { + branchName = stagingBranchKey.get(); + oldStageRefObjId = git.resolve(branchName); + branchObjId = git.resolve(destBranchShortKey.get()); + + query = queryProvider.get(); + List<ChangeData> unsorted_list = query.byBranchStatus(destBranchShortKey, Change.Status.INTEGRATING); + changes_integrating = arrangeOrderLikeInRef(git, oldStageRefObjId, branchObjId, unsorted_list); + + query = queryProvider.get(); + unsorted_list = query.byBranchStatus(destBranchShortKey, Change.Status.STAGED); + changes_staged = arrangeOrderLikeInRef(git, oldStageRefObjId, branchObjId, unsorted_list); + } catch (OrmException e) { + logger.atSevere().log("qtcodereview: rebuild staging ref %s failed. Failed to access database %s", + stagingBranchKey, e); + throw new MergeConflictException("fatal: Failed to access database"); + } catch (IOException e) { + logger.atSevere().log("qtcodereview: rebuild staging ref %s db failed. IOException %s", + stagingBranchKey, e); + throw new MergeConflictException("fatal: IOException"); + } + + try { + logger.atInfo().log("qtcodereview: rebuild staging ref reset %s back to %s", + stagingBranchKey, destBranchShortKey); + Result result = QtUtil.createStagingBranch(git, destBranchShortKey); + if (result == null) throw new NoSuchRefException("Cannot create staging ref: " + branchName); + logger.atInfo().log("qtcodereview: rebuild staging ref reset result %s", result); + newStageRefObjId = git.resolve(branchName); + } catch (NoSuchRefException e) { + logger.atSevere().log("qtcodereview: rebuild staging ref reset %s failed. No such ref %s", + stagingBranchKey, e); + throw new MergeConflictException("fatal: NoSuchRefException"); + } catch (IOException e) { + logger.atSevere().log("qtcodereview: rebuild staging ref reset %s failed. IOException %s", + stagingBranchKey, e); + throw new MergeConflictException("fatal: IOException"); + } + + try { + newObjId = pickChangesToStagingRef(git, projectKey, changes_integrating, newStageRefObjId); + newStageRefObjId = newObjId; + } catch(Exception e) { + logger.atSevere().log("qtcodereview: rebuild staging ref %s. failed to cherry pick integrating changes %s", + stagingBranchKey, e); + newObjId = null; + } + + if (newObjId != null) { + try { + newObjId = pickChangesToStagingRef(git, projectKey, changes_staged, newObjId); + newStageRefObjId = newObjId; + } catch(Exception e) { + newObjId = null; + logger.atInfo().log("qtcodereview: rebuild staging ref %s merge conflict", stagingBranchKey); + String message = "Merge conflict in staging branch. Status changed back to new. Please stage again."; + QtChangeUpdateOp op = qtUpdateFactory.create(Change.Status.NEW, message, null, null, null); + try (BatchUpdate u = updateFactory.create(dbProvider.get(), projectKey, user, TimeUtil.nowTs())) { + for (ChangeData item: changes_staged) { + Change change = item.change(); + logger.atInfo().log("qtcodereview: staging ref rebuild merge conflict. Change %s back to NEW", change); + u.addOp(change.getId(), op); + } + u.execute(); + } catch (OrmException ex) { + logger.atSevere().log("qtcodereview: staging ref rebuild. Failed to access database %s", ex); + } catch (UpdateException | RestApiException ex) { + logger.atSevere().log("qtcodereview: staging ref rebuild. Failed to update change status %s", ex); + } + } + } + + try { + RefUpdate refUpdate = git.updateRef(branchName); + refUpdate.setNewObjectId(newStageRefObjId); + refUpdate.update(); + + // send ref updated event only if it changed + if (!newStageRefObjId.equals(oldStageRefObjId)) { + referenceUpdated.fire(projectKey, branchName, oldStageRefObjId, + newStageRefObjId, user.state()); + } + } catch (IOException e) { + logger.atSevere().log("qtcodereview: rebuild %s failed to update ref %s", stagingBranchKey, e); + throw new MergeConflictException("fatal: IOException"); + } } /** diff --git a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommonFlowsIT.java b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommonFlowsIT.java index 8068163..2469d62 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommonFlowsIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtCommonFlowsIT.java @@ -347,4 +347,41 @@ public class QtCommonFlowsIT extends QtCodeReviewIT { assertThat(updatedHead).isEqualTo(initialHead); } + @Test + public void unStage_MergeConflict_While_Building() throws Exception { + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result c1 = pushCommit("master", "commitmsg1", "file1", "content1"); + testRepo.reset(initialHead); + PushOneCommit.Result c2 = pushCommit("master", "commitmsg2", "file2", "content2"); + testRepo.reset(initialHead); + PushOneCommit.Result c3 = pushCommit("master", "commitmsg3", "thesamefile", "content3"); + // c4 depends on c3 + PushOneCommit.Result c4 = pushCommit("master", "commitmsg4", "thesamefile", "conflict content3"); + approve(c1.getChangeId()); + approve(c2.getChangeId()); + approve(c3.getChangeId()); + approve(c4.getChangeId()); + + QtStage(c1); + QtNewBuild("master", "master-build-500"); + RevCommit stagingExpected = getRemoteHead(project, R_STAGING + "master"); + + QtStage(c2); + QtStage(c3); + QtStage(c4); + + QtUnStage(c3); + Change change = c1.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.INTEGRATING); + change = c2.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.NEW); + change = c3.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.NEW); + change = c4.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.NEW); + + RevCommit stagingHeadMaster = getRemoteHead(project, R_STAGING + "master"); + assertThat(stagingHeadMaster.getId()).isEqualTo(stagingExpected.getId()); + } + } diff --git a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtUnStageIT.java b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtUnStageIT.java index 633515b..06b249e 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtUnStageIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtUnStageIT.java @@ -212,6 +212,29 @@ public class QtUnStageIT extends QtCodeReviewIT { } @Test + public void multiChange_UnStage_MergeConflict() throws Exception { + RevCommit initialHead = getRemoteHead(); + PushOneCommit.Result c1 = pushCommit("master", "commitmsg1", "thesamefile", "content1"); + // c2 depends on c1 + PushOneCommit.Result c2 = pushCommit("master", "commitdependingon1", "thesamefile", "conflict"); + testRepo.reset(initialHead); + PushOneCommit.Result c3 = pushCommit("master", "commitmsg3", "file3", "content3"); + + approve(c1.getChangeId()); + QtStage(c1); + approve(c2.getChangeId()); + QtStage(c2); + approve(c3.getChangeId()); + QtStage(c3); + + RevCommit stagingHead = qtUnStageExpectCommit(c1, initialHead); + Change change = c2.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.NEW); + change = c3.getChange().change(); + assertThat(change.getStatus()).isEqualTo(Change.Status.NEW); + } + + @Test public void errorUnStage_No_Permission() throws Exception { PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1"); approve(c.getChangeId()); |