sync: Abort rebase in progress if force-checkout is set

This will make "repo sync -d --force-checkout" more reliable
in CI automation, as there are fewer things in the way that may
need manual intervention.

Bug: b/40015382
Change-Id: I8a79971724a3d9a8e2d682b7a0c04deda9e34177
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/423317
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Erik Elmeke <erik@haleytek.corp-partner.google.com>
Commit-Queue: Erik Elmeke <erik@haleytek.corp-partner.google.com>
diff --git a/project.py b/project.py
index 07685da..80a6c39 100644
--- a/project.py
+++ b/project.py
@@ -727,12 +727,34 @@
         return None
 
     def IsRebaseInProgress(self):
+        """Returns true if a rebase or "am" is in progress"""
+        # "rebase-apply" is used for "git rebase".
+        # "rebase-merge" is used for "git am".
         return (
             os.path.exists(self.work_git.GetDotgitPath("rebase-apply"))
             or os.path.exists(self.work_git.GetDotgitPath("rebase-merge"))
             or os.path.exists(os.path.join(self.worktree, ".dotest"))
         )
 
+    def IsCherryPickInProgress(self):
+        """Returns True if a cherry-pick is in progress."""
+        return os.path.exists(self.work_git.GetDotgitPath("CHERRY_PICK_HEAD"))
+
+    def _AbortRebase(self):
+        """Abort ongoing rebase, cherry-pick or patch apply (am).
+
+        If no rebase, cherry-pick or patch apply was in progress, this method
+        ignores the status and continues.
+        """
+
+        def _git(*args):
+            # Ignore return code, in case there was no rebase in progress.
+            GitCommand(self, *args, log_as_error=False).Wait()
+
+        _git("cherry-pick", "--abort")
+        _git("rebase", "--abort")
+        _git("am", "--abort")
+
     def IsDirty(self, consider_untracked=True):
         """Is the working directory modified in some way?"""
         self.work_git.update_index(
@@ -1583,7 +1605,15 @@
         if branch is None or syncbuf.detach_head:
             # Currently on a detached HEAD.  The user is assumed to
             # not have any local modifications worth worrying about.
-            if self.IsRebaseInProgress():
+            rebase_in_progress = (
+                self.IsRebaseInProgress() or self.IsCherryPickInProgress()
+            )
+            if rebase_in_progress and force_checkout:
+                self._AbortRebase()
+                rebase_in_progress = (
+                    self.IsRebaseInProgress() or self.IsCherryPickInProgress()
+                )
+            if rebase_in_progress:
                 fail(_PriorSyncFailedError(project=self.name))
                 return