Action to fixup Copybara failed merges (#4744)
Extend the submodule synchronization action to fix various issues with
Copybara-exported PRs. It rewrites commits with the Copybara tag to
mark them as merged from the specified commit.
When this is rolled out, any commit pushed that contains the Copybara
tag COPYBARA_INTEGRATE_REVIEW, which indicates that Copybara did not
successfully perform an integrate (because it didn't remove this tag)
will be rewritten to merge from the specified commit and update the
submodules from SUBMODULE_VERSIONS.txt.
Tested:
Ran this on my fork. Pushed
https://github.com/GMNGeoffrey/iree/commit/81d31db7b8f4


which triggered
https://github.com/GMNGeoffrey/iree/runs/1836274764 which created
https://github.com/GMNGeoffrey/iree/commit/462987384b70


diff --git a/.github/workflows/copybara_fixup.yml b/.github/workflows/copybara_fixup.yml
new file mode 100644
index 0000000..1d2d680
--- /dev/null
+++ b/.github/workflows/copybara_fixup.yml
@@ -0,0 +1,79 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Edits the HEAD commit if it is tagged with the Copybara tag to use the
+# submodule state specified in SUBMODULE_VERSIONS.txt and merge from the
+# specified commit.
+# WARNING: rewrites history!
+
+name: Fixup Copybara
+
+on:
+ push:
+ branches:
+ # Do not add this to human branches like main. It rewrites history!
+ - google
+
+env:
+ COPYBARA_TAG: "COPYBARA_INTEGRATE_REVIEW"
+ UPSTREAM_REMOTE: origin
+
+jobs:
+ fixup:
+ runs-on: ubuntu-18.04
+ steps:
+ - name: Checking out repository
+ uses: actions/checkout@v2
+ with:
+ token: ${{ secrets.GITHUB_WRITE_ACCESS_TOKEN }}
+ # Get all history. We're force-pushing here and will otherwise drop
+ # all the branch history. This takes a whopping 2 seconds. I think
+ # we'll live.
+ fetch-depth: 0
+ - name: Setting git config
+ run: |
+ git config --local user.email "iree-github-actions-bot@google.com"
+ git config --local user.name "Copybara Fixup Action"
+ - name: Running fixer script
+ run: |
+ ./scripts/git/fix_copybara_export.sh
+ - name: Checking for a change
+ run: |
+ echo "commit_amended=false" >> $GITHUB_ENV
+ if [[ "$(git rev-parse HEAD)" != "${GITHUB_SHA?}" ]]; then
+ echo "commit_amended=true" >> $GITHUB_ENV
+ fi
+ - name: Pushing changes
+ if: env.commit_amended == 'true'
+ run: git push -f origin ${{ github.ref }}
+
+ check_submodules:
+ runs-on: ubuntu-18.04
+ steps:
+ - name: Checking out repository
+ uses: actions/checkout@v2
+ - name: Checking submodules
+ run: ./scripts/git/submodule_versions.py check
+
+ check_copybara_tag:
+ runs-on: ubuntu-18.04
+ steps:
+ - name: Checking out repository
+ uses: actions/checkout@v2
+ - name: Checking for Copybara tag
+ run: |
+ if git log --format=%B -n 1 HEAD | grep -q "${COPYBARA_TAG}"; then
+ echo "Commit contains Copybara tag"
+ exit 1
+ fi
diff --git a/.github/workflows/synchronize_submodules.yml b/.github/workflows/synchronize_submodules.yml
deleted file mode 100644
index c93a918..0000000
--- a/.github/workflows/synchronize_submodules.yml
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2019 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Edits the HEAD commit to use the submodule state specified in SUBMODULE_VERSIONS.txt
-# WARNING: rewrites history!
-
-name: Synchronize Submodules
-
-on:
- push:
- branches:
- # Do not add this to human branches like main. It rewrites history!
- - google
-
-jobs:
- synchronize:
- runs-on: ubuntu-18.04
- steps:
- - name: Checking out repository
- uses: actions/checkout@v2
- with:
- token: ${{ secrets.GITHUB_WRITE_ACCESS_TOKEN }}
- # Get all history. We're force-pushing here and will otherwise drop
- # all the branch history. This takes a whopping 2 seconds. I think
- # we'll live.
- fetch-depth: 0
- - name: Importing submodules from SUBMODULE_VERSIONS.txt
- run: ./scripts/git/submodule_versions.py import
- - name: Checking submodule state
- run: |
- echo "has_diff=false" >> $GITHUB_ENV
- git diff --cached --exit-code || echo "has_diff=true" >> $GITHUB_ENV
- - name: Committing updates
- if: env.has_diff == 'true'
- run: |
- git config --local user.email "iree-github-actions-bot@google.com"
- git config --local user.name "Submodule Synchronize Action"
- git commit --amend -a --no-edit
- - name: Pushing changes
- if: env.has_diff == 'true'
- run: git push -f origin ${{ github.ref }}
-
- check:
- runs-on: ubuntu-18.04
- steps:
- - name: Checking out repository
- uses: actions/checkout@v2
- - name: Checking submodules
- run: ./scripts/git/submodule_versions.py check
diff --git a/scripts/git/fix_copybara_merge.sh b/scripts/git/fix_copybara_export.sh
similarity index 61%
rename from scripts/git/fix_copybara_merge.sh
rename to scripts/git/fix_copybara_export.sh
index 21e4fad..e6f11cb 100755
--- a/scripts/git/fix_copybara_merge.sh
+++ b/scripts/git/fix_copybara_export.sh
@@ -28,9 +28,20 @@
set -e
-COPYBARA_TAG="COPYBARA_INTEGRATE_REVIEW"
+COPYBARA_TAG="${COPYBARA_TAG:-COPYBARA_INTEGRATE_REVIEW}"
UPSTREAM_REMOTE="${UPSTREAM_REMOTE:-upstream}"
+# Get the commit message of the HEAD commit
+MESSAGE="$(git log --format=%B -n 1 HEAD)"
+# We want to preserve the original commit author. git commit-tree uses these env
+# variables to determine the author to use (falling back to the git config). It
+# does not have any command line flags for these. See
+# https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables#_committing
+export GIT_AUTHOR_NAME="$(git log --format=%an -n 1 HEAD)"
+export GIT_AUTHOR_EMAIL="$(git log --format=%ae -n 1 HEAD)"
+
+################################ Safety checks #################################
+
if [[ -n "$(git status --porcelain)" ]]; then
echo -e "\n\nWorking directory not clean. Aborting"
git status
@@ -46,58 +57,77 @@
exit 1
fi
+# We don't want to be rewriting commits that have already hit the main branch.
+# Obviously this is not foolproof because there can be a race, but still not a
+# bad check to have.
+git fetch "${UPSTREAM_REMOTE?}" main:main
+if git merge-base --is-ancestor HEAD main; then
+ echo -e "\n\nHEAD commit is already on main branch. Aborting"
+ exit 1
+fi
+
if [[ -n "$(git rev-list --merges HEAD^..HEAD)" ]]; then
- echo -e "\n\nHEAD commit is already a merge commit. Aborting"
- git log -n 1 HEAD
+ echo -e "\n\nHEAD commit is already a merge commit. Aborting."
exit 1
fi
-# Get the commit message of the HEAD commit
-MESSAGE="$(git log --format=%B -n 1 HEAD)"
+################################################################################
-# Extract the commit to merge from using the Copybara tag.
-MERGE_FROM="$(echo "${MESSAGE?}" | awk -v pat="${COPYBARA_TAG?}" '$0~pat{print $NF}')"
-
-if [[ -z "${MERGE_FROM?}" ]]; then
- echo -e "\n\nHEAD commit is not tagged with ${COPYBARA_TAG?}. Aborting"
- git log -n 1 HEAD
- exit 1
-fi
-
-echo "To revert the changes made by this script, run:"
+echo -e "\n\nTo revert the changes made by this script, run:"
echo "git reset --hard $(git rev-parse HEAD)"
-# And create a new message with the tag removed
+# Fix submodules
+./scripts/git/submodule_versions.py import
+
+if ! git diff --cached --exit-code; then
+ echo -e "\n\nUpdating commit with fixed submodules"
+ git commit --amend -a --no-edit
+fi
+
+if ! echo "${MESSAGE?}" | grep -q "${COPYBARA_TAG}"; then
+ echo -e "\n\nHEAD commit does not contain Copybara tag '${COPYBARA_TAG?}'."
+ git log -n 1 HEAD
+ exit 0
+fi
+
+COPYBARA_LINE="$(echo "${MESSAGE?}" | grep "${COPYBARA_TAG?}")"
+
+# Extract the commit to merge from using the Copybara tag.
+MERGE_FROM="$(echo "${COPYBARA_LINE?}" | awk '{print $NF}')"
+
+if [[ -z "${MERGE_FROM?}" ]]; then
+ echo -e "\n\nFailed extracting commit to merge from. Aborting"
+ exit 1
+fi
+
+# Create a new message with the tag removed
NEW_MESSAGE="$(echo "${MESSAGE?}" | sed "/${COPYBARA_TAG?}/d")"
# Make sure we actually have the commit we need to merge from.
echo -e "\n\nFetching ${MERGE_FROM?}"
git fetch "${UPSTREAM_REMOTE?}" "${MERGE_FROM?}"
-echo -e "\n\nMerging from ${MERGE_FROM?}:"
+echo -e "\n\nIdentified ${MERGE_FROM?} as commit to merge from:"
git log -n 1 "${MERGE_FROM?}"
-if ! git merge-base --is-ancestor "${MERGE_FROM?}" main; then
- echo -e "\n\nCommit to merge from is not on main branch. Aborting"
- exit 1
-fi
-
# Add a tag to the commit to merge from so it is highlighted in the git log. If
# someone knows how to just highlight an individual commit with git log, that
# would be preferable.
git tag "merge-from-${MERGE_FROM?}" "${MERGE_FROM?}"
-echo -e "\n\nCurrent git graph:"
+echo -e "\n\nCurrent git log graph:"
git log --left-right --graph --oneline --boundary "HEAD...main"
# Create a new commit object `git commit-tree` based on the tree of the current
# HEAD commit with the parent of the HEAD commit as first parent and the commit
# to merge from as the second. Use the new message as the commit message. Reset
-# the current branch to this commit.
-# See https://stackoverflow.com/q/48560351
+# the current branch to this commit. See https://stackoverflow.com/q/48560351
git reset --soft "$(git commit-tree -m "${NEW_MESSAGE?}" -p HEAD^ -p ${MERGE_FROM?} HEAD^{tree})"
-echo -e "\n\nCreated fake merge. New git graph:"
+echo -e "\n\nCreated fake merge. New commit:"
+git log -n1 HEAD
+
+echo -e "\n\nNew git log graph:"
git log --left-right --graph --oneline --boundary "HEAD...main"
# Delete the tag we created