Created
May 9, 2018 06:16
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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