Merge "Fix rebase patch set and revert change to update Git first"
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index 3868710..94b2169 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server;
 
+import com.google.common.collect.Sets;
 import com.google.gerrit.common.ChangeHookRunner;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.reviewdb.client.Account;
@@ -38,7 +39,6 @@
 import com.google.gerrit.server.mail.ReplyToChangeSender;
 import com.google.gerrit.server.mail.RevertedSender;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -68,6 +68,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -196,13 +197,15 @@
    * Rebases a commit
    *
    * @param git Repository to find commits in
+   * @param inserter inserter to handle new trees and blobs.
    * @param original The commit to rebase
    * @param base Base to rebase against
    * @return CommitBuilder the newly rebased commit
    * @throws IOException Merged failed
    */
-  public static CommitBuilder rebaseCommit(Repository git, RevCommit original,
-      RevCommit base, PersonIdent committerIdent) throws IOException {
+  public static CommitBuilder rebaseCommit(Repository git,
+      final ObjectInserter inserter, RevCommit original, RevCommit base,
+      PersonIdent committerIdent) throws IOException {
 
     if (original.getParentCount() == 0) {
       throw new IOException(
@@ -222,6 +225,20 @@
     }
 
     final ThreeWayMerger merger = MergeStrategy.RESOLVE.newMerger(git, true);
+    merger.setObjectInserter(new ObjectInserter.Filter() {
+      @Override
+      protected ObjectInserter delegate() {
+        return inserter;
+      }
+
+      @Override
+      public void flush() {
+      }
+
+      @Override
+      public void release() {
+      }
+    });
     merger.setBase(parentCommit);
     merger.merge(original, base);
 
@@ -251,7 +268,7 @@
       final ApprovalsUtil approvalsUtil) throws NoSuchChangeException,
       EmailException, OrmException, MissingObjectException,
       IncorrectObjectTypeException, IOException,
-      PatchSetInfoNotAvailableException, InvalidChangeOperationException {
+      InvalidChangeOperationException {
 
     final Change.Id changeId = patchSetId.getParentKey();
     final ChangeControl changeControl =
@@ -319,104 +336,118 @@
           branchTipCommit = revWalk.parseCommit(destRef.getObjectId());
         }
 
-        final RevCommit originalCommit =
-            revWalk.parseCommit(ObjectId.fromString(originalPatchSet
-                .getRevision().get()));
-
-        CommitBuilder rebasedCommitBuilder =
-            rebaseCommit(git, originalCommit, branchTipCommit, myIdent);
-
+        final RevCommit rebasedCommit;
         final ObjectInserter oi = git.newObjectInserter();
-        final ObjectId rebasedCommitId;
         try {
-          rebasedCommitId = oi.insert(rebasedCommitBuilder);
+          ObjectId oldId = ObjectId.fromString(originalPatchSet.getRevision().get());
+          ObjectId newId = oi.insert(rebaseCommit(
+              git, oi, revWalk.parseCommit(oldId), branchTipCommit, myIdent));
           oi.flush();
+          rebasedCommit = revWalk.parseCommit(newId);
         } finally {
           oi.release();
         }
 
-        Change updatedChange =
-            db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
-              @Override
-              public Change update(Change change) {
-                if (change.getStatus().isOpen()) {
-                  change.nextPatchSetId();
-                  return change;
-                } else {
-                  return null;
-                }
-              }
-            });
+        change.nextPatchSetId();
+        final PatchSet newPatchSet = new PatchSet(change.currPatchSetId());
+        newPatchSet.setCreatedOn(new Timestamp(System.currentTimeMillis()));
+        newPatchSet.setUploader(user.getAccountId());
+        newPatchSet.setRevision(new RevId(rebasedCommit.name()));
 
-        if (updatedChange == null) {
-          throw new InvalidChangeOperationException("Change is closed: "
-              + change.toString());
-        } else {
-          change = updatedChange;
-        }
-
-        final PatchSet rebasedPatchSet = new PatchSet(change.currPatchSetId());
-        rebasedPatchSet.setCreatedOn(change.getCreatedOn());
-        rebasedPatchSet.setUploader(user.getAccountId());
-        rebasedPatchSet.setRevision(new RevId(rebasedCommitId.getName()));
-
-        insertAncestors(db, rebasedPatchSet.getId(),
-            revWalk.parseCommit(rebasedCommitId));
-
-        db.patchSets().insert(Collections.singleton(rebasedPatchSet));
         final PatchSetInfo info =
-            patchSetInfoFactory.get(db, rebasedPatchSet.getId());
+            patchSetInfoFactory.get(rebasedCommit, newPatchSet.getId());
 
-        change =
-            db.changes().atomicUpdate(change.getId(),
-                new AtomicUpdate<Change>() {
-                  @Override
-                  public Change update(Change change) {
-                    change.setCurrentPatchSet(info);
-                    ChangeUtil.updated(change);
-                    return change;
-                  }
-                });
-
-        final RefUpdate ru = git.updateRef(rebasedPatchSet.getRefName());
-        ru.setNewObjectId(rebasedCommitId);
+        RefUpdate ru = git.updateRef(newPatchSet.getRefName());
+        ru.setExpectedOldObjectId(ObjectId.zeroId());
+        ru.setNewObjectId(rebasedCommit);
         ru.disableRefLog();
         if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-          throw new IOException("Failed to create ref "
-              + rebasedPatchSet.getRefName() + " in " + git.getDirectory()
-              + ": " + ru.getResult());
+          throw new IOException(String.format(
+              "Failed to create ref %s in %s: %s", newPatchSet.getRefName(),
+              change.getDest().getParentKey().get(), ru.getResult()));
         }
-
         replication.fire(change.getProject(), ru.getName());
 
-        List<PatchSetApproval> patchSetApprovals = approvalsUtil.copyVetosToLatestPatchSet(change);
+        final Set<Account.Id> oldReviewers = Sets.newHashSet();
+        final Set<Account.Id> oldCC = Sets.newHashSet();
+        db.changes().beginTransaction(change.getId());
+        try {
+          Change updatedChange;
 
-        final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
-        final Set<Account.Id> oldCC = new HashSet<Account.Id>();
-
-        for (PatchSetApproval a : patchSetApprovals) {
-          if (a.getValue() != 0) {
-            oldReviewers.add(a.getAccountId());
+          updatedChange = db.changes().atomicUpdate(changeId,
+              new AtomicUpdate<Change>() {
+                @Override
+                public Change update(Change change) {
+                  if (change.getStatus().isOpen()) {
+                    change.updateNumberOfPatchSets(newPatchSet.getPatchSetId());
+                    return change;
+                  } else {
+                    return null;
+                  }
+                }
+              });
+          if (updatedChange != null) {
+            change = updatedChange;
           } else {
-            oldCC.add(a.getAccountId());
+            throw new InvalidChangeOperationException(
+                String.format("Change %s is closed", change.getId()));
           }
-        }
 
-        final ChangeMessage cmsg =
-            new ChangeMessage(new ChangeMessage.Key(changeId,
-                ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
-        cmsg.setMessage("Patch Set " + patchSetId.get() + ": Rebased");
-        db.changeMessages().insert(Collections.singleton(cmsg));
+          insertAncestors(db, newPatchSet.getId(), rebasedCommit);
+          db.patchSets().insert(Collections.singleton(newPatchSet));
+          updatedChange = db.changes().atomicUpdate(changeId,
+              new AtomicUpdate<Change>() {
+                @Override
+                public Change update(Change change) {
+                  if (change.getStatus().isClosed()) {
+                    return null;
+                  }
+                  if (!change.currentPatchSetId().equals(patchSetId)) {
+                    return null;
+                  }
+                  if (change.getStatus() != Change.Status.DRAFT) {
+                    change.setStatus(Change.Status.NEW);
+                  }
+                  change.setLastSha1MergeTested(null);
+                  change.setCurrentPatchSet(info);
+                  ChangeUtil.updated(change);
+                  return change;
+                }
+              });
+          if (updatedChange != null) {
+            change = updatedChange;
+          } else {
+            throw new InvalidChangeOperationException(
+                String.format("Change %s was modified", change.getId()));
+          }
+
+          for (PatchSetApproval a : approvalsUtil.copyVetosToLatestPatchSet(change)) {
+            if (a.getValue() != 0) {
+              oldReviewers.add(a.getAccountId());
+            } else {
+              oldCC.add(a.getAccountId());
+            }
+          }
+
+          final ChangeMessage cmsg =
+              new ChangeMessage(new ChangeMessage.Key(changeId,
+                  ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
+          cmsg.setMessage("Patch Set " + patchSetId.get() + ": Rebased");
+          db.changeMessages().insert(Collections.singleton(cmsg));
+          db.commit();
+        } finally {
+          db.rollback();
+        }
 
         final ReplacePatchSetSender cm =
             rebasedPatchSetSenderFactory.create(change);
         cm.setFrom(user.getAccountId());
-        cm.setPatchSet(rebasedPatchSet);
+        cm.setPatchSet(newPatchSet);
         cm.addReviewers(oldReviewers);
         cm.addExtraCC(oldCC);
         cm.send();
 
-        hooks.doPatchsetCreatedHook(change, rebasedPatchSet, db);
+        hooks.doPatchsetCreatedHook(change, newPatchSet, db);
       } finally {
         revWalk.release();
       }
@@ -432,9 +463,7 @@
       final PatchSetInfoFactory patchSetInfoFactory,
       final GitReferenceUpdated replication, PersonIdent myIdent)
       throws NoSuchChangeException, EmailException, OrmException,
-      MissingObjectException, IncorrectObjectTypeException, IOException,
-      PatchSetInfoNotAvailableException {
-
+      MissingObjectException, IncorrectObjectTypeException, IOException {
     final Change.Id changeId = patchSetId.getParentKey();
     final PatchSet patch = db.patchSets().get(patchSetId);
     if (patch == null) {
@@ -446,7 +475,7 @@
       git = gitManager.openRepository(db.changes().get(changeId).getProject());
     } catch (RepositoryNotFoundException e) {
       throw new NoSuchChangeException(changeId, e);
-    };
+    }
 
     final RevWalk revWalk = new RevWalk(git);
     try {
@@ -459,54 +488,62 @@
       RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
       revWalk.parseHeaders(parentToCommitToRevert);
 
-      CommitBuilder revertCommit = new CommitBuilder();
-      revertCommit.addParentId(commitToRevert);
-      revertCommit.setTreeId(parentToCommitToRevert.getTree());
-      revertCommit.setAuthor(authorIdent);
-      revertCommit.setCommitter(myIdent);
+      CommitBuilder revertCommitBuilder = new CommitBuilder();
+      revertCommitBuilder.addParentId(commitToRevert);
+      revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
+      revertCommitBuilder.setAuthor(authorIdent);
+      revertCommitBuilder.setCommitter(myIdent);
 
       final ObjectId computedChangeId =
           ChangeIdUtil.computeChangeId(parentToCommitToRevert.getTree(),
               commitToRevert, authorIdent, myIdent, message);
-      revertCommit.setMessage(ChangeIdUtil.insertId(message, computedChangeId, true));
+      revertCommitBuilder.setMessage(ChangeIdUtil.insertId(message, computedChangeId, true));
 
-      final ObjectInserter oi = git.newObjectInserter();;
-      ObjectId id;
+      RevCommit revertCommit;
+      final ObjectInserter oi = git.newObjectInserter();
       try {
-        id = oi.insert(revertCommit);
+        ObjectId id = oi.insert(revertCommitBuilder);
         oi.flush();
+        revertCommit = revWalk.parseCommit(id);
       } finally {
         oi.release();
       }
 
-      Change.Key changeKey = new Change.Key("I" + computedChangeId.name());
-      final Change change =
-          new Change(changeKey, new Change.Id(db.nextChangeId()),
-              user.getAccountId(), db.changes().get(changeId).getDest());
+      final Change change = new Change(
+          new Change.Key("I" + computedChangeId.name()),
+          new Change.Id(db.nextChangeId()),
+          user.getAccountId(),
+          db.changes().get(changeId).getDest());
       change.nextPatchSetId();
 
       final PatchSet ps = new PatchSet(change.currPatchSetId());
       ps.setCreatedOn(change.getCreatedOn());
-      ps.setUploader(user.getAccountId());
-      ps.setRevision(new RevId(id.getName()));
+      ps.setUploader(change.getOwner());
+      ps.setRevision(new RevId(revertCommit.name()));
 
-      db.patchSets().insert(Collections.singleton(ps));
-
-      final PatchSetInfo info =
-          patchSetInfoFactory.get(revWalk.parseCommit(id), ps.getId());
-      change.setCurrentPatchSet(info);
+      change.setCurrentPatchSet(patchSetInfoFactory.get(revertCommit, ps.getId()));
       ChangeUtil.updated(change);
-      db.changes().insert(Collections.singleton(change));
 
       final RefUpdate ru = git.updateRef(ps.getRefName());
-      ru.setNewObjectId(id);
+      ru.setExpectedOldObjectId(ObjectId.zeroId());
+      ru.setNewObjectId(revertCommit);
       ru.disableRefLog();
       if (ru.update(revWalk) != RefUpdate.Result.NEW) {
-        throw new IOException("Failed to create ref " + ps.getRefName()
-            + " in " + git.getDirectory() + ": " + ru.getResult());
+        throw new IOException(String.format(
+            "Failed to create ref %s in %s: %s", ps.getRefName(),
+            change.getDest().getParentKey().get(), ru.getResult()));
       }
-      replication.fire(db.changes().get(changeId).getProject(),
-          ru.getName());
+      replication.fire(change.getProject(), ru.getName());
+
+      db.changes().beginTransaction(change.getId());
+      try {
+        insertAncestors(db, ps.getId(), revertCommit);
+        db.patchSets().insert(Collections.singleton(ps));
+        db.changes().insert(Collections.singleton(change));
+        db.commit();
+      } finally {
+        db.rollback();
+      }
 
       final ChangeMessage cmsg =
           new ChangeMessage(new ChangeMessage.Key(changeId,
@@ -514,7 +551,7 @@
       final StringBuilder msgBuf =
           new StringBuilder("Patch Set " + patchSetId.get() + ": Reverted");
       msgBuf.append("\n\n");
-      msgBuf.append("This patchset was reverted in change: " + changeKey.get());
+      msgBuf.append("This patchset was reverted in change: " + change.getKey().get());
 
       cmsg.setMessage(msgBuf.toString());
       db.changeMessages().insert(Collections.singleton(cmsg));
@@ -596,7 +633,7 @@
   public static <T extends ReplyToChangeSender> void updatedChange(
       final ReviewDb db, final IdentifiedUser user, final Change change,
       final ChangeMessage cmsg, ReplyToChangeSender.Factory<T> senderFactory)
-      throws NoSuchChangeException, EmailException, OrmException {
+      throws OrmException {
     db.changeMessages().insert(Collections.singleton(cmsg));
 
     new ApprovalsUtil(db, null).syncChangeStatus(change);