Summarize number of commits being processed by `git cl upload`.

Occasionally, users can get a branch into a state where `git cl upload`
takes a long time because it is operating on an unexpectedly large diff.
One way where this can happen is when the local view of origin/main is
behind and someone force-patches in a CL based on a newer origin/main:
git cl upload will happily consider all the origin/main commits it
does not have in its local view of origin/main to simply be part of the
diff.

As this is a rather frustrating user experience, make it a bit easier to
realize when this sort of thing is happening by summarizing the number
of commits `git cl upload` is processing.

One alternative would be to stats about the diff; however, calculating
the number of commits is considerably faster than calculating the actual
diff. A quick local test shows that calculating the diff for 10k commits
takes nearly 20 seconds, while calculating the number of commits takes a
150 milliseconds.

Change-Id: I0e8706f164f6bf669f36f4792401589644be38b5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/4819796
Commit-Queue: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Gavin Mak <gavinmak@google.com>
diff --git a/git_cl.py b/git_cl.py
index d2acf19..0b879d4 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -30,6 +30,9 @@
 import zlib
 
 from third_party import colorama
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
 import auth
 import clang_format
 import fix_encoding
@@ -732,6 +735,21 @@
           if not any(fnmatch.fnmatch(f, p) for p in patterns)]
 
 
+def _GetCommitCountSummary(begin_commit: str,
+                           end_commit: str = "HEAD") -> Optional[str]:
+  """Generate a summary of the number of commits in (begin_commit, end_commit).
+
+  Returns a string containing the summary, or None if the range is empty.
+  """
+  count = int(
+      RunGitSilent(['rev-list', '--count', f'{begin_commit}..{end_commit}']))
+
+  if not count:
+    return None
+
+  return f'{count} commit{"s"[:count!=1]}'
+
+
 def print_stats(args):
   """Prints statistics about the change to the user."""
   # --no-ext-diff is broken in some versions of Git, so try to work around
@@ -1815,6 +1833,8 @@
     self.EnsureAuthenticated(force=options.force)
     self.EnsureCanUploadPatchset(force=options.force)
 
+    print(f'Processing {_GetCommitCountSummary(*git_diff_args)}...')
+
     # Apply watchlists on upload.
     watchlist = watchlists.Watchlists(settings.GetRoot())
     files = self.GetAffectedFiles(base_branch)
@@ -4843,13 +4863,13 @@
   return ret
 
 
-def UploadAllSquashed(options, orig_args):
-  # type: (optparse.Values, Sequence[str]) -> Tuple[Sequence[Changelist], bool]
+def UploadAllSquashed(options: optparse.Values,
+                      orig_args: Sequence[str]) -> int:
   """Uploads the current and upstream branches (if necessary)."""
   cls, cherry_pick_current = _UploadAllPrecheck(options, orig_args)
 
   # Create commits.
-  uploads_by_cl = []  #type: Sequence[Tuple[Changelist, _NewUpload]]
+  uploads_by_cl: list[Tuple[Changelist, _NewUpload]] = []
   if cherry_pick_current:
     parent = cls[1]._GitGetBranchConfigValue(GERRIT_SQUASH_HASH_CONFIG_KEY)
     new_upload = cls[0].PrepareCherryPickSquashedCommit(options, parent)
@@ -4986,13 +5006,14 @@
     base_commit = cl.GetCommonAncestorWithUpstream()
     end_commit = RunGit(['rev-parse', cl.GetBranchRef()]).strip()
 
-    diff = RunGitSilent(['diff', '%s..%s' % (base_commit, end_commit)])
-    if diff:
+    commit_summary = _GetCommitCountSummary(base_commit, end_commit)
+    if commit_summary:
       cls.append(cl)
       if (not first_pass and
           cl._GitGetBranchConfigValue(GERRIT_SQUASH_HASH_CONFIG_KEY) is None):
         # We are mid-stack and the user must upload their upstream branches.
         must_upload_upstream = True
+      print(f'Found change with {commit_summary}...')
     elif first_pass:  # The current branch has nothing to commit. Exit.
       DieWithError('Branch %s has nothing to commit' % cl.GetBranch())
     # Else: A mid-stack branch has nothing to commit. We do not add it to cls.