new_variant: warn user about pending changes

If there are uncommitted changes or if the local branch is ahead of
upstream, warn the user and have them fix it (or override it) before
continuing.

BUG=b:176124406
TEST=follow instructions in platform/dev/contrib/variant/testdata/README.md

Change-Id: Ic2f50029b7c43718d92e55a4dd627a93df234cc7
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2600106
Commit-Queue: Paul Fagerburg <pfagerburg@chromium.org>
Tested-by: Paul Fagerburg <pfagerburg@chromium.org>
Reviewed-by: Justin TerAvest <teravest@chromium.org>
diff --git a/contrib/variant/add_variant_to_yaml.sh b/contrib/variant/add_variant_to_yaml.sh
index 389856e..1c36353 100755
--- a/contrib/variant/add_variant_to_yaml.sh
+++ b/contrib/variant/add_variant_to_yaml.sh
@@ -3,7 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-VERSION="1.4.0"
+VERSION="1.5.0"
 SCRIPT=$(basename -- "${0}")
 set -e
 
@@ -30,6 +30,10 @@
 # shellcheck disable=SC1091
 source "${BASH_SOURCE%/*}/check_standalone.sh"
 
+# shellcheck source=check_pending_changes.sh
+# shellcheck disable=SC1091
+source "${BASH_SOURCE%/*}/check_pending_changes.sh"
+
 # This is the name of the base board that we're using to make the variant.
 # ${var,,} converts to all lowercase.
 BASE="${1,,}"
@@ -59,6 +63,9 @@
   exit 1
 fi
 
+# If there are pending changes, exit the script (unless overridden)
+check_pending_changes "$(pwd)"
+
 # Start a branch. Use YMD timestamp to avoid collisions.
 DATE=$(date +%Y%m%d)
 BRANCH="create_${VARIANT}_${DATE}"
