Skip to content

Commit

Permalink
WIP 2
Browse files Browse the repository at this point in the history
  • Loading branch information
infinisil committed Apr 5, 2024
1 parent 3de10bd commit a64f132
Showing 1 changed file with 99 additions and 92 deletions.
191 changes: 99 additions & 92 deletions scripts/sync-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,40 @@ tmp=$(mktemp -d)
cd "$tmp"
trap 'rm -rf "$tmp"' exit


step() {
echo -e "\e[34m$1\e[0m"
}

# Checks whether a revision range is linear, returns 1 if it's not
# Usage: isLinear REPO REV_RANGE
# - REPO: The local Git repo that contains the revisions
# - REV_RANGE: The revision range, see `man git log`
isLinear() {
local repo=$1
local revs=$2
# Loops through all merge commits in the range
for _mergeCommit in $(git 2>/dev/null -C "$repo" log --pretty=format:%H --min-parents=2 "$revs"); do
# And returns as soon as the first is found
return 1
done
}



step "Fetching nixfmt pull request and creating a branch for the head commit"
git init nixfmt
git -C nixfmt fetch "$nixfmtUrl" "refs/pull/$nixfmtPrNumber/merge"
nixfmtBaseCommit=$(git -C nixfmt rev-parse FETCH_HEAD^1)
nixfmtHeadCommit=$(git -C nixfmt rev-parse FETCH_HEAD^2)
git -C nixfmt switch -c main "$nixfmtHeadCommit"

# In case the PR contains merge commits, we strip those away, such that the resulting Nixpkgs history is always linear.
step "Linearising nixfmt history after the base commit"
# https://stackoverflow.com/a/17994534
FILTER_BRANCH_SQUELCH_WARNING=1 git -C nixfmt filter-branch --parent-filter 'cut -f 2,3 -d " "' --msg-filter 'echo $GIT_COMMIT' "$nixfmtBaseCommit"..main
FILTER_BRANCH_SQUELCH_WARNING=1 git -C nixfmt filter-branch --parent-filter 'cut -f 2,3 -d " "' "$nixfmtBaseCommit"..main

nixfmtCommitCount=$(git -C nixfmt rev-list --count "$nixfmtBaseCommit"..main)
if (( nixfmtCommitCount == 0 )); then
step "No commits, deleting the nixpkgs branch $nixpkgsMirrorBranch if it exists"
# git push requires a repository to work at all, _any_ repository
# git push requires a repository to work at all, _any_ repository!
git init -q trash
git -C trash push "$nixpkgsUrl" :refs/heads/"$nixpkgsMirrorBranch"
rm -rf trash
Expand All @@ -66,16 +70,17 @@ else
echo "There are $nixfmtCommitCount linearised commits"
fi

commitsToMirror=("$nixfmtBaseCommit")
readarray -t -O 1 commitsToMirror < <(git -C nixfmt rev-list --reverse "$nixfmtBaseCommit"..main)
# All the commits of the PR, including the base commit (which may change as the base branch is updated)
prCommits=("$nixfmtBaseCommit")
readarray -t -O 1 prCommits < <(git -C nixfmt rev-list --reverse "$nixfmtBaseCommit"..main)

