mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 23:21:59 +00:00
simple backport script
This commit is contained in:
parent
1d843df1f3
commit
29bb9f6665
@ -52,22 +52,56 @@ $ cat 20.1-report.tsv | cut -f1 | sort | uniq -c | sort -rn
|
||||
10 no-backport
|
||||
```
|
||||
|
||||
|
||||
### Как разметить пулреквест?
|
||||
По умолчанию бекпортируются все пулреквесты, у которых в описании указана категория чейнжлога Bug fix. Если этого недостаточно, используйте теги:
|
||||
* v20.1-backported -- этот пулреквест уже бекпортирован в ветку 20.1. На случай, если автоматически не определилось.
|
||||
По умолчанию бекпортируются все пулреквесты, у которых в описании указана
|
||||
категория чейнжлога Bug fix. Если этого недостаточно, используйте теги:
|
||||
* v20.1-no-backport -- в ветку 20.1 бекпортировать не нужно.
|
||||
* pr-no-backport -- ни в какие ветки бекпортировать не нужно.
|
||||
* v20.1-conflicts -- при бекпорте в 20.1 произошёл конфликт. Такие пулреквесты скрипт пропускает, к ним можно потом вернуться.
|
||||
* v20.1-conflicts -- при бекпорте в 20.1 произошёл конфликт. Такие пулреквесты
|
||||
скрипт пропускает, к ним можно потом вернуться.
|
||||
* pr-must-backport -- нужно бекпортировать в поддерживаемые ветки.
|
||||
* v20.1-must-backport -- нужно бекпортировать в 20.1.
|
||||
|
||||
### Я бекпортировал, почему скрипт не видит?
|
||||
* Сообщение коммита должно содержать текст backport/cherry-pick #12345, или
|
||||
иметь вид стандартного гитхабовского мерж-коммита для ПР #12345.
|
||||
* Коммит должен быть достижим по `git log --first-parent my-branch`. Возможно,
|
||||
в ветке сделали pull с merge, от чего некоторые коммиты из ветки становятся
|
||||
недоступны по `--first-parent`.
|
||||
|
||||
В качестве обхода, добавьте в ветку пустой коммит с текстом вроде "backport
|
||||
#12345 -- real backport commit is <sha>".
|
||||
|
||||
### Я поправил пулреквест, почему скрипт не видит?
|
||||
В процессе работы скрипт кеширует данные о пулреквестах в текущей папке, чтобы экономить квоту гитхаба. Удалите закешированные файлы, например, для всех реквестов, которые не помечены как пропущенные:
|
||||
В процессе работы скрипт кеширует данные о пулреквестах в текущей папке, чтобы
|
||||
экономить квоту гитхаба. Удалите закешированные файлы, например, для всех
|
||||
реквестов, которые не помечены как пропущенные:
|
||||
```
|
||||
$ cat <ваша-ветка>-report.tsv | grep -v "^skip" | cut -f4
|
||||
$ cat <ваша-ветка>-report.tsv | grep -v "^skip" | cut -f4 | xargs rm
|
||||
```
|
||||
|
||||
## Как сформировать change log
|
||||
В этой же папке запустите:
|
||||
```
|
||||
$ time GITHUB_TOKEN=... ./changelog.sh v20.3.4.10-stable v20.3.5.21-stable
|
||||
9 PRs added between v20.3.4.10-stable and v20.3.5.21-stable.
|
||||
### ClickHouse release v20.3.5.21-stable FIXME as compared to v20.3.4.10-stable
|
||||
|
||||
#### Bug Fix
|
||||
|
||||
* Fix 'Different expressions with the same alias' error when query has PREWHERE
|
||||
and WHERE on distributed table and `SET distributed_product_mode = 'local'`.
|
||||
[#9871](https://github.com/ClickHouse/ClickHouse/pull/9871) ([Artem
|
||||
Zuikov](https://github.com/4ertus2)).
|
||||
...
|
||||
```
|
||||
|
||||
Скрипт выведет changelog на экран, а также сохранит его в `./changelog.md`.
|
||||
Скопируйте этот текст в большой changelog, проверьте и поправьте версию и дату
|
||||
релиза, вычитайте сообщения. Если сообщения неправильные, обязательно исправьте
|
||||
их на гитхабе -- это поможет при последующей генерации changelog для других
|
||||
версий, содержащих этот пулреквест. Чтобы скрипт подтянул изменения с гитхаба,
|
||||
удалите соответствующие файлы `./pr12345.json`. Если вы часто видите
|
||||
неправильно оформленные пулреквесты, это повод подумать об улучшении проверки
|
||||
Description check в CI.
|
||||
|
@ -10,10 +10,13 @@ merge_base=$(git merge-base origin/master "origin/$branch")
|
||||
git log "$merge_base..origin/master" --first-parent > master-log.txt
|
||||
git log "$merge_base..origin/$branch" --first-parent > "$branch-log.txt"
|
||||
|
||||
# NOTE keep in sync with ./changelog.sh.
|
||||
# Search for PR numbers in commit messages. First variant is normal merge, and second
|
||||
# variant is squashed.
|
||||
# variant is squashed. Next are some backport message variants.
|
||||
find_prs=(sed -n "s/^.*Merge pull request #\([[:digit:]]\+\).*$/\1/p;
|
||||
s/^.*(#\([[:digit:]]\+\))$/\1/p")
|
||||
s/^.*(#\([[:digit:]]\+\))$/\1/p;
|
||||
s/^.*back[- ]*port[ ]*#\([[:digit:]]\+\).*$/\1/Ip;
|
||||
s/^.*cherry[- ]*pick[ ]*#\([[:digit:]]\+\).*$/\1/Ip")
|
||||
|
||||
"${find_prs[@]}" master-log.txt | sort -rn > master-prs.txt
|
||||
"${find_prs[@]}" "$branch-log.txt" | sort -rn > "$branch-prs.txt"
|
||||
@ -39,7 +42,7 @@ do
|
||||
rm "$file"
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
sleep 0.1
|
||||
fi
|
||||
|
||||
if ! [ "$pr" == "$(jq -r .number "$file")" ]
|
||||
@ -61,7 +64,12 @@ do
|
||||
if echo "$labels" | grep -x "pr-must-backport\|v$branch-must-backport" > /dev/null; then action="backport"; fi
|
||||
if echo "$labels" | grep -x "v$branch-conflicts" > /dev/null; then action="conflict"; fi
|
||||
if echo "$labels" | grep -x "pr-no-backport\|v$branch-no-backport" > /dev/null; then action="no-backport"; fi
|
||||
if echo "$labels" | grep -x "v$branch\|v$branch-backported" > /dev/null; then action="done"; fi
|
||||
# FIXME Ignore "backported" labels for now. If we can't find the backport commit,
|
||||
# this means that the changelog script also won't be able to. An alternative
|
||||
# way to mark PR as backported is to add an empty commit with text like
|
||||
# "backported #12345", so that it can be found between tags and put in proper
|
||||
# place in changelog.
|
||||
#if echo "$labels" | grep -x "v$branch\|v$branch-backported" > /dev/null; then action="done"; fi
|
||||
|
||||
# Find merge commit SHA for convenience
|
||||
merge_sha="$(jq -r .merge_commit_sha "$file")"
|
||||
|
69
utils/simple-backport/changelog.sh
Executable file
69
utils/simple-backport/changelog.sh
Executable file
@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
from="$1"
|
||||
to="$2"
|
||||
|
||||
git log "$from..$to" --first-parent > "changelog-log.txt"
|
||||
|
||||
# NOTE keep in sync with ./backport.sh.
|
||||
# Search for PR numbers in commit messages. First variant is normal merge, and second
|
||||
# variant is squashed. Next are some backport message variants.
|
||||
find_prs=(sed -n "s/^.*Merge pull request #\([[:digit:]]\+\).*$/\1/p;
|
||||
s/^.*(#\([[:digit:]]\+\))$/\1/p;
|
||||
s/^.*back[- ]*port[ ]*#\([[:digit:]]\+\).*$/\1/Ip;
|
||||
s/^.*cherry[- ]*pick[ ]*#\([[:digit:]]\+\).*$/\1/Ip")
|
||||
|
||||
"${find_prs[@]}" "changelog-log.txt" | sort -rn > "changelog-prs.txt"
|
||||
|
||||
|
||||
echo "$(wc -l < "changelog-prs.txt") PRs added between $from and $to."
|
||||
|
||||
function github_download()
|
||||
{
|
||||
local url=${1}
|
||||
local file=${2}
|
||||
if ! [ -f "$file" ]
|
||||
then
|
||||
if ! curl -H "Authorization: token $GITHUB_TOKEN" \
|
||||
-sSf "$url" \
|
||||
> "$file"
|
||||
then
|
||||
>&2 echo "Failed to download '$url' to '$file'. Contents: '"
|
||||
>&2 cat "$file"
|
||||
>&2 echo "'."
|
||||
rm "$file"
|
||||
return 1
|
||||
fi
|
||||
sleep 0.1
|
||||
fi
|
||||
}
|
||||
|
||||
for pr in $(cat "changelog-prs.txt")
|
||||
do
|
||||
# Download PR info from github.
|
||||
file="pr$pr.json"
|
||||
github_download "https://api.github.com/repos/ClickHouse/ClickHouse/pulls/$pr" "$file" || continue
|
||||
|
||||
if ! [ "$pr" == "$(jq -r .number "$file")" ]
|
||||
then
|
||||
>&2 echo "Got wrong data for PR #$pr (please check and remove '$file')."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Download author info from github.
|
||||
user_id=$(jq -r .user.id "$file")
|
||||
user_file="user$user_id.json"
|
||||
github_download "$(jq -r .user.url "$file")" "$user_file" || continue
|
||||
|
||||
if ! [ "$user_id" == "$(jq -r .id "$user_file")" ]
|
||||
then
|
||||
>&2 echo "Got wrong data for user #$user_id (please check and remove '$user_file')."
|
||||
continue
|
||||
fi
|
||||
done
|
||||
|
||||
echo "### ClickHouse release $to FIXME as compared to $from
|
||||
" > changelog.md
|
||||
./format-changelog.py changelog-prs.txt >> changelog.md
|
||||
cat changelog.md
|
109
utils/simple-backport/format-changelog.py
Executable file
109
utils/simple-backport/format-changelog.py
Executable file
@ -0,0 +1,109 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import itertools
|
||||
import argparse
|
||||
import json
|
||||
import collections
|
||||
import re
|
||||
|
||||
parser = argparse.ArgumentParser(description='Format changelog for given PRs.')
|
||||
parser.add_argument('file', metavar='FILE', type=argparse.FileType('r', encoding='utf-8'), nargs=1, default=sys.stdin, help='File with PR numbers, one per line.')
|
||||
args = parser.parse_args()
|
||||
|
||||
# This function mirrors the PR description checks in ClickhousePullRequestTrigger.
|
||||
# Returns False if the PR should not be mentioned changelog.
|
||||
def parse_one_pull_request(item):
|
||||
description = item['body']
|
||||
# Don't skip empty lines because they delimit parts of description
|
||||
lines = [line for line in map(lambda x: x.strip(), description.split('\n') if description else [])]
|
||||
lines = [re.sub(r'\s+', ' ', l) for l in lines]
|
||||
|
||||
category = ''
|
||||
entry = ''
|
||||
|
||||
if lines:
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
if re.match(r'(?i).*category.*:$', lines[i]):
|
||||
i += 1
|
||||
if i >= len(lines):
|
||||
break
|
||||
category = re.sub(r'^[-*\s]*', '', lines[i])
|
||||
i += 1
|
||||
elif re.match(r'(?i)^\**\s*(Short description|Change\s*log entry)', lines[i]):
|
||||
i += 1
|
||||
# Can have one empty line between header and the entry itself. Filter it out.
|
||||
if i < len(lines) and not lines[i]:
|
||||
i += 1
|
||||
# All following lines until empty one are the changelog entry.
|
||||
entry_lines = []
|
||||
while i < len(lines) and lines[i]:
|
||||
entry_lines.append(lines[i])
|
||||
i += 1
|
||||
entry = ' '.join(entry_lines)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if not category:
|
||||
# Shouldn't happen, because description check in CI should catch such PRs.
|
||||
# Fall through, so that it shows up in output and the user can fix it.
|
||||
category = "NO CL CATEGORY"
|
||||
|
||||
# Filter out the PR categories that are not for changelog.
|
||||
if re.match(r'(?i)doc|((non|in|not|un)[-\s]*significant)', category):
|
||||
return False
|
||||
|
||||
if not entry:
|
||||
# Shouldn't happen, because description check in CI should catch such PRs.
|
||||
category = "NO CL ENTRY"
|
||||
entry = "NO CL ENTRY: '" + item['title'] + "'"
|
||||
|
||||
entry = entry.strip()
|
||||
if entry[-1] != '.':
|
||||
entry += '.'
|
||||
|
||||
item['entry'] = entry
|
||||
item['category'] = category
|
||||
|
||||
return True
|
||||
|
||||
|
||||
category_to_pr = collections.defaultdict(lambda: [])
|
||||
users = {}
|
||||
for line in args.file[0]:
|
||||
pr = json.loads(open(f'pr{line.strip()}.json').read())
|
||||
assert(pr['number'])
|
||||
if not parse_one_pull_request(pr):
|
||||
continue
|
||||
|
||||
assert(pr['category'])
|
||||
category_to_pr[pr['category']].append(pr)
|
||||
user_id = pr['user']['id']
|
||||
users[user_id] = json.loads(open(f'user{user_id}.json').read())
|
||||
|
||||
def print_category(category):
|
||||
print("#### " + category)
|
||||
print()
|
||||
for pr in category_to_pr[category]:
|
||||
user = users[pr["user"]["id"]]
|
||||
user_name = user["name"] if user["name"] else user["login"]
|
||||
|
||||
# Substitute issue links
|
||||
pr["entry"] = re.sub(r'#([0-9]{4,})', r'[#\1](https://github.com/ClickHouse/ClickHouse/issues/\1)', pr["entry"])
|
||||
|
||||
print(f'* {pr["entry"]} [#{pr["number"]}]({pr["html_url"]}) ([{user_name}]({user["html_url"]})).')
|
||||
|
||||
print()
|
||||
|
||||
# Print categories in preferred order
|
||||
categories_preferred_order = ['Backward Incompatible Change', 'New Feature', 'Bug Fix', 'Improvement', 'Performance Improvement', 'Build/Testing/Packaging Improvement', 'Other']
|
||||
for category in categories_preferred_order:
|
||||
if category in category_to_pr:
|
||||
print_category(category)
|
||||
category_to_pr.pop(category)
|
||||
|
||||
# Print the rest of the categories
|
||||
for category in category_to_pr:
|
||||
print_category(category)
|
Loading…
Reference in New Issue
Block a user