diff --git a/contrib/variant/check_pending_changes.sh b/contrib/variant/check_pending_changes.sh
new file mode 100755
index 0000000..7b08c3e
--- /dev/null
+++ b/contrib/variant/check_pending_changes.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+check_pending_changes() {
+  # Check if there are pending changes in this repo, and if so, exit with
+  # an error.
+  #
+  # Usage: check_pending_changes ${DIR}
+
+  local DIR="$1"
+
+  # Check for local modifications and warn the user to stash or revert.
+  # Porcelain v2 format uses a '1' for changes, '2' for renames, and 'u'
+  # for unmerged.
+  if git status --porcelain=v2 --branch | grep -q "^[12u]" ; then
+    MSG=$(echo "You have uncommitted changes in ${DIR}
+
+Please stash or revert these changes, then re-run this program." | fmt -w 80)
+   cat <<EOF >&2
+******************************************************************************
+${MSG}
+******************************************************************************
+EOF
+    return 1
+  fi
+
+  # We usually want to track the upstream, but if NEW_VARIANT_WIP=1, then we
+  # want to create new branches from our current HEAD and not from the
+  # upstream-tracking branch. Check for this variable first, and if it's
+  # set to 1, then allow the calling script to continue.
+
+  # ${var:-0} assigns a value of 0 if the variable is not set.
+  if [[ "${NEW_VARIANT_WIP:-0}" == 1 ]] ; then
+    return 0
+  fi
+
+  pushd "${DIR}"
+
+  # Check that we are tracking the upstream, and if not, ask the user
+  # to checkout upstream or use NEW_VARIANT_WIP=1.
+  if git status --porcelain=v2 --branch | grep -q "branch\.ab" ; then
+    MSG=$(echo "Your local tree is ahead of the upstream by 1 or more commits.
+You probably don't want to base the new variant on this branch, so please check
+out the upstream branch in ${DIR} and then re-run this program.
+
+If you want to base the new variant on the current branch, set NEW_VARIANT_WIP=1
+in your environment and re-run this program." | fmt -w 80)
+   cat <<EOF >&2
+******************************************************************************
+${MSG}
+******************************************************************************
+EOF
+    return 1
+  fi
+
+  popd
+  return 0
+}
diff --git a/contrib/variant/copy_cras_config.sh b/contrib/variant/copy_cras_config.sh
index 0f90d1f..ad476f2 100755
--- a/contrib/variant/copy_cras_config.sh
+++ b/contrib/variant/copy_cras_config.sh
@@ -3,7 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-VERSION="1.2.0"
+VERSION="1.3.0"
 SCRIPT=$(basename -- "${0}")
 set -e
 
@@ -30,6 +30,10 @@
 # shellcheck disable=SC1091
 source "${BASH_SOURCE%/*}/check_standalone.sh"
 
+# shellcheck source=check_pending_changes.sh
+# shellcheck disable=SC1091
+source "${BASH_SOURCE%/*}/check_pending_changes.sh"
+
 # This is the name of the base board.
 # ${var,,} converts to all lowercase.
 BASE="${1,,}"
@@ -44,6 +48,9 @@
 
 cd "${HOME}/trunk/src/overlays/overlay-${BASE}/chromeos-base/chromeos-bsp-${BASE}/files/cras-config"
 
+# If there are pending changes, exit the script (unless overridden)
+check_pending_changes "$(pwd)"
+
 # Start a branch. Use YMD timestamp to avoid collisions.
 DATE=$(date +%Y%m%d)
 BRANCH="create_${VARIANT}_${DATE}"
diff --git a/contrib/variant/create_coreboot_config.sh b/contrib/variant/create_coreboot_config.sh
index f2c5333..20fc183 100755
--- a/contrib/variant/create_coreboot_config.sh
+++ b/contrib/variant/create_coreboot_config.sh
@@ -3,7 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-VERSION="2.3.0"
+VERSION="2.4.0"
 SCRIPT=$(basename -- "${0}")
 set -e
 
@@ -27,6 +27,10 @@
 # shellcheck disable=SC1091
 source "${BASH_SOURCE%/*}/check_standalone.sh"
 
+# shellcheck source=check_pending_changes.sh
+# shellcheck disable=SC1091
+source "${BASH_SOURCE%/*}/check_pending_changes.sh"
+
 # This is the name of the base board.
 # ${var,,} converts to all lowercase.
 BASE="${1,,}"
@@ -55,6 +59,9 @@
   exit 1
 fi
 
+# If there are pending changes, exit the script (unless overridden)
+check_pending_changes "$(pwd)"
+
 # Start a branch. Use YMD timestamp to avoid collisions.
 DATE=$(date +%Y%m%d)
 BRANCH="create_${VARIANT}_${DATE}"
diff --git a/contrib/variant/create_coreboot_variant.sh b/contrib/variant/create_coreboot_variant.sh
index e40e169..0a244ad 100755
--- a/contrib/variant/create_coreboot_variant.sh
+++ b/contrib/variant/create_coreboot_variant.sh
@@ -3,7 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-VERSION="4.3.1"
+VERSION="4.4.0"
 SCRIPT="$(basename -- "$0")"
 set -e
 
@@ -40,6 +40,10 @@
 # shellcheck disable=SC1091
 source "${BASH_SOURCE%/*}/check_standalone.sh"
 
+# shellcheck source=check_pending_changes.sh
+# shellcheck disable=SC1091
+source "${BASH_SOURCE%/*}/check_pending_changes.sh"
+
 # This is the name of the base board
 BASE="$(to_lower "$1")"
 # This is the name of the reference board that we're using to make the variant.
@@ -78,6 +82,9 @@
   exit 1
 fi
 
+# If there are pending changes, exit the script (unless overridden)
+check_pending_changes "$(pwd)"
+
 # Start a branch. Use YMD timestamp to avoid collisions.
 DATE="$(date +%Y%m%d)"
 BRANCH="coreboot_${VARIANT}_${DATE}"
diff --git a/contrib/variant/create_initial_ec_image.sh b/contrib/variant/create_initial_ec_image.sh
index 7277b45..c8b946d 100755
--- a/contrib/variant/create_initial_ec_image.sh
+++ b/contrib/variant/create_initial_ec_image.sh
@@ -3,7 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-VERSION="1.3.0"
+VERSION="1.4.0"
 SCRIPT=$(basename -- "${0}")
 set -e
 
@@ -25,6 +25,10 @@
 # shellcheck disable=SC1091
 source "${BASH_SOURCE%/*}/check_standalone.sh"
 
+# shellcheck source=check_pending_changes.sh
+# shellcheck disable=SC1091
+source "${BASH_SOURCE%/*}/check_pending_changes.sh"
+
 # This is the name of the reference board that we copying to make the variant.
 # ${var,,} converts to all lowercase.
 REF="${1,,}"
@@ -52,6 +56,9 @@
   exit 1
 fi
 
+# If there are pending changes, exit the script (unless overridden)
+check_pending_changes "$(pwd)"
+
 # Start a branch. Use YMD timestamp to avoid collisions.
 DATE=$(date +%Y%m%d)
 BRANCH="create_${VARIANT}_${DATE}"
diff --git a/contrib/variant/fw_build_config.sh b/contrib/variant/fw_build_config.sh
index de5e601..7d52242 100755
--- a/contrib/variant/fw_build_config.sh
+++ b/contrib/variant/fw_build_config.sh
@@ -3,7 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-VERSION="1.1.0"
+VERSION="1.2.0"
 SCRIPT=$(basename -- "${0}")
 set -e
 
@@ -32,6 +32,10 @@
 # shellcheck disable=SC1091
 source "${BASH_SOURCE%/*}/check_standalone.sh"
 
+# shellcheck source=check_pending_changes.sh
+# shellcheck disable=SC1091
+source "${BASH_SOURCE%/*}/check_pending_changes.sh"
+
 # This is the name of the base board.
 # ${var,,} converts to all lowercase.
 BASE="${1,,}"
@@ -46,6 +50,9 @@
 # The config.star file will be located here
 cd "${HOME}/trunk/src/project/${BASE}/${VARIANT}"
 
+# If there are pending changes, exit the script (unless overridden)
+check_pending_changes "$(pwd)"
+
 # Start a branch. Use YMD timestamp to avoid collisions.
 DATE=$(date +%Y%m%d)
 BRANCH="create_${VARIANT}_${DATE}"
diff --git a/contrib/variant/testdata/README.md b/contrib/variant/testdata/README.md
index 9652903..0e889ee 100644
--- a/contrib/variant/testdata/README.md
+++ b/contrib/variant/testdata/README.md
@@ -198,3 +198,42 @@
 When the branches are abandoned as part of clean-up, your repo will go back
 to `m/master`, so you will need to manually `git checkout` the branch where
 you were making changes.
+
+
+Testing pending changes detection
+=================================
+The automation scripts will exit with an error if any pending changes are
+detected. "Pending changes" means any uncommitted changes, or any commits
+that are ahead of upstream.
+
+To test detection of uncommitted changes, first pick a tracked file in any
+of the repositories (listed below) where new_variant.py will run scripts and
+make a benign modification (e.g. add a comment). Do not commit the change.
+
+All boards:
+* third_party/coreboot
+* third_party/chromiumos-overlay
+* platform/ec
+* overlays
+
+Board-specific:
+* private-overlays/overlay-hatch-private
+* private-overlays/baseboard-hatch-private
+* private-overlays/baseboard-puff-private
+* private-overlays/baseboard-volteer-private
+* private-overlays/baseboard-dedede-private
+
+Now run the end-to-end test. The test should report that there are pending
+changes and exit.
+
+To test detection of commits ahead of upstream, revisit any of the
+aforementioned repositories. In the repository, run `repo start pending .`,
+create a new throwaway file, `git add` it, and `git commit`. `git status`
+should report
+```
+Your branch is ahead of 'cros/main' by 1 commit.
+  (use "git push" to publish your local commits)
+```
+
+Now run the end-to-end test. The test should report that there are pending
+changes and exit.