Skip to content

Commit 3cde63c

Browse files
committed
utility script to facilitate cherry-picking
Script which helps to determine the complete list of commits so that a given commit can be cherry-picked without problems.
1 parent 5672afa commit 3cde63c

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/bin/bash
2+
3+
# given a commit cp_commit on branch source_branch as well
4+
# as another branch target_branch, this script finds the list
5+
# of all commits that are needed to succesfully cherry-pick cp_commit
6+
# onto the target branch
7+
8+
cp_commit=$1 # Commit you want to cherry-pick
9+
source_branch=$2 # Branch containing commit cp_commit (e.g., master)
10+
target_branch=$3 # Branch you want to cherry-pick onto (e.g., foo)
11+
12+
# Check if all required arguments are provided
13+
if [ -z "$cp_commit" ] || [ -z "$source_branch" ] || [ -z "$target_branch" ]; then
14+
echo "Usage: $0 <commit-hash-A> <source-branch> <target-branch>"
15+
exit 1
16+
fi
17+
18+
# Function to check if two git commits modify at least one common file
19+
modifies_common_files() {
20+
if [ "$#" -ne 2 ]; then
21+
echo "Usage: check_common_files <commit1> <commit2>"
22+
return 1
23+
fi
24+
25+
local commit1="$1"
26+
local commit2="$2"
27+
28+
# Get the list of modified files for each commit
29+
local files_commit1
30+
files_commit1=$(git diff --name-only "${commit1}^" "${commit1}")
31+
32+
local files_commit2
33+
files_commit2=$(git diff --name-only "${commit2}^" "${commit2}")
34+
35+
# Check for common files
36+
local common_files
37+
common_files=$(echo -e "${files_commit1}\n${files_commit2}" | sort | uniq -d)
38+
39+
# Output result
40+
if [ -n "$common_files" ]; then
41+
return 1
42+
fi
43+
return 0
44+
}
45+
46+
# function to check if 2 commits can be swapped. This can determine if a commit needs
47+
# to come stricly before another commit.
48+
can_swap_commits() {
49+
local commitA="$1"
50+
shift
51+
local commitB=("$@") # this is the list of commits that should swap (as a whole) with commitA
52+
53+
reverseCommitList=()
54+
55+
# Loop through the original array in reverse order
56+
for ((i=${#commitB[@]}-1; i>=0; i--)); do
57+
reverseCommitList+=("${commitB[i]}")
58+
done
59+
60+
# Create a temporary branch for testing
61+
local temp_branch="temp_swap_test_branch"
62+
63+
# record current state
64+
GIT_CUR=$(git branch --show-current)
65+
# Create a new temporary branch from the current HEAD
66+
git checkout ${commitA}^ -b "$temp_branch" &> /dev/null
67+
68+
RC=1
69+
for commit in ${reverseCommitList[@]}; do
70+
# Cherry-pick commit B onto a branch without commitA
71+
if git cherry-pick "$commit" &> /dev/null; then
72+
# RC=1 # Commits can be swapped
73+
RC_local=1
74+
else
75+
RC=0 # Cannot swap due to conflict when cherry-picking B
76+
git cherry-pick --abort
77+
fi
78+
done
79+
80+
# Cleanup: Reset to the original branch and delete the temp branch
81+
git checkout ${GIT_CUR} &> /dev/null
82+
git branch -D "$temp_branch" &>/dev/null
83+
return ${RC}
84+
}
85+
86+
# Step 1: Identify branch-break off point
87+
BRANCHPOINT=$(git merge-base "$source_branch" "$target_branch")
88+
89+
90+
COMMITLIST=()
91+
# Collect the initial set of commits to consider using a while loop
92+
while IFS= read -r line; do
93+
COMMITLIST+=("$line")
94+
done < <(git log ${cp_commit}^...${BRANCHPOINT} --pretty=format:"%H")
95+
96+
# filter out commits not touching the same files
97+
FILTERED_COMMITS1=()
98+
for commit_hash in "${COMMITLIST[@]}"; do
99+
modifies_common_files ${commit_hash} ${cp_commit}
100+
RC=$?
101+
if [ ${RC} -eq 1 ]; then
102+
FILTERED_COMMITS1+=(${commit_hash})
103+
fi
104+
done
105+
106+
# Next, filter out commits which are irrelevant for ${cp_commit}
107+
CP_COMMIT_LIST=(${cp_commit}) # The list of CP=cherry_pick commits to keep/construct
108+
109+
for commit_hash in "${FILTERED_COMMITS1[@]}"; do
110+
if [ ! "${commit_hash}" == "${cp_commit}" ]; then
111+
can_swap_commits "${commit_hash}" "${CP_COMMIT_LIST[@]}"
112+
if [ $? -eq 0 ]; then
113+
# echo "COMMIT ${commit_hash} is needed"
114+
# in this case we need to record it to the list of relevant commits
115+
# and also trace it's dependencies in turn
116+
CP_COMMIT_LIST+=(${commit_hash})
117+
fi
118+
fi
119+
done
120+
121+
# reverse the final list to have correct cherry-pick order
122+
123+
CP_COMMITS_REVERSED=()
124+
for ((i=${#CP_COMMIT_LIST[@]}-1; i>=0; i--)); do
125+
CP_COMMITS_REVERSED+=("${CP_COMMIT_LIST[i]}")
126+
done
127+
128+
# List the commits
129+
echo "To cherry-pick ${cp_commit} onto branch ${target_branch}, we need to apply:"
130+
for ((i=0;i<${#CP_COMMITS_REVERSED[@]}; i++)); do
131+
echo "${i}: ${CP_COMMITS_REVERSED[i]}"
132+
done
133+
134+
exit 0

0 commit comments

Comments
 (0)