Skip to content

Instantly share code, notes, and snippets.

@axiac
Created May 9, 2018 06:16
Show Gist options
  • Save axiac/3a772c0d20a9f2e77b2165df0ed71e48 to your computer and use it in GitHub Desktop.
Save axiac/3a772c0d20a9f2e77b2165df0ed71e48 to your computer and use it in GitHub Desktop.
One-time script designed to "transplant" the history of PhpUnit MockObjects project into the PhpUnit repo as a branch
#!/bin/bash
#
# One-time script specifically designed to "transplant" a part of
# the PhpUnit MockObjects repo into the PhpUnit repo as a branch.
# @see https://github.com/sebastianbergmann/phpunit/issues/3103
#
#
# Define some constants
# The PHPUnit commit to start the MockObjects branch
BRANCH_BASE=377f99ab9a0527cbb41a95c742572632a0357537
# The first MockObjects commit to import
MOCKOBJECTS_FIRST=ab3c85afe5fa355174b9e05c5fbd7f470150b786
# Temporary files are created here
TEMP_DIR=/tmp/phpunit-mockobjects-merge-$$
HASH_LIST=$TEMP_DIR/hashlist.txt
FILTER_SCRIPT=$TEMP_DIR/filter-script.sh
#
# Do not allow running the script in a non-empty directory
if [ -n "$(ls -A)" ]; then
echo "The following files or directories exist in the current directory:"
ls -A -1 -F | sed 's/^/ /'
echo "Cannot continue."
echo "Please run this script in an empty directory."
exit 1
fi
#
# Make sure we cleanup on abnormal exit (and on normal exit too)
trap "rm -rf $TEMP_DIR" EXIT
#
# Create a temporary directory to store some files
mkdir -p $TEMP_DIR
#
# Clone PHPUnit
echo "##### Cloning PHPUnit here ######"
git clone git@github.com:sebastianbergmann/phpunit.git .
# Add MockObjects as a remote
echo "##### Fetching master from MockObjects ######"
git remote add mo git@github.com:sebastianbergmann/phpunit-mock-objects.git
# Fetch from MockObjects
git fetch mo master
#
# Record the information about the commits to be rebased
echo "##### Saving the information of the commits to be rebased #####"
OLD_IFS=IFS
IFS=$'\n'
# Tree hash, author time, author name, author email, committer time, committer name, committer email, parent commits
for line in $(git log --pretty="%T:%at:%an:%ae:%ct:%cn:%ce:%P" $MOCKOBJECTS_FIRST~1..mo/master); do
IFS=':'
read -r tree GIT_AUTHOR_DATE GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_DATE GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL parents <<< "$line"
IFS=' '
# Replace hashes of the parent commits with the hashes of their trees
p_trees=$(for parent in $parents; do
git log -n 1 --pretty=%T $parent
done)
IFS=$'\n'
echo ${line/$parents/$p_trees} | tee -a $HASH_LIST
done
IFS=OLD_IFS
#
# Create the MockObjects branch into the PHPUnit repo
echo "##### Create the MockObjects branch into the PHPUnit repo #####"
git checkout -b mo-stub $BRANCH_BASE
# Create the first commit on this branch identical to commit $MOCKOBJECTS_FIRST
echo "##### Transplant the first commit of MockObjects onto the new branch #####"
# Remove everything...
git rm -r -- .gitignore *
# ... but PHPUnit/Framework/MockObject
git reset HEAD PHPUnit/Framework/MockObject
git checkout -- PHPUnit/Framework/MockObject
# Commit and copy the details (committer, author, dates, commit message) of the target commit
date=$(git log -n 1 --pretty=%ct $MOCKOBJECTS_FIRST)
name=$(git log -n 1 --pretty=%cn $MOCKOBJECTS_FIRST)
email=$(git log -n 1 --pretty=%ce $MOCKOBJECTS_FIRST)
GIT_COMMITTER_DATE=$date GIT_COMMITTER_NAME=$name GIT_COMMITTER_EMAIL=$email git commit -C $MOCKOBJECTS_FIRST
# Quick check we are on the right track
if [ "$(git log -n 1 --format=%T:%at:%an:%ae:%ct:%cn:%ce:%s)" = "$(git log -n 1 --format=%T:%at:%an:%ae:%ct:%cn:%ce:%s $MOCKOBJECTS_FIRST)" ]; then
echo "##### The new commit looks good. Will continuei. #####"
else
echo
echo "Something wrong happened. Cannot continue."
exit 1
fi
#
# Rebase the MockObjects commits $MOCKOBJECTS_FIRST..master onto the new branch, preserve the merges
echo "##### Rebasing the rest of the MockObjects commits onto the new branch. #####"
git branch mockobjects mo/master
git rebase --preserve-merges --onto mo-stub $MOCKOBJECTS_FIRST mockobjects
# Rebasing fails on merge commits that had conflicts in the original repo.
OK=1
until [ $OK = 0 ]; do
# Resolve the conflict by checking out the files directly from the commit that failed to be rebased.
git reset REBASE_HEAD -- .
git checkout -- .
# Commit to conclude the rebase of the current commit
git commit --no-edit
# Resume the rebase. It stops again on the next conflict.
git rebase --continue
OK=$?
done
# Remove a leftover file that was deleted on a branch and modified on the other branch
# and Git dutifully kept the local version of the file when it rebased commit 488753cd0a25cd49fd3f827715a7412d88f3a3ab
rm package-composer.json
# Verify the outcome
if [ "$(git log -n 1 --format="%T %s")" = "$(git log -n 1 --format="%T %s" mo/master)" ]; then
echo "##### Everything seems to be ok after rebase. Will continue. #####"
else
echo
echo "Something wrong happened. Some commits are not identical with the original."
echo "Cannot continue."
exit 1
fi
#
# Restore the original committer and author information
echo "##### Create the script to fix the committer and author information. #####"
cat > $FILTER_SCRIPT << 'END_SCRIPT'
#!/bin/bash
HASH_LIST=$1
tree="$(git log -n 1 --pretty=%T $GIT_COMMIT)"
parents="$(git log -n 1 --pretty=%P $GIT_COMMIT)"
p_trees=$(for parent in $parents; do
git log -n 1 --pretty=%T $parent
done)
p_trees=${p_trees//$'\n'/ }
#info="$(grep ^$tree "$HASH_LIST" | grep -m 1 -E "$p_trees$")"
info=$(grep ^$tree "$HASH_LIST" | grep -E "$p_trees$")
if [ $(echo "$info" | wc -l) -ne 1 ]; then
echo "grep ^$tree $HASH_LIST | grep -E $p_trees$" >> /tmp/errors.txt
echo "$info" >> /tmp/errors.txt
echo "================================" >> /tmp/errors.txt
fi
OLD_IFS=$IFS
IFS=":"
read tree GIT_AUTHOR_DATE GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_DATE GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL p_trees <<< "$info"
IFS=$OLD_IFS
END_SCRIPT
chmod +x $FILTER_SCRIPT
echo "##### Fix the committer and author information. #####"
git filter-branch --env-filter "source $FILTER_SCRIPT $TEMP_DIR/hashlist.txt" mo-stub..mockobjects
#
# Cleanup
echo "##### Cleanup #####"
git branch -d mo-stub
git remote remove mo
git gc
echo "##### Completed. #####"
echo
cat << 'END'
##### How to prepare MockObjects to be merged back into PhpUnit
#####
##### Step 1: Reorganize MockObjects
#####
git checkout mockobjects
##### Remove all the files from the root directory; they define the package
##### but we're about to remove this property from MockObjects
##### You'll probably need to move ChangeLog.md and pieces from phpunit.xml
##### into the corresponding files of PhpUnit later
git rm -r .gitattributes .github .gitignore .php_cs.dist .travis.yml CONTRIBUTING.md ChangeLog.md LICENSE README.md build.xml composer.json phpunit.xml
##### Move the source and test files where they will stay in PhpUnit
git mv src MockObject
mkdir -p src/Framework
git mv MockObject src/Framework/
git mv tests MockObject
mkdir -p tests/Framework
git mv MockObject tests/Framework/
git mv tests/Framework/MockObject/_fixture tests/_files
##### Commit step 1/3 of the preparations
git commit -m "Prepare merging MockObjects back into PhpUnit (1/3)" -m "* remove package files;"$'\n'"* move source and test files to their final location;"
#####
##### Step 2: Merge PhpUnit into MockObjects and resolve the conflicts
#####
git merge master
##### You will get a lot of conflicts here because the PhpUnit files were modified
##### in master and deleted in mockobjects and vice-versa.
##### They can be easily solved by checking out the existing files from each branch
##### (as long as the files of the branches don't overlap).
git checkout master -- .
git checkout mockobjects -- src/Framework/MockObject
git checkout mockobjects -- tests/Framework/MockObject
# tests/_files/Mockable.php is present in both branches; keep the one of MockObjects
git checkout mockobjects -- tests/_files/Mockable.php
git add tests/_files
##### Conflicts are solved now; commit.
git commit -m "Prepare merging MockObjects back into PhpUnit (2/3)" -m "* merge PhpUnit into MockObjects and resolve the conflicts using the existing files of each branch;"
#####
##### Step 3: Adjust PhpUnit to know about the new location of MockObjects files
#####
##### Human intervention is required here to load MockObjects from its new location and let the tests succeed.
##### You probably need to do small changes in other files too (ChangeLog.md f.e.)
##### Change the files as needed, commit, merge the branch back to master when ready.
##### Happy testing!
END
# That's all, folks!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment