               local-repo branch (format "%s/%s" remote remote-branch))
              (cl-return-from straight-vc-git--pull-from-remote-raw t))))))
 
-(cl-defun straight-vc-git--validate-head-pushed (recipe)
-  "Validate that in RECIPE's local repo, main branch is behind primary remote."
+(cl-defun straight-vc-git--ensure-head-pushed
+    (recipe)
+  "Ensure that in RECIPE's local repo, main branch is behind primary remote.
+Return non-nil. If no local repository, do nothing and return non-nil."
   (unless (plist-member recipe :repo)
-    (cl-return-from straight-vc-git--validate-head-pushed t))
-  (ignore
-   (straight--with-plist recipe
-       (local-repo branch)
-     (let* ((branch (or branch straight-vc-git-default-branch))
-            (ref (format "%s/%s" straight-vc-git-primary-remote branch)))
-       (when (straight--check-call
-              "git" "merge-base" "--is-ancestor"
-              branch ref)
-         (cl-return-from straight-vc-git--validate-head-pushed t))
-       (straight-vc-git--popup
-         (format "In repository %S, branch %S has commits unpushed to %S."
-                 local-repo branch ref)
-         ("p" "Pull and then push"
-          (unless (condition-case _
-                      (ignore
-                       (straight-vc-git--pull-from-remote-raw
-                        recipe straight-vc-git-primary-remote branch))
-                    (quit t))
-            (when (straight-are-you-sure
-                   (format "Really push to %S in %S?" ref local-repo))
-              ;; If push fails, fall back to higher-level error handling
-              ;; to allow the user the option to skip, come back later,
-              ;; etc. I think it's foolish to allow force-pushing; the
-              ;; user can do that manually if they *really* want to.
-              (straight--get-call
-               "git" "push" straight-vc-git-primary-remote
-               (format "refs/heads/%s:refs/heads/%s" branch branch))))))))))
+    (cl-return-from straight-vc-git--ensure-head-pushed t))
+  (let ((push-error-message nil))
+    (while t
+      (while (not (straight-vc-git--validate-local recipe)))
+      (straight--with-plist recipe
+          (local-repo branch)
+        (let* ((branch (or branch straight-vc-git-default-branch))
+               (ref (format "%s/%s" straight-vc-git-primary-remote branch)))
+          (when (straight--check-call
+                 "git" "merge-base" "--is-ancestor"
+                 branch ref)
+            (cl-return-from straight-vc-git--ensure-head-pushed t))
+          (let* ((log (straight--get-call
+                       "git" "log" "--format=%h %s"
+                       (concat ref ".." branch)))
+                 (num-commits (length (straight--split-and-trim log))))
+            (straight-vc-git--popup
+              (format
+               (concat "In repository %S, branch %S has %d "
+                       "commit%s unpushed to %S%s:\n\n%s")
+               local-repo branch num-commits (if (= num-commits 1) "" "s") ref
+               (if (> num-commits 5) ", including" "")
+               (concat
+                (straight--split-and-trim log 2 5)
+                (when push-error-message
+                  (concat "\n\nPush failed:\n\n  "
+                          push-error-message))))
+              ("f" (format "Pull %S into branch %S" ref branch)
+               (straight-vc-git--pull-from-remote-raw
+                recipe straight-vc-git-primary-remote branch))
+              ("p" (format "Push branch %S to %S" branch ref)
+               (when (straight-are-you-sure
+                      (format "Really push to %S in %S?" ref local-repo))
+                 (straight-catching-quit
+                   (let ((result
+                          (straight--call
+                           "git" "push" straight-vc-git-primary-remote
+                           (format
+                            "refs/heads/%s:refs/heads/%s" branch branch))))
+                     (unless (car result)
+                       (setq push-error-message
+                             (string-trim (cdr result)))))))))))))))
 
 (defun straight-vc-git--validate-local (recipe)
   "Validate that local repository for RECIPE is as expected.

    file))

;;;;; Interactive popup windows

(defun straight-popup-raw (prompt actions)
  "Display PROMPT and allow user to choose between one of several ACTIONS.


;;;;; Strings

(defun straight--split-and-trim (string &optional indent max-lines)
  "Split the STRING on newlines, returning a list.
Remove any blank lines at the beginning or end. If INDENT is
non-nil, then add that many spaces to the beginning of each line
and concatenate them with newlines, returning a string instead of
a list. If MAX-ENTRIES is non-nil, then it should be a
nonnegative integer, and any lines past that many are discarded."
  (let ((parts (split-string string "\n")))
    ;; Remove blank lines from beginning.
    (while (equal (car parts) "")
