Skip to content

Commit

Permalink
chore: update scripts folder
Browse files Browse the repository at this point in the history
  • Loading branch information
dafnamatsry committed Jul 16, 2024
1 parent 7bd5ea7 commit 12a991a
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 0 deletions.
13 changes: 13 additions & 0 deletions scripts/generate_changelog.sh
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]+"
36 changes: 36 additions & 0 deletions scripts/install_build_tools.sh
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
156 changes: 156 additions & 0 deletions scripts/merge_branches.py
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)
4 changes: 4 additions & 0 deletions scripts/merge_paths.json
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"
}
25 changes: 25 additions & 0 deletions scripts/merge_paths_test.py
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}'."
62 changes: 62 additions & 0 deletions scripts/merge_status.py
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()
6 changes: 6 additions & 0 deletions scripts/taplo.sh
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

0 comments on commit 12a991a

Please sign in to comment.