-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7bd5ea7
commit 12a991a
Showing
7 changed files
with
302 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#!/bin/bash | ||
|
||
set -e | ||
|
||
# Usage: | ||
# scripts/generate_changelog.sh <FROM_TAG> <TO_TAG> | ||
|
||
# Install git-cliff if missing. | ||
GIT_CLIFF_VERSION="2.4.0" | ||
cargo install --list | grep -q "git-cliff v${GIT_CLIFF_VERSION}" || cargo install git-cliff@${GIT_CLIFF_VERSION} | ||
|
||
# Combine dev tags into the next RC / stable tag. | ||
git-cliff $1..$2 -o changelog_$1_$2.md --ignore-tags ".*-dev.[0-9]+" |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#!/bin/env bash | ||
|
||
set -e | ||
|
||
function install_pypy() { | ||
pushd /opt | ||
$USE_SUDO bash -c ' | ||
curl -Lo pypy3.9-v7.3.11-linux64.tar.bz2 https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2 | ||
tar -xf pypy3.9-v7.3.11-linux64.tar.bz2 | ||
rm pypy3.9-v7.3.11-linux64.tar.bz2 | ||
chmod +x pypy3.9-v7.3.11-linux64/bin/pypy3 | ||
if [ -L /usr/local/bin/pypy3.9 ]; then | ||
unlink /usr/local/bin/pypy3.9 | ||
fi | ||
ln -s /opt/pypy3.9-v7.3.11-linux64/bin/pypy3 /usr/local/bin/pypy3.9 | ||
if [ -L /opt/pypy3.9 ]; then | ||
unlink /opt/pypy3.9 | ||
fi | ||
ln -s /opt/pypy3.9-v7.3.11-linux64 /opt/pypy3.9 | ||
pypy3.9 -m ensurepip | ||
pypy3.9 -m pip install wheel | ||
' | ||
popd | ||
} | ||
|
||
function install_rust () { | ||
curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path | ||
} | ||
|
||
install_pypy & | ||
install_rust & | ||
wait |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
#!/usr/bin/env python3.9 | ||
|
||
""" | ||
Merge a branch into another branch. Example usage: | ||
``` | ||
scripts/merge_branches.py --src main-v0.13.0 | ||
``` | ||
""" | ||
|
||
import argparse | ||
import json | ||
import os | ||
import subprocess | ||
from typing import Dict, List, Optional | ||
|
||
FINAL_BRANCH = "main" | ||
MERGE_PATHS_FILE = "scripts/merge_paths.json" | ||
|
||
|
||
def load_merge_paths() -> Dict[str, str]: | ||
return json.load(open(MERGE_PATHS_FILE)) | ||
|
||
|
||
def run_command(command: str, allow_error: bool = False) -> List[str]: | ||
""" | ||
Runs a bash command and returns the output as a list of lines. | ||
""" | ||
try: | ||
command_output = ( | ||
subprocess.check_output(command, shell=True, cwd=os.getcwd()) | ||
.decode("utf-8") | ||
.splitlines() | ||
) | ||
output_lines = "\n".join(command_output) | ||
print(f"Command '{command}' output:\n{output_lines}") | ||
return command_output | ||
except subprocess.CalledProcessError as error: | ||
if not allow_error: | ||
raise | ||
print(f"Command '{command}' hit error: {error=}.") | ||
return str(error).splitlines() | ||
|
||
|
||
def get_dst_branch(src_branch: str, dst_branch_override: Optional[str]) -> str: | ||
if dst_branch_override is not None: | ||
return dst_branch_override | ||
assert ( | ||
src_branch.replace("origin/", "") != FINAL_BRANCH | ||
), f"{FINAL_BRANCH} has no default destination branch." | ||
|
||
return load_merge_paths()[src_branch] | ||
|
||
|
||
def srcdiff(source_branch: str, destination_branch: Optional[str], files: List[str]): | ||
destination_branch = get_dst_branch( | ||
src_branch=source_branch, dst_branch_override=destination_branch | ||
) | ||
files_line = " ".join(files) | ||
run_command( | ||
f"git diff $(git merge-base origin/{source_branch} origin/{destination_branch}) " | ||
f"origin/{source_branch} {files_line}" | ||
) | ||
|
||
|
||
def dstdiff(source_branch: str, destination_branch: Optional[str], files: List[str]): | ||
destination_branch = get_dst_branch( | ||
src_branch=source_branch, dst_branch_override=destination_branch | ||
) | ||
files_line = " ".join(files) | ||
run_command( | ||
f"git diff $(git merge-base origin/{source_branch} origin/{destination_branch}) " | ||
f"origin/{destination_branch} {files_line}" | ||
) | ||
|
||
|
||
def merge_branches(src_branch: str, dst_branch: Optional[str]): | ||
""" | ||
Merge source branch into destination branch. | ||
If no destination branch is passed, the destination branch is taken from state on repo. | ||
""" | ||
user = os.environ["USER"] | ||
dst_branch = get_dst_branch(src_branch=src_branch, dst_branch_override=dst_branch) | ||
|
||
merge_branch = f"{user}/merge-{src_branch}-into-{dst_branch}" | ||
print(f"Source branch: {src_branch}") | ||
print(f"Destination branch: {dst_branch}\n") | ||
|
||
run_command("git fetch") | ||
run_command(f"git checkout origin/{dst_branch}") | ||
run_command(f"git checkout -b {merge_branch}") | ||
print("Merging...") | ||
run_command("git config merge.conflictstyle diff3") | ||
|
||
run_command(f"git merge origin/{src_branch}", allow_error=True) | ||
|
||
run_command("git config --unset merge.conflictstyle") | ||
run_command("git status -s | grep \"^UU\" | awk '{ print $2 }' | tee /tmp/conflicts") | ||
|
||
conflicts_file = "/tmp/conflicts" | ||
conflicts = [line.strip() for line in open(conflicts_file).readlines() if line.strip() != ""] | ||
conflict_line = " ".join(conflicts) | ||
run_command(f"git add {conflict_line}", allow_error=True) | ||
run_command("git add changed_files/*", allow_error=True) | ||
print("Committing conflicts...") | ||
if len(conflicts) == 0: | ||
run_command( | ||
f'git commit --allow-empty -m "No conflicts in {src_branch} -> {dst_branch} merge, ' | ||
'this commit is for any change needed to pass the CI."' | ||
) | ||
else: | ||
run_command( | ||
f'git commit -m "chore: merge branch {src_branch} into {dst_branch} (with conflicts)"' | ||
) | ||
|
||
print("Pushing...") | ||
run_command(f"git push --set-upstream origin {merge_branch}") | ||
(merge_base,) = run_command(f"git merge-base origin/{src_branch} origin/{dst_branch}") | ||
|
||
print("Creating PR...") | ||
run_command( | ||
f'gh pr create --base {dst_branch} --title "Merge {src_branch} into {dst_branch}" ' | ||
'--body ""' | ||
) | ||
|
||
if len(conflicts) != 0: | ||
compare = "https://github.com/starkware-libs/blockifier/compare" | ||
comment_file_path = "/tmp/comment.XXXXXX" | ||
with open(comment_file_path, "w") as comment_file: | ||
for conflict in conflicts: | ||
(filename_hash,) = run_command(f"echo -n {conflict} | sha256sum | cut -d' ' -f1") | ||
comment_file.write( | ||
f"[Src]({compare}/{merge_base}..{src_branch}#diff-{filename_hash}) " | ||
f"[Dst]({compare}/{merge_base}..{dst_branch}#diff-{filename_hash}) " | ||
f"{conflict}\n" | ||
) | ||
run_command(f"gh pr comment -F {comment_file_path}") | ||
os.remove(comment_file_path) | ||
|
||
os.remove(conflicts_file) | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser(description="Merge a branch into another branch.") | ||
parser.add_argument("--src", type=str, help="The source branch to merge.") | ||
parser.add_argument( | ||
"--dst", | ||
type=str, | ||
default=None, | ||
help=( | ||
"The destination branch to merge into. If no branch explicitly provided, uses the " | ||
f"destination branch registered for the source branch in {MERGE_PATHS_FILE}." | ||
), | ||
) | ||
args = parser.parse_args() | ||
|
||
merge_branches(src_branch=args.src, dst_branch=args.dst) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"main-v0.13.1": "main-v0.13.2", | ||
"main-v0.13.2": "main" | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from merge_branches import FINAL_BRANCH, MERGE_PATHS_FILE, load_merge_paths | ||
|
||
|
||
def test_linear_path(): | ||
merge_paths = load_merge_paths() | ||
|
||
src_dst_iter = iter(merge_paths.items()) | ||
(oldest_branch, prev_dst_branch) = next(src_dst_iter) | ||
assert ( | ||
oldest_branch not in merge_paths.values() | ||
), f"Oldest branch '{oldest_branch}' cannot be a destination branch." | ||
|
||
for src_branch, dst_branch in src_dst_iter: | ||
assert ( | ||
prev_dst_branch == src_branch | ||
), ( | ||
f"Since the merge graph is linear, the source branch '{src_branch}' must be the same " | ||
f"as the previous destination branch, which is '{prev_dst_branch}'. Check out " | ||
f"{MERGE_PATHS_FILE}." | ||
) | ||
prev_dst_branch = dst_branch | ||
|
||
assert ( | ||
prev_dst_branch == FINAL_BRANCH | ||
), f"The last destination is '{prev_dst_branch}' but must be '{FINAL_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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#!/usr/bin/env python3.9 | ||
|
||
from enum import Enum | ||
import json | ||
import os | ||
import subprocess | ||
from datetime import datetime | ||
|
||
from merge_branches import MERGE_PATHS_FILE | ||
|
||
|
||
class BashColor(Enum): | ||
red = "31" | ||
green = "32" | ||
yellow = "33" | ||
blue = "34" | ||
white = "97" | ||
cyan = "96" | ||
|
||
|
||
def color_txt(color: BashColor, txt: str, bold: bool = True) -> str: | ||
bold_str = "1;" if bold else "" | ||
color_code = color.value | ||
lines = txt.splitlines() | ||
return "\n".join(f"\033[{bold_str}{color_code}m{line}\033[0m" for line in lines) | ||
|
||
|
||
def print_merge_status(): | ||
merge_paths = json.load(open(MERGE_PATHS_FILE)) | ||
for branch, merge_into in merge_paths.items(): | ||
# Get the list of timestamps of unmerged commits. | ||
unmerged_commits_timestamps = list( | ||
map( | ||
int, | ||
subprocess.check_output( | ||
[ | ||
"git", | ||
"log", | ||
f"origin/{merge_into}..origin/{branch}", | ||
"--format=format:%ct", | ||
] | ||
) | ||
.decode("utf8") | ||
.strip() | ||
.splitlines(), | ||
) | ||
) | ||
|
||
if len(unmerged_commits_timestamps) == 0: | ||
status = color_txt(BashColor.green, "Up to date") | ||
else: | ||
last_unmerged_commit_time = datetime.fromtimestamp(min(unmerged_commits_timestamps)) | ||
unmerged_days = (datetime.now() - last_unmerged_commit_time).days | ||
status = f"{unmerged_days} days" | ||
if unmerged_days > 7: | ||
status = color_txt(BashColor.red, status) | ||
|
||
print(f"{branch}-->{merge_into}".ljust(40), status) | ||
|
||
|
||
if __name__ == "__main__": | ||
print_merge_status() |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/bash | ||
|
||
# To auto-fix formatting install taplo (`cargo install taplo-cli`) and run `taplo format`. | ||
# Formatting options can be configured in the root `taplo.toml` file. | ||
|
||
taplo format --check --diff 1>&2 |