github-merge-pr: remove newline on removing SoB from committer
[maintainer-tools.git] / github-merge-pr.sh
1 #!/bin/bash
2
3 # How to setup the repository to make this correctly work
4 # 1. Clone repo and make sure you can correctly push to that
5 # (example with openwrt main repo you need to use ssh remote url)
6 # 2. Make sure you can correctly push and force push to the github
7 # repository
8 #
9 # Usage: github-merge-pr.sh PR_NUMBER BRANCH REPO_NAME
10 #
11 # BRANCH is optional and defaults to main.
12 # REPO_NAME is optional and defaults to openwrt. It does
13 # describe the repository name to use to pull PR info from.
14
15 # Script .config variables
16 # Github repository owner or organization name.
17 # By default set to openwrt, set this line in .config to overwrite
18 # the default value.
19 GITHUB_REPO_OWNER="openwrt"
20
21 # Your repository token, generate this token at your profile page:
22 # - Navigate to https://github.com/settings/tokens
23 # - Click on "Generate new token"
24 # - Enter a description, e.g. "pr.sh" and pick the "repo" scope
25 # - Hit "Generate token"
26 # Set this line in .config to provide a GITHUB_TOKEN and add comments
27 #GITHUB_TOKEN="d41d8cd98f00b204e9800998ecf8427e"
28 #
29 # Set this line in .config to use SSH key to rebase PR branch
30 # GITHUB_USE_SSH=1
31
32 # Everything in .config will overwrite the default values set up
33 [ -f .config ] && source .config
34
35 PRID="$1"
36 BRANCH="${2:-main}"
37 GITHUB_REPO_NAME="${3:-openwrt}"
38 DRY_RUN="$4"
39
40 GIT=git
41 REPO="$GITHUB_REPO_OWNER"/"$GITHUB_REPO_NAME"
42
43 yesno() {
44 local prompt="$1"
45 local default="${2:-n}"
46 local input
47
48 while [ 1 ]; do
49 printf "%s y/n [%s] > " "$prompt" "$default"
50 read input
51 case "${input:-$default}" in
52 y*) return 0 ;;
53 n*) return 1 ;;
54 esac
55 done
56 }
57
58 if ! command -v jq &> /dev/null; then
59 echo "jq could not be found! This script require jq!"
60 exit 1
61 fi
62
63 if [ -z "$PRID" -o -n "${PRID//[0-9]*/}" ]; then
64 echo "Usage: $0 <PR-ID> [rebase-branch] [dry-run]" >&2
65 exit 1
66 fi
67
68 if [ -n "$DRY_RUN" ]; then
69 GIT="echo git"
70 fi
71
72 if [ -z "$(git branch --list "$BRANCH")" ]; then
73 echo "Given rebase branch '$BRANCH' does not exist!" >&2
74 exit 2
75 fi
76
77 if ! PR_INFO="$(curl -f -s "https://api.github.com/repos/$REPO/pulls/$PRID")"; then
78 echo "Failed fetch PR #$PRID info" >&2
79 exit 3
80 fi
81
82 if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "false" ]; then
83 echo "PR #$PRID can't be force pushed by maintainers. Trying to merge as is!" >&2
84 fi
85
86 if [ "$(echo "$PR_INFO" | jq -r ".mergeable")" == "false" ]; then
87 echo "PR #$PRID is not mergeable for Github.com. Check the PR!" >&2
88 exit 5
89 fi
90
91 echo "Pulling current $BRANCH from origin"
92 $GIT checkout $BRANCH
93 $GIT fetch origin $BRANCH
94
95 if ! $GIT rebase origin/$BRANCH; then
96 echo "Failed to rebase $BRANCH with origin/$BRANCH" >&2
97 exit 7
98 fi
99
100 PR_USER="$(echo "$PR_INFO" | jq -r ".head.user.login")"
101 PR_BRANCH="$(echo "$PR_INFO" | jq -r ".head.ref")"
102 LOCAL_PR_BRANCH="$PR_BRANCH"-"$PR_USER"
103 if [ "$GITHUB_USE_SSH" = "1" ]; then
104 PR_REPO="$(echo "$PR_INFO" | jq -r ".head.repo.ssh_url")"
105 else
106 PR_REPO="$(echo "$PR_INFO" | jq -r ".head.repo.html_url")"
107 fi
108
109 if ! $GIT remote get-url $PR_USER &> /dev/null || [ -n "$DRY_RUN" ]; then
110 echo "Adding $PR_USER with repo $PR_REPO to remote"
111 $GIT remote add $PR_USER $PR_REPO
112 fi
113
114 echo "Fetching remote $PR_USER"
115 $GIT fetch $PR_USER $PR_BRANCH
116
117 if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "true" ]; then
118 echo "Creating branch $LOCAL_PR_BRANCH for $PR_BRANCH"
119 if ! $GIT checkout -b $LOCAL_PR_BRANCH $PR_USER/$PR_BRANCH; then
120 echo "Failed to checkout new branch $PR_BRANCH from $PR_USER/$PR_BRANCH" >&2
121 exit 8
122 fi
123
124 echo "Rebasing $LOCAL_PR_BRANCH on top of $BRANCH"
125 if ! $GIT rebase origin/$BRANCH; then
126 echo "Failed to rebase $PR_BRANCH with origin/$BRANCH" >&2
127 exit 9
128 fi
129
130 # Add to each commit Link: https://github.com/$REPO/pull/$PRID
131 if ! $GIT filter-repo --message-callback "
132 return message + b\"Link: https://github.com/$REPO/pull/$PRID\"
133 " --refs $BRANCH..$LOCAL_PR_BRANCH --force; then
134 echo "Failed to add Link: Pull Request tag" >&2
135 exit 9
136 fi
137
138 # Remove any previous SoB tag if the Pull Request Author and Committer match
139 if ! $GIT filter-repo --message-callback "
140 return message.replace(b\"Signed-off-by: $(git config user.name) <$(git config user.email)>\n\",b\"\")
141 " --refs $BRANCH..$LOCAL_PR_BRANCH --force; then
142 echo "Failed to remove previous Committer SoB tag" >&2
143 exit 9
144 fi
145
146 # Add Committer SoB to better reference who merged the thing last
147 if ! $GIT rebase origin/$BRANCH $LOCAL_PR_BRANCH --exec "git commit -s --amend -C HEAD"; then
148 echo "Failed to add Committer SoB tag" >&2
149 exit 9
150 fi
151
152 echo "Force pushing $LOCAL_PR_BRANCH to HEAD:$PR_BRANCH for $PR_USER"
153 if ! $GIT push $PR_USER HEAD:$PR_BRANCH --force; then
154 echo "Failed to force push HEAD to $PR_USER" >&2
155 exit 10
156 fi
157
158 echo "Returning to $BRANCH"
159 $GIT checkout $BRANCH
160 fi
161
162 if [ -n "$($GIT log origin/$BRANCH..HEAD)" ]; then
163 echo "Working on dirty branch for $BRANCH! Please reset $BRANCH to origin/$BRANCH" >&2
164 exit 11
165 fi
166
167 echo "Actually merging the PR #$PRID from branch $PR_USER/$PR_BRANCH"
168 if ! $GIT merge --ff-only $PR_USER/$PR_BRANCH; then
169 echo "Failed to merge $PR_USER/$PR_BRANCH on $BRANCH" >&2
170 else
171 if yesno "Push to openwrt $BRANCH" "y"; then
172 echo "Pushing to openwrt git server"
173 if ! $GIT push; then
174 echo "Failed to push to $BRANCH but left branch as is." >&2
175 exit 12
176 fi
177
178 # Default close comment
179 COMMENT="Thanks! Rebased on top of $BRANCH and merged!"
180
181 if [ -n "$GITHUB_TOKEN" ] && [ -z "$DRY_RUN" ]; then
182 echo ""
183 echo "Enter a comment and hit <enter> to close the PR at Github automatically now."
184 echo "Hit <ctrl>-<c> to exit."
185 echo ""
186 echo "If you do not provide a comment, the default will be: "
187 echo "[$COMMENT]"
188
189 echo -n "Comment > "
190 read usercomment
191
192 echo "Sending message to PR..."
193
194 comment="${usercomment:-$COMMENT}"
195 comment="${comment//\\/\\\\}"
196 comment="${comment//\"/\\\"}"
197 comment="$(printf '{"body":"%s"}' "$comment")"
198
199 if ! curl -s -o /dev/null -w "%{http_code} %{url_effective}\\n" --user "$GITHUB_TOKEN:x-oauth-basic" --request POST --data "$comment" "https://api.github.com/repos/$REPO/issues/$PRID/comments" || \
200 ! curl -s -o /dev/null -w "%{http_code} %{url_effective}\\n" --user "$GITHUB_TOKEN:x-oauth-basic" --request PATCH --data '{"state":"closed"}' "https://api.github.com/repos/$REPO/pulls/$PRID"
201 then
202 echo "" >&2
203 echo "Something failed while sending comment to the PR via ">&2
204 echo "the Github API, please review the state manually at " >&2
205 echo "https://github.com/$REPO/pull/$PRID" >&2
206 exit 6
207 fi
208 fi
209
210 echo -e "\n"
211 echo "The PR has been merged!"
212 echo -e "\n"
213 fi
214 fi
215
216 if [ "$(echo "$PR_INFO" | jq -r ".maintainer_can_modify")" == "true" ]; then
217 echo "Deleting branch $LOCAL_PR_BRANCH"
218 $GIT branch -D $LOCAL_PR_BRANCH
219 fi
220
221 exit 0