# Computes the commit subject of the Nixpkgs commit that contains the formatted changes
# Usage: bodyForCommit INDEX
# Usage: bodyForCommitIndex INDEX
# - INDEX: The index of the nixfmt commit in the PR that is being used
# 0 means the PR's base commit
bodyForCommit() {
bodyForCommitIndex() {
local index=$1
local commit=${commitsToMirror[$index]}
local commit=${prCommits[$index]}
local url=$nixfmtUrl/commit/$commit
local subject
subject=$(git -C nixfmt show -s --format=%s "$commit")
Expand All @@ -93,13 +98,15 @@ step "Fetching upstream Nixpkgs commit history"
git init --bare nixpkgs.git

git -C nixpkgs.git remote add upstream "$nixpkgsUpstreamUrl"
# This makes sure that we don't actually have to fetch any contents, otherwise we'd wait forever!
git -C nixpkgs.git config remote.upstream.promisor true
git -C nixpkgs.git config remote.upstream.partialclonefilter tree:0

git -C nixpkgs.git fetch --no-tags upstream HEAD:master

# Instead of e.g. fetching Nixpkgs master, which would continuously move, we "pin" Nixpkgs to the latest commit before the PRs commit
step "Finding the last Nixpkgs commit before the first commit on nixfmt's branch"
nixfmtFirstCommit=${commitsToMirror[1]}
nixfmtFirstCommit=${prCommits[1]}
# Commit date, not author date, not sure what's better
nixfmtFirstCommitDateEpoch=$(git -C nixfmt log -1 --format=%ct "$nixfmtFirstCommit")
nixfmtFirstCommitDateHuman=$(git -C nixfmt log -1 --format=%ci "$nixfmtFirstCommit")
Expand All @@ -110,49 +117,57 @@ nixpkgsBaseCommitDateHuman=$(git -C nixpkgs.git log -1 --format=%ci "$nixpkgsBas

echo "The last Nixpkgs commit before then is $nixpkgsBaseCommit on $nixpkgsBaseCommitDateHuman, which will be used as the Nixpkgs base commit"

step "Fetching mirror Nixpkgs commit history in branch $nixpkgsMirrorBranch if it exists"
step "Fetching Nixpkgs commit history in branch $nixpkgsMirrorBranch if it exists"
git -C nixpkgs.git remote add mirror "$nixpkgsUrl"
git -C nixpkgs.git config remote.mirror.promisor true
git -C nixpkgs.git config remote.mirror.partialclonefilter tree:0

# After this:
# - $nixpkgsCommitCount should be the number of commits that can be reused
# - $startingCommit should be the nixpkgs commit that the branch should be reset to
# - $extraCommits should be the number of commits already processed
nixpkgsCommitCount=0
startingCommit=$nixpkgsBaseCommit
if ! git -C nixpkgs.git fetch --no-tags mirror "$nixpkgsMirrorBranch":mirrorBranch; then
echo "There is not"
startingCommit=$nixpkgsBaseCommit
extraCommits=0
echo "There is not, likely a new PR"
else
echo "There is, it points to $(git -C nixpkgs.git rev-parse mirrorBranch)"
step "Checking to which extent work from the existing branch can be reused"
if [[ -z "$(git -C nixpkgs.git branch --contains="$nixpkgsBaseCommit" mirrorBranch)" ]]; then
echo "It is not"
startingCommit=$nixpkgsBaseCommit
extraCommits=0
echo "It cannot, the desired base commit is not present at all, likely caused by a rebase"
else
echo "It is!"
echo "Checking if the branch has a linear history"
if ! isLinear nixpkgs.git "$nixpkgsBaseCommit"..mirrorBranch; then
echo "It is not linear, resetting the branch"
startingCommit=$nixpkgsBaseCommit
extraCommits=0
echo "It cannot, the branch is not linear, this is a bug, but not fatal"
else
echo "It is linear!"
nixpkgsCount=$(git -C nixpkgs.git rev-list --count "$nixpkgsBaseCommit"..mirrorBranch)
echo "There's $nixpkgsCount commits in the branch on top of the base commit"
extraCommits=0
# Check if there's at least 1 commits in nixpkgs and at least 0 commits in nixfmt
#echo "It can!"
previousNixpkgsCommitCount=$(git -C nixpkgs.git rev-list --count "$nixpkgsBaseCommit"..mirrorBranch)
echo "There's $previousNixpkgsCommitCount commits in the branch on top of the base commit"
# E.g. if the nixfmt PR has 1 commit, the Nixpkgs PR has two

# Iterate through the Nixpkgs commits and check whether they match the nixfmt ones

# Check if there's at least 1 commit in nixpkgs and at least 0 commits in nixfmt
# Check if commit 1 in nixpkgs corresponds to commit 0 in nixfmt
# If true, increase extraCommits by one, otherwise break
# Check if there's at least 2 commits in nixpkgs and at least 1 commits in nixfmt
# If true, increase nixpkgsCommitCount by one, otherwise break
# Check if there's at least 2 commits in nixpkgs and at least 1 commit in nixfmt
# If so, check if commit 2 in nixpkgs corresponds to commit 1 in nixfmt
# ...
while
if (( nixpkgsCount >= extraCommits + 1 && nixfmtCommitCount >= extraCommits)); then
echo "Checking whether commit with index $(( extraCommits + 1 )) in nixpkgs corresponds to commit with index $extraCommits in nixfmt"
nixpkgsCommit=$(git -C nixpkgs.git rev-parse "mirrorBranch~$((nixpkgsCount - (extraCommits + 1)))")
if (( nixpkgsCommitCount > nixfmtCommitCount )); then
echo "All nixfmt commits are already cached"
false
elif (( nixpkgsCommitCount + 1 > previousNixpkgsCommitCount )); then
echo "Not all nixfmt commits are cached"
false
else
echo "Checking whether commit with index $(( nixpkgsCommitCount + 1 )) in nixpkgs corresponds to commit with index $nixpkgsCommitCount in nixfmt"
# A bit messy, but we kind of go back from the head of the branch via parents.
# This only works correctly because we verified that the branch is linear.
# An alternative would be to read the commits into an array, just like it's done for prCommits
nixpkgsCommit=$(git -C nixpkgs.git rev-parse "mirrorBranch~$((previousNixpkgsCommitCount - (nixpkgsCommitCount + 1)))")

# We generate the bodies of the commits to contain the nixfmt commit so we can check against it here to verify it's the same
body=$(git -C nixpkgs.git log -1 "$nixpkgsCommit" --pretty=%B)
expectedBody=$(bodyForCommit "$extraCommits")
expectedBody=$(bodyForCommitIndex "$nixpkgsCommitCount")
if [[ "$body" == "$expectedBody" ]]; then
echo "It does!"
else
Expand All @@ -162,102 +177,94 @@ else
echo "$expectedBody"
false
fi
else
false
fi
do
extraCommits=$(( extraCommits + 1 ))
nixpkgsCommitCount=$(( nixpkgsCommitCount + 1 ))
startingCommit=$(git -C nixpkgs.git rev-parse "mirrorBranch~$(( previousNixpkgsCommitCount - nixpkgsCommitCount ))")
done

nixpkgsCommit=$(git -C nixpkgs.git rev-parse "mirrorBranch~$(( nixpkgsCount - extraCommits ))")
startingCommit="$nixpkgsCommit"
echo "$nixpkgsCommitCount commits can be reused, starting from $startingCommit"
fi
fi
fi

echo "Starting commit is $startingCommit, extraCommits is $extraCommits"

step "Fetching contents of Nixpkgs base commit $nixpkgsBaseCommit"
git init nixpkgs
git -C nixpkgs fetch --no-tags --depth 1 "$nixpkgsUpstreamUrl" "$nixpkgsBaseCommit":base
git -C nixpkgs config user.name "GitHub Actions"
git -C nixpkgs config user.email "[email protected]"

step "Fetching contents of Nixpkgs base commit $nixpkgsBaseCommit"
# This is needed because for every commit we reset Nixpkgs to the base branch before formatting
git -C nixpkgs fetch --no-tags --depth 1 "$nixpkgsUpstreamUrl" "$nixpkgsBaseCommit"

step "Fetching contents of the starting commit and updating the mirror branch"
if (( extraCommits == 0 )); then
# This is only needed so we can push the resulting diff after formatting
if (( nixpkgsCommitCount == 0 )); then
# No reusable commits, create a new branch, starting commit is the same as the base commit here
git -C nixpkgs switch -c mirrorBranch "$startingCommit"
else
# Reusable commits, fetch the starting one and set a local branch to it
git -C nixpkgs fetch --no-tags --depth 1 "$nixpkgsUrl" "$startingCommit":mirrorBranch
git -C nixpkgs switch mirrorBranch
fi

git -C nixpkgs push --force "$nixpkgsUrl" mirrorBranch:"$nixpkgsMirrorBranch"

if (( extraCommits == 0 )); then
index=0
else
index=$(( extraCommits - 1 ))
fi

if (( index == nixfmtCommitCount )); then
echo "Nothing to do"
if (( nixpkgsCommitCount - 1 == nixfmtCommitCount )); then
echo "Already up-to-date"
exit 0
fi

git -C nixpkgs config user.name "GitHub Actions"
git -C nixpkgs config user.email "[email protected]"
appliedNixfmtPath=

updateToIndex() {
nixfmtCommit=${commitsToMirror[$index]}
# Format Nixpkgs with a specific nixfmt version and push the result.
# Usage: step INDEX
# - INDEX: The nixfmt commit index to format with, 0 is for the PR's base commit
next() {
local index=$1
nixfmtCommit=${prCommits[$index]}

step "Checking out nixfmt at $nixfmtCommit"
git -C nixfmt checkout -q "$nixfmtCommit"

step "Building nixfmt"
#nix build ./nixfmt
nix-build nixfmt -A build
}
nix-build nixfmt

if [[ -n "$appliedNixfmtPath" && "$appliedNixfmtPath" == "$(realpath result)" ]]; then
echo "The nixfmt store path didn't change, saving ourselves a formatting"
else
step "Checking out Nixpkgs at the base commit"
git -C nixpkgs checkout "$nixpkgsBaseCommit" -- .

applyNixfmt() {
step "Checking out Nixpkgs at the base commit"
git -C nixpkgs checkout "$nixpkgsBaseCommit" -- .
step "Running nixfmt on nixpkgs"
if ! xargs -r -0 -P"$(nproc)" -n1 -a <(find nixpkgs -type f -name '*.nix' -print0) result/bin/nixfmt; then
echo -e "\e[31mFailed to run nixfmt on some files\e[0m"
exit 1
fi
git -C nixpkgs add -A

step "Running nixfmt on nixpkgs"
if ! xargs -r -0 -P"$(nproc)" -n1 -a <(find nixpkgs -type f -name '*.nix' -print0) result/bin/nixfmt; then
echo -e "\e[31mFailed to run nixfmt on some files\e[0m"
exit 1
appliedNixfmtPath=$(realpath result)
fi
}

commitResult() {
step "Committing the formatted result"
git -C nixpkgs add -A
git -C nixpkgs commit --allow-empty -m "$(bodyForCommit "$index")"
git -C nixpkgs commit --allow-empty -m "$(bodyForCommitIndex "$index")"

step "Pushing result"
git -C nixpkgs push "$nixpkgsUrl" mirrorBranch:"$nixpkgsMirrorBranch"
nixpkgsCommitCount=$(( nixpkgsCommitCount + 1 ))
}


updateToIndex

appliedNixfmtPath=$(realpath result)

if (( extraCommits == 0 )); then
applyNixfmt
commitResult
if (( nixpkgsCommitCount == 0 )); then
# If we don't have a base-formatted Nixpkgs commit yet, create it
next 0
else
# Otherwise, just build the nixfmt that was used for the current commit, such that we know the store path
update "$(( nixpkgsCommitCount - 1 ))"
appliedNixfmtPath=$(realpath result)
fi

while (( index != nixfmtCommitCount )); do
index=$(( index + 1 ))

updateToIndex

step "Formatting nixpkgs"
if [[ "$appliedNixfmtPath" != "$(realpath result)" ]]; then
applyNixfmt
commitResult
appliedNixfmtPath=$(realpath result)
else
echo "The nixfmt store path didn't change, saving ourselves a formatting"
commitResult
fi
while (( nixpkgsCommitCount - 1 < nixfmtCommitCount )); do
# The number of commits in Nixpkgs is also the index of the nixfmt commit to apply next
# E.g. with 1 Nixpkgs commit (only the base formatted one), we need to run the 1st commit from the nixfmt PR next
next "$nixpkgsCommitCount"
done

0 comments on commit a64f132

Please sign in to comment.