summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorThibault Saunier <tsaunier@igalia.com>2021-09-25 21:03:06 -0300
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>2021-09-28 15:50:27 +0000
commit6d2f033dbbd7fe4086ba287b9dc7fdfdb7b4b193 (patch)
treeab5e0ae76898b666fc5a58a70bc7f4e458467971 /scripts
parent66da54964eb254d1782f62b82d4d995ad3dbc6a8 (diff)
downloadgstreamer-6d2f033dbbd7fe4086ba287b9dc7fdfdb7b4b193.tar.gz
scripts: Add a script to rebase branches from old modules into monorepo
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/919>
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/rebase-branch-from-old-module.py220
1 files changed, 220 insertions, 0 deletions
diff --git a/scripts/rebase-branch-from-old-module.py b/scripts/rebase-branch-from-old-module.py
new file mode 100755
index 0000000000..2e0dbc73be
--- /dev/null
+++ b/scripts/rebase-branch-from-old-module.py
@@ -0,0 +1,220 @@
+#!/usr/bin/env python3
+
+from pathlib import Path as P
+from urllib.parse import urlparse
+from contextlib import contextmanager
+import os
+import re
+import sys
+
+import argparse
+import requests
+
+import subprocess
+
+import random
+import string
+
+URL = "https://gitlab.freedesktop.org/"
+PARSER = argparse.ArgumentParser(
+ description="`Rebase` a branch from an old GStreamer module onto the monorepo"
+)
+PARSER.add_argument("repo", help="The repo with the old module to use.")
+PARSER.add_argument("branch", help="The branch to rebase.")
+
+log_depth = [] # type: T.List[str]
+
+@contextmanager
+def nested(name=''):
+ global log_depth
+ log_depth.append(name)
+ try:
+ yield
+ finally:
+ log_depth.pop()
+
+def bold(text: str):
+ return f"\033[1m{text}\033[0m"
+
+def green(text: str):
+ return f"\033[1;32m{text}\033[0m"
+
+def red(text: str):
+ return f"\033[1;31m{text}\033[0m"
+
+def yellow(text: str):
+ return f"\033[1;33m{text}\033[0m"
+
+def fprint(msg, nested=True):
+ if log_depth:
+ prepend = log_depth[-1] + ' | ' if nested else ''
+ else:
+ prepend = ''
+
+ print(prepend + msg, end="")
+ sys.stdout.flush()
+
+
+class GstCherryPicker:
+ def __init__(self):
+
+ self.branch = None
+ self.repo = None
+ self.module = None
+
+ self.git_rename_limit = None
+
+ def check_clean(self):
+ try:
+ out = self.git("status", "--porcelain")
+ if out:
+ fprint("\n" + red('Git repository is not clean:') + "\n```\n" + out + "\n```\n")
+ sys.exit(1)
+
+ except Exception as e:
+ sys.exit(
+ f"Git repository is not clean. Clean it up before running ({e})")
+
+ def run(self):
+ assert self.branch
+ assert self.repo
+ self.check_clean()
+
+ try:
+ git_rename_limit = int(self.git("config", "merge.renameLimit"))
+ except subprocess.CalledProcessError:
+ git_rename_limit = 0
+ if int(git_rename_limit) < 999999:
+ self.git_rename_limit = git_rename_limit
+ fprint("-> Setting git rename limit to 999999 so we can properly cherry-pick between repos")
+ self.git("config", "merge.renameLimit", "999999")
+ fprint(f"{green(' OK')}\n", nested=False)
+
+ try:
+ self.rebase()
+ finally:
+ if self.git_rename_limit is not None:
+ self.git("config", "merge.renameLimit", str(self.git_rename_limit))
+
+ def rebase(self):
+ repo = urlparse(self.repo)
+
+ repo_path = P(repo.path)
+ self.module = module = repo_path.stem
+ remote_name = f"{module}-{repo_path.parent.name}"
+ fprint('Adding remotes...')
+ self.git("remote", "add", remote_name, self.repo, can_fail=True)
+ self.git("remote", "add", module, f"{URL}gstreamer/{module}.git",
+ can_fail=True)
+ fprint(f"{green(' OK')}\n", nested=False)
+
+ fprint(f'Fetching {remote_name}...')
+ self.git("fetch", remote_name,
+ interaction_message=f"fetching {remote_name} with:\n"
+ f" `$ git fetch {remote_name}`")
+ fprint(f"{green(' OK')}\n", nested=False)
+
+ fprint(f'Fetching {module}...')
+ self.git("fetch", module,
+ interaction_message=f"fetching {module} with:\n"
+ f" `$ git fetch {module}`")
+ fprint(f"{green(' OK')}\n", nested=False)
+
+ prevbranch = self.git("rev-parse", "--abbrev-ref", "HEAD").strip()
+ tmpbranchname = f"{remote_name}_{self.branch}"
+ fprint(f'Checking out branch {remote_name}/{self.branch} as {tmpbranchname}\n')
+ try:
+ self.git("checkout", f"{remote_name}/{self.branch}", "-b", tmpbranchname)
+ self.git("rebase", f"{module}/master",
+ interaction_message=f"Failed rebasing {remote_name}/{self.branch} on {module}/master with:\n"
+ f" `$ git rebase {module}/master`")
+ self.cherry_pick(tmpbranchname)
+ except:
+ self.git("rebase", "--abort", can_fail=True)
+ self.git("checkout", prevbranch)
+ self.git("branch", "-D", tmpbranchname)
+ raise
+ fprint(f"{green(' OK')}\n", nested=False)
+
+ def cherry_pick(self, branch):
+ shas = self.git('log', '--format=format:%H', f'{self.module}/master..').strip()
+ fprint(f'Resetting on origin/main')
+ self.git("reset", "--hard", "origin/main")
+ fprint(f"{green(' OK')}\n", nested=False)
+
+ for sha in reversed(shas.split()):
+ fprint(f' - Cherry picking: {bold(sha)}\n')
+ self.git("cherry-pick", sha,
+ interaction_message=f"cherry-picking {sha} onto {branch} with:\n "
+ f" `$ git cherry-pick {sha}`"
+ )
+
+
+ def git(self, *args, can_fail=False, interaction_message=None, call=False):
+ retry = True
+ while retry:
+ retry = False
+ try:
+ if not call:
+ try:
+ return subprocess.check_output(["git"] + list(args),
+ stdin=subprocess.DEVNULL,
+ stderr=subprocess.STDOUT).decode()
+ except:
+ if not can_fail:
+ fprint(f"\n\n{bold(red('ERROR'))}: `git {' '.join(args)}` failed" + "\n", nested=False)
+ raise
+ else:
+ subprocess.call(["git"] + list(args))
+ return "All good"
+ except Exception as e:
+ if interaction_message:
+ output = getattr(e, "output", b"")
+ if output is not None:
+ out = output.decode()
+ else:
+ out = "????"
+ fprint(f"\n```"
+ f"\n{out}\n"
+ f"Entering a shell to fix:\n\n"
+ f" {bold(interaction_message)}\n\n"
+ f"You should then exit with the following codes:\n\n"
+ f" - {bold('`exit 0`')}: once you have fixed the problem and we can keep moving the \n"
+ f" - {bold('`exit 1`')}: {bold('retry')}: once you have let the repo in a state where cherry-picking the commit should be to retried\n"
+ f" - {bold('`exit 3`')}: stop the script and abandon moving your MRs\n"
+ "\n```\n", nested=False)
+ try:
+ if os.name == 'nt':
+ shell = os.environ.get(
+ "COMSPEC", r"C:\WINDOWS\system32\cmd.exe")
+ else:
+ shell = os.environ.get(
+ "SHELL", os.path.realpath("/bin/sh"))
+ subprocess.check_call(shell)
+ except subprocess.CalledProcessError as e:
+ if e.returncode == 1:
+ retry = True
+ continue
+ elif e.returncode == 3:
+ sys.exit(3)
+ except:
+ # Result of subshell does not really matter
+ pass
+
+ return "User fixed it"
+
+ if can_fail:
+ return "Failed but we do not care"
+
+ raise e
+
+
+def main():
+ picker = GstCherryPicker()
+ PARSER.parse_args(namespace=picker)
+ picker.run()
+
+
+if __name__ == '__main__':
+ main()
+