This commit is contained in:
Akkariin Meiko
2022-03-12 03:16:09 +08:00
Unverified
parent 12b76e0c7a
commit 27c4ec74a1
10075 changed files with 5122287 additions and 1 deletions
View File
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
from common import git
SCRIPTDIR = os.path.realpath(os.path.dirname(__file__))
if __name__ == "__main__":
subprojects_dir = os.path.join(SCRIPTDIR, "..", "subprojects")
exitcode = 0
for repo_name in os.listdir(subprojects_dir):
repo_dir = os.path.normpath(os.path.join(SCRIPTDIR, subprojects_dir, repo_name))
if not os.path.exists(os.path.join(repo_dir, '.git')):
continue
diff = git('diff', repository_path=repo_dir).strip('\n')
if diff:
print('ERROR: Repository %s is not clean' % repo_dir)
print('NOTE: Make sure to commit necessary changes in the gst_plugins_cache.json files')
print(diff)
exitcode += 1
sys.exit(exitcode)
+159
View File
@@ -0,0 +1,159 @@
import os
import sys
import shlex
import shutil
import argparse
import platform
import subprocess
import uuid
ROOTDIR = os.path.abspath(os.path.dirname(__file__))
if os.name == 'nt':
import ctypes
from ctypes import wintypes
_GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
_GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
_GetShortPathNameW.restype = wintypes.DWORD
def win32_get_short_path_name(long_name):
"""
Gets the short path name of a given long path.
http://stackoverflow.com/a/23598461/200291
"""
output_buf_size = 0
while True:
output_buf = ctypes.create_unicode_buffer(output_buf_size)
needed = _GetShortPathNameW(long_name, output_buf, output_buf_size)
if output_buf_size >= needed:
return output_buf.value
else:
output_buf_size = needed
def get_wine_shortpath(winecmd, wine_paths):
seen = set()
wine_paths += [p for p in wine_paths if not (p in seen or seen.add(p))]
getShortPathScript = '%s.bat' % str(uuid.uuid4()).lower()[:5]
with open(getShortPathScript, mode='w') as f:
f.write("@ECHO OFF\nfor %%x in (%*) do (\n echo|set /p=;%~sx\n)\n")
f.flush()
try:
with open(os.devnull, 'w') as stderr:
wine_path = subprocess.check_output(
winecmd +
['cmd', '/C', getShortPathScript] + wine_paths,
stderr=stderr).decode('utf-8')
except subprocess.CalledProcessError as e:
print("Could not get short paths: %s" % e)
wine_path = ';'.join(wine_paths)
finally:
os.remove(getShortPathScript)
if len(wine_path) > 2048:
raise AssertionError('WINEPATH size {} > 2048'
' this will cause random failure.'.format(
len(wine_path)))
return wine_path
class Colors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
force_disable = False
def _windows_ansi():
from ctypes import windll, byref
from ctypes.wintypes import DWORD
kernel = windll.kernel32
stdout = kernel.GetStdHandle(-11)
mode = DWORD()
if not kernel.GetConsoleMode(stdout, byref(mode)):
return False
# Try setting ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4)
# If that fails (returns 0), we disable colors
return kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON')
@classmethod
def can_enable(cls):
if not os.isatty(sys.stdout.fileno()):
return False
if platform.system().lower() == 'windows':
return cls._windows_ansi()
return os.environ.get('TERM') != 'dumb'
@classmethod
def disable(cls):
cls.HEADER = ''
cls.OKBLUE = ''
cls.OKGREEN = ''
cls.WARNING = ''
cls.FAIL = ''
cls.ENDC = ''
@classmethod
def enable(cls):
if cls.force_disable:
return
cls.HEADER = '\033[95m'
cls.OKBLUE = '\033[94m'
cls.OKGREEN = '\033[92m'
cls.WARNING = '\033[93m'
cls.FAIL = '\033[91m'
cls.ENDC = '\033[0m'
def git(*args, repository_path='.', fatal=True):
try:
ret = subprocess.check_output(["git"] + list(args), cwd=repository_path,
stdin=subprocess.DEVNULL,
stderr=subprocess.STDOUT).decode()
except subprocess.CalledProcessError as e:
if fatal:
raise e
print("Non-fatal error running git {}:\n{}".format(' '.join(args), e))
return None
return ret
def accept_command(commands):
"""Search @commands and returns the first found absolute path."""
for command in commands:
command = shutil.which(command)
if command:
return command
return None
def get_meson():
meson = os.path.join(ROOTDIR, 'meson', 'meson.py')
if os.path.exists(meson):
return [sys.executable, meson]
mesonintrospect = os.environ.get('MESONINTROSPECT', '')
for comp in shlex.split (mesonintrospect):
# mesonintrospect might look like "/usr/bin/python /somewhere/meson introspect",
# let's not get tricked
if 'python' in os.path.basename (comp):
continue
if os.path.exists(comp):
if comp.endswith('.py'):
return [sys.executable, comp]
else:
return [comp]
meson = accept_command(['meson.py'])
if meson:
return [sys.executable, meson]
meson = accept_command(['meson'])
if meson:
return [meson]
raise RuntimeError('Could not find Meson')
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
import argparse
import os
from string import Template
TEMPLATE = Template('''
#include <gst/gst.h>
$elements_declaration
$typefind_funcs_declaration
$device_providers_declaration
$dynamic_types_declaration
$plugins_declaration
void
gst_init_static_plugins (void)
{
static gsize initialization_value = 0;
if (g_once_init_enter (&initialization_value)) {
$elements_registration
$typefind_funcs_registration
$device_providers_registration
$dynamic_types_registration
$plugins_registration
g_once_init_leave (&initialization_value, 1);
}
}
''')
# Retrieve the plugin name as it can be a plugin filename
def get_plugin_name(name):
for p in plugins:
if name in p:
return p
return ''
def process_features(features_list, plugins, feature_prefix):
plugins_list = plugins
feature_declaration = []
feature_registration = []
if features_list is not None:
feature_plugins = features_list.split(';')
for plugin in feature_plugins:
split = plugin.split(':')
plugin_name = split[0].strip()
if len(split) == 2:
if (get_plugin_name(plugin_name)) != '':
plugins_list.remove(get_plugin_name(plugin_name))
features = split[1].split(',')
for feature in features:
feature = feature.replace("-", "_")
feature_declaration += ['%s_REGISTER_DECLARE(%s);' % (feature_prefix, feature)]
feature_registration += ['%s_REGISTER(%s, NULL);' % (feature_prefix, feature)]
return (plugins_list, feature_declaration, feature_registration)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-o', dest="output", help="Output file")
parser.add_argument('-p','--plugins', nargs='?', default='', dest="plugins", help="The list of plugins")
parser.add_argument('-e', '--elements', nargs='?', default='', dest="elements", help="The list of plugin:elements")
parser.add_argument('-t', '--type-finds', nargs='?', default='', dest="typefindfuncs", help="The list of plugin:typefinds")
parser.add_argument('-d', '--devide-providers', nargs='?', default='', dest="deviceproviders", help="The list of plugin:deviceproviders")
parser.add_argument('-T', '--dynamic-types', nargs='?', default='', dest="dynamictypes", help="The list of plugin:dynamictypes")
options = parser.parse_args()
if options.output is None:
output_file = 'gstinitstaticplugins.c'
else:
output_file = options.output
enable_staticelements_plugin = 0;
elements_declaration = []
elements_registration = []
typefind_funcs_declaration = []
typefind_funcs_registration = []
device_providers_declaration = []
device_providers_registration = []
dynamic_types_declaration = []
dynamic_types_registration = []
plugins_declaration = []
plugins_registration = []
if options.plugins is None or options.plugins.isspace():
plugins = []
else:
plugins = options.plugins.split(';')
# process the features
(plugins, elements_declaration, elements_registration) = process_features(options.elements, plugins, 'GST_ELEMENT')
(plugins, typefind_funcs_declaration, typefind_funcs_registration) = process_features(options.typefindfuncs, plugins, 'GST_TYPE_FIND')
(plugins, device_providers_declaration, device_providers_registration) = process_features(options.deviceproviders, plugins, 'GST_DEVICE_PROVIDER')
(plugins, dynamic_types_declaration, dynamic_types_registration) = process_features(options.dynamictypes, plugins, 'GST_DYNAMIC_TYPE')
# Enable plugin or elements according to the ';' separated list.
for plugin in plugins:
split = plugin.split(':')
plugin_name = split[0]
if plugin_name == '':
continue
filename = os.path.basename(plugin)
if filename.startswith('libgst') and filename.endswith('.a'):
plugin_name = filename[len('libgst'):-len('.a')]
plugins_registration += ['GST_PLUGIN_STATIC_REGISTER(%s);' % (plugin_name)]
plugins_declaration += ['GST_PLUGIN_STATIC_DECLARE(%s);' % (plugin_name)]
with open(output_file.strip(), "w") as f:
static_elements_plugin = ''
f.write(TEMPLATE.substitute({
'elements_declaration': '\n'.join(elements_declaration),
'elements_registration': '\n '.join(elements_registration),
'typefind_funcs_declaration': '\n'.join(typefind_funcs_declaration),
'typefind_funcs_registration': '\n '.join(typefind_funcs_registration),
'device_providers_declaration': '\n'.join(device_providers_declaration),
'device_providers_registration': '\n '.join(device_providers_registration),
'dynamic_types_declaration': '\n'.join(dynamic_types_declaration),
'dynamic_types_registration': '\n '.join(dynamic_types_registration),
'plugins_declaration': '\n'.join(plugins_declaration),
'plugins_registration': '\n '.join(plugins_registration),
}))
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env python3
import argparse
import os
import json
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(dest="output", help="Output file")
parser.add_argument(dest="plugins", help="The list of plugins")
options = parser.parse_args()
all_paths = set()
for plugin in options.plugins.split(os.pathsep):
all_paths.add(os.path.dirname(plugin))
with open(options.output, "w") as f:
json.dump(list(all_paths), f, indent=4, sort_keys=True)
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
# Git pre-commit hook that runs multiple hooks specified in $HOOKS.
# Make sure this script is executable. Bypass hooks with git commit --no-verify.
# This file is inspired by a set of unofficial pre-commit hooks available
# at github.
# Link: https://github.com/githubbrowser/Pre-commit-hooks
# Contact: David Martin, david.martin.mailbox@googlemail.com
###########################################################
# SETTINGS:
# pre-commit hooks to be executed. They should be in the same .git/hooks/ folder
# as this script. Hooks should return 0 if successful and nonzero to cancel the
# commit. They are executed in the order in which they are listed.
###########################################################
HOOKS="scripts/git-hooks/pre-commit.hook scripts/git-hooks/pre-commit-python.hook"
# exit on error
set -e
if [ "$GST_DISABLE_PRE_COMMIT_HOOKS" = "1" ]
then
echo "Pre-commits hooks disabled by env GST_DISABLE_PRE_COMMIT_HOOKS."
exit 0
fi
echo $PWD
for hook in $HOOKS
do
echo "Running hook: $hook"
# run hook if it exists
# if it returns with nonzero exit with 1 and thus abort the commit
if [ -f "$PWD/$hook" ]; then
"$PWD/$hook"
if [ $? != 0 ]; then
exit 1
fi
else
echo "Error: file $hook not found."
echo "Aborting commit. Make sure the hook is at $PWD/$hook and executable."
echo "You can disable it by removing it from the list"
echo "You can skip all pre-commit hooks with --no-verify (not recommended)."
exit 1
fi
done
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
import tempfile
NOT_PYCODESTYLE_COMPLIANT_MESSAGE_PRE = \
"Your code is not fully pycodestyle compliant and contains"\
" the following coding style issues:\n\n"
NOT_PYCODESTYLE_COMPLIANT_MESSAGE_POST = \
"Please fix these errors and commit again, you can do so "\
"from the root directory automatically like this, assuming the whole "\
"file is to be commited:"
NO_PYCODESTYLE_MESSAGE = \
"You should install the pycodestyle style checker to be able"\
" to commit in this repo.\nIt allows us to garantee that "\
"anything that is commited respects the pycodestyle coding style "\
"standard.\nYou can install it:\n"\
" * on ubuntu, debian: $sudo apt-get install pycodestyle \n"\
" * on fedora: #yum install python3-pycodestyle \n"\
" * on arch: #pacman -S python-pycodestyle \n"\
" * or `pip install --user pycodestyle`"
def system(*args, **kwargs):
kwargs.setdefault('stdout', subprocess.PIPE)
proc = subprocess.Popen(args, **kwargs)
out, err = proc.communicate()
if isinstance(out, bytes):
out = out.decode()
return out
def copy_files_to_tmp_dir(files):
tempdir = tempfile.mkdtemp()
for name in files:
filename = os.path.join(tempdir, name)
filepath = os.path.dirname(filename)
if not os.path.exists(filepath):
os.makedirs(filepath)
with open(filename, 'w') as f:
system('git', 'show', ':' + name, stdout=f)
return tempdir
def main():
modified_files = system('git', 'diff-index', '--cached',
'--name-only', 'HEAD', '--diff-filter=ACMR').split("\n")[:-1]
non_compliant_files = []
output_message = None
for modified_file in modified_files:
try:
if not modified_file.endswith(".py"):
continue
pycodestyle_errors = system('pycodestyle', '--repeat', '--ignore', 'E402,E501,E128,W605,W503', modified_file)
if pycodestyle_errors:
if output_message is None:
output_message = NOT_PYCODESTYLE_COMPLIANT_MESSAGE_PRE
output_message += pycodestyle_errors
non_compliant_files.append(modified_file)
except OSError as e:
output_message = NO_PYCODESTYLE_MESSAGE
break
if output_message:
print(output_message)
if non_compliant_files:
print(NOT_PYCODESTYLE_COMPLIANT_MESSAGE_POST)
for non_compliant_file in non_compliant_files:
print("autopep8 -i --max-line-length 120", non_compliant_file, "; git add ",
non_compliant_file)
print("git commit")
sys.exit(1)
if __name__ == '__main__':
main()
+83
View File
@@ -0,0 +1,83 @@
#!/bin/sh
#
# Check that the code follows a consistant code style
#
# Check for existence of indent, and error out if not present.
# On some *bsd systems the binary seems to be called gnunindent,
# so check for that first.
version=`gnuindent --version 2>/dev/null`
if test "x$version" = "x"; then
version=`gindent --version 2>/dev/null`
if test "x$version" = "x"; then
version=`indent --version 2>/dev/null`
if test "x$version" = "x"; then
echo "GStreamer git pre-commit hook:"
echo "Did not find GNU indent, please install it before continuing."
exit 1
else
INDENT=indent
fi
else
INDENT=gindent
fi
else
INDENT=gnuindent
fi
case `$INDENT --version` in
GNU*)
;;
default)
echo "GStreamer git pre-commit hook:"
echo "Did not find GNU indent, please install it before continuing."
echo "(Found $INDENT, but it doesn't seem to be GNU indent)"
exit 1
;;
esac
INDENT_PARAMETERS="--braces-on-if-line \
--case-brace-indentation0 \
--case-indentation2 \
--braces-after-struct-decl-line \
--line-length80 \
--no-tabs \
--cuddle-else \
--dont-line-up-parentheses \
--continuation-indentation4 \
--honour-newlines \
--tab-size8 \
--indent-level2 \
--leave-preprocessor-space"
echo "--Checking style--"
for file in `git diff-index --cached --name-only HEAD --diff-filter=ACMR| grep "\.c$"` ; do
# nf is the temporary checkout. This makes sure we check against the
# revision in the index (and not the checked out version).
nf=`git checkout-index --temp ${file} | cut -f 1`
newfile=`mktemp /tmp/${nf}.XXXXXX` || exit 1
$INDENT ${INDENT_PARAMETERS} \
$nf -o $newfile 2>> /dev/null
# FIXME: Call indent twice as it tends to do line-breaks
# different for every second call.
$INDENT ${INDENT_PARAMETERS} \
$newfile 2>> /dev/null
diff -u -p "${nf}" "${newfile}"
r=$?
rm "${newfile}"
rm "${nf}"
if [ $r != 0 ] ; then
echo "================================================================================================="
echo " Code style error in: $file "
echo " "
echo " Please fix before committing. Don't forget to run git add before trying to commit again. "
echo " If the whole file is to be committed, this should work (run from the top-level directory): "
echo " "
echo " gst-indent $file; git add $file; git commit"
echo " "
echo "================================================================================================="
exit 1
fi
done
echo "--Checking style pass--"
+45
View File
@@ -0,0 +1,45 @@
#!/bin/sh
for execname in gnuindent gindent indent; do
version=`$execname --version 2>/dev/null`
if test "x$version" != "x"; then
INDENT=$execname
break
fi
done
if test -z $INDENT; then
echo "GStreamer git pre-commit hook:"
echo "Did not find GNU indent, please install it before continuing."
exit 1
fi
case `$INDENT --version` in
GNU*)
;;
default)
echo "Did not find GNU indent, please install it before continuing."
echo "(Found $INDENT, but it doesn't seem to be GNU indent)"
exit 1
;;
esac
# Run twice. GNU indent isn't idempotent
# when run once
for i in 1 2; do
$INDENT \
--braces-on-if-line \
--case-brace-indentation0 \
--case-indentation2 \
--braces-after-struct-decl-line \
--line-length80 \
--no-tabs \
--cuddle-else \
--dont-line-up-parentheses \
--continuation-indentation4 \
--honour-newlines \
--tab-size8 \
--indent-level2 \
--leave-preprocessor-space \
$* || exit $?
done
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
BASEDIR=$(dirname $0)
filter_cmd=("cat")
if test -f ".indentignore"; then
filter_args=()
while read -r line; do
if test -n "$line"; then
filter_args+=("-e" "$line")
fi
done < ".indentignore"
if [[ ${#filter_args[@]} -gt 0 ]]; then
filter_cmd=("grep" "-v" "${filter_args[@]}")
fi
fi
git ls-files "*.c" | "${filter_cmd[@]}" | xargs -d '\n' $BASEDIR/gst-indent
+681
View File
@@ -0,0 +1,681 @@
#!/usr/bin/env python3
from urllib.parse import urlparse
from contextlib import contextmanager
import os
import re
import sys
try:
import gitlab
except ModuleNotFoundError:
print("========================================================================", file=sys.stderr)
print("ERROR: Install python-gitlab with `python3 -m pip install python-gitlab python-dateutil`", file=sys.stderr)
print("========================================================================", file=sys.stderr)
sys.exit(1)
try:
from dateutil import parser as dateparse
except ModuleNotFoundError:
print("========================================================================", file=sys.stderr)
print("ERROR: Install dateutil with `python3 -m pip install dateutil`", file=sys.stderr)
print("========================================================================", file=sys.stderr)
sys.exit(1)
import argparse
import requests
import subprocess
ROOT_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))
URL = "https://gitlab.freedesktop.org/"
SIGN_IN_URL = URL + 'sign_in'
LOGIN_URL = URL + 'users/sign_in'
LOGIN_URL_LDAP = URL + '/users/auth/ldapmain/callback'
MONOREPO_REMOTE_NAME = 'origin'
NAMESPACE = "gstreamer"
MONOREPO_NAME = 'gstreamer'
MONOREPO_REMOTE = URL + f'{NAMESPACE}/{MONOREPO_NAME}'
MONOREPO_BRANCH = 'main'
PING_SIGN = '@'
MOVING_NAMESPACE = NAMESPACE
PARSER = argparse.ArgumentParser(
description="Move merge request from old GStreamer module to the new"
"GStreamer 'monorepo'.\n"
" All your pending merge requests from all GStreamer modules will"
" be moved the the mono repository."
)
PARSER.add_argument("--skip-branch", action="store", nargs="*",
help="Ignore MRs for branches which match those names.", dest="skipped_branches")
PARSER.add_argument("--skip-on-failure", action="store_true", default=False)
PARSER.add_argument("--dry-run", "-n", action="store_true", default=False)
PARSER.add_argument("--use-branch-if-exists",
action="store_true", default=False)
PARSER.add_argument("--list-mrs-only", action="store_true", default=False)
PARSER.add_argument(
"-c",
"--config-file",
action="append",
dest='config_files',
help="Configuration file to use. Can be used multiple times.",
required=False,
)
PARSER.add_argument(
"-g",
"--gitlab",
help=(
"Which configuration section should "
"be used. If not defined, the default selection "
"will be used."
),
required=False,
)
PARSER.add_argument(
"-m",
"--module",
help="GStreamer module to move MRs for. All if none specified. Can be used multiple times.",
dest='modules',
action="append",
required=False,
)
PARSER.add_argument(
"-mr",
"--mr-url",
default=None,
type=str,
help=(
"URL of the MR to work on."
),
required=False,
)
GST_PROJECTS = [
'gstreamer',
'gst-plugins-base',
'gst-plugins-good',
'gst-plugins-bad',
'gst-plugins-ugly',
'gst-libav',
'gst-rtsp-server',
'gstreamer-vaapi',
'gstreamer-sharp',
'gst-python',
'gst-omx',
'gst-editing-services',
'gst-devtools',
'gst-docs',
'gst-examples',
'gst-build',
'gst-ci',
]
GST_PROJECTS_ID = {
'gstreamer': 1357,
'gst-rtsp-server': 1362,
'gstreamer-vaapi': 1359,
'gstreamer-sharp': 1358,
'gst-python': 1355,
'gst-plugins-ugly': 1354,
'gst-plugins-good': 1353,
'gst-plugins-base': 1352,
'gst-plugins-bad': 1351,
'gst-omx': 1350,
'gst-libav': 1349,
'gst-integration-testsuites': 1348,
'gst-examples': 1347,
'gst-editing-services': 1346,
'gst-docs': 1345,
'gst-devtools': 1344,
'gst-ci': 1343,
'gst-build': 1342,
}
# We do not want to deal with LFS
os.environ["GIT_LFS_SKIP_SMUDGE"] = "1"
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 GstMRMover:
def __init__(self):
self.modules = []
self.gitlab = None
self.config_files = []
self.gl = None
self.mr = None
self.mr_url = None
self.all_projects = []
self.skipped_branches = []
self.git_rename_limit = None
self.skip_on_failure = None
self.dry_run = False
def connect(self):
fprint("Logging into gitlab...")
if self.gitlab:
gl = gitlab.Gitlab.from_config(self.gitlab, self.config_files)
fprint(f"{green(' OK')}\n", nested=False)
return gl
gitlab_api_token = os.environ.get('GITLAB_API_TOKEN')
if gitlab_api_token:
gl = gitlab.Gitlab(URL, private_token=gitlab_api_token)
fprint(f"{green(' OK')}\n", nested=False)
return gl
session = requests.Session()
sign_in_page = session.get(SIGN_IN_URL).content.decode()
for line in sign_in_page.split('\n'):
m = re.search('name="authenticity_token" value="([^"]+)"', line)
if m:
break
token = None
if m:
token = m.group(1)
if not token:
fprint(f"{red('Unable to find the authenticity token')}\n")
sys.exit(1)
for data, url in [
({'user[login]': 'login_or_email',
'user[password]': 'SECRET',
'authenticity_token': token}, LOGIN_URL),
({'username': 'login_or_email',
'password': 'SECRET',
'authenticity_token': token}, LOGIN_URL_LDAP)]:
r = session.post(url, data=data)
if r.status_code != 200:
continue
try:
gl = gitlab.Gitlab(URL, api_version=4, session=session)
gl.auth()
except gitlab.exceptions.GitlabAuthenticationError as e:
continue
return gl
sys.exit(bold(f"{red('FAILED')}.\n\nPlease go to:\n\n"
' https://gitlab.freedesktop.org/-/profile/personal_access_tokens\n\n'
f'and generate a token {bold("with read/write access to all but the registry")},'
' then set it in the "GITLAB_API_TOKEN" environment variable:"'
f'\n\n $ GITLAB_API_TOKEN=<your token> {" ".join(sys.argv)}\n'))
def git(self, *args, can_fail=False, interaction_message=None, call=False, revert_operation=None):
cwd = ROOT_DIR
retry = True
while retry:
retry = False
try:
if not call:
try:
return subprocess.check_output(["git"] + list(args), cwd=cwd,
stdin=subprocess.DEVNULL,
stderr=subprocess.STDOUT).decode()
except subprocess.CalledProcessError:
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), cwd=cwd)
return "All good"
except Exception as e:
if interaction_message:
if self.skip_on_failure:
return "SKIP"
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 in {cwd} 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 merge request\n"
f" - {bold('`exit 1`')}: {bold('retry')}: once you have let the repo in a state where the operation should be to retried\n"
f" - {bold('`exit 2`')}: to skip that merge request\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, cwd=cwd)
except subprocess.CalledProcessError as e:
if e.returncode == 1:
retry = True
continue
elif e.returncode == 2:
if revert_operation:
self.git(*revert_operation, can_fail=True)
return "SKIP"
elif e.returncode == 3:
if revert_operation:
self.git(*revert_operation, can_fail=True)
sys.exit(3)
except Exception:
# 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 cleanup_args(self):
if self.mr_url:
self.modules.append(GST_PROJECTS[0])
(namespace, module, _, _, mr) = os.path.normpath(urlparse(self.mr_url).path).split('/')[1:]
self.modules.append(module)
self.mr = int(mr)
elif not self.modules:
if self.mr:
sys.exit(f"{red(f'Merge request #{self.mr} specified without module')}\n\n"
f"{bold(' -> Use `--module` to specify which module the MR is from.')}")
self.modules = GST_PROJECTS
else:
VALID_PROJECTS = GST_PROJECTS[1:]
for m in self.modules:
if m not in VALID_PROJECTS:
projects = '\n- '.join(VALID_PROJECTS)
sys.exit(
f"{red(f'Unknown module {m}')}\nModules are:\n- {projects}")
if self.mr and len(self.modules) > 1:
sys.exit(f"{red(f'Merge request #{self.mr} specified but several modules where specified')}\n\n"
f"{bold(' -> Use `--module` only once to specify an merge request.')}")
self.modules.append(GST_PROJECTS[0])
def run(self):
self.cleanup_args()
self.gl = self.connect()
self.gl.auth()
# Skip pre-commit hooks when migrating. Some users may have a
# different version of gnu indent and that can lead to cherry-pick
# failing.
os.environ["GST_DISABLE_PRE_COMMIT_HOOKS"] = "1"
try:
prevbranch = self.git(
"rev-parse", "--abbrev-ref", "HEAD", can_fail=True).strip()
except Exception:
fprint(bold(yellow("Not on a branch?\n")), indent=False)
prevbranch = None
try:
self.setup_repo()
from_projects, to_project = self.fetch_projects()
with nested(' '):
self.move_mrs(from_projects, to_project)
finally:
if self.git_rename_limit is not None:
self.git("config", "merge.renameLimit",
str(self.git_rename_limit))
if prevbranch:
fprint(f'Back to {prevbranch}\n')
self.git("checkout", prevbranch)
def fetch_projects(self):
fprint("Fetching projects... ")
self.all_projects = [proj for proj in self.gl.projects.list(
membership=1, all=True) if proj.name in self.modules]
try:
self.user_project, = [p for p in self.all_projects
if p.namespace['path'] == self.gl.user.username
and p.name == MONOREPO_NAME]
except ValueError:
fprint(
f"{red(f'ERROR')}\n\nCould not find repository {self.gl.user.name}/{MONOREPO_NAME}")
fprint(f"{red(f'Got to https://gitlab.freedesktop.org/gstreamer/gstreamer/ and create a fork so we can move your Merge requests.')}")
sys.exit(1)
fprint(f"{green(' OK')}\n", nested=False)
from_projects = []
user_projects_name = [proj.name for proj in self.all_projects if proj.namespace['path']
== self.gl.user.username and proj.name in GST_PROJECTS]
for project, id in GST_PROJECTS_ID.items():
if project not in user_projects_name or project == 'gstreamer':
continue
projects = [p for p in self.all_projects if p.id == id]
if not projects:
upstream_project = self.gl.projects.get(id)
else:
upstream_project, = projects
assert project
from_projects.append(upstream_project)
fprint(f"\nMoving MRs from:\n")
fprint(f"----------------\n")
for p in from_projects:
fprint(f" - {bold(p.path_with_namespace)}\n")
to_project = self.gl.projects.get(GST_PROJECTS_ID['gstreamer'])
fprint(f"To: {bold(to_project.path_with_namespace)}\n\n")
return from_projects, to_project
def recreate_mr(self, project, to_project, mr):
branch = f"{project.name}-{mr.source_branch}"
if not self.create_branch_for_mr(branch, project, mr):
return None
description = f"**Copied from {URL}/{project.path_with_namespace}/-/merge_requests/{mr.iid}**\n\n{mr.description}"
title = mr.title
if ':' not in mr.title:
title = f"{project.name}: {mr.title}"
new_mr_dict = {
'source_branch': branch,
'allow_collaboration': True,
'remove_source_branch': True,
'target_project_id': to_project.id,
'target_branch': MONOREPO_BRANCH,
'title': title,
'labels': mr.labels,
'description': description,
}
try:
fprint(f"-> Recreating MR '{bold(mr.title)}'...")
if self.dry_run:
fprint(f"\nDry info:\n{new_mr_dict}\n")
else:
new_mr = self.user_project.mergerequests.create(new_mr_dict)
fprint(f"{green(' OK')}\n", nested=False)
except gitlab.exceptions.GitlabCreateError as e:
fprint(f"{yellow('SKIPPED')} (An MR already exists)\n", nested=False)
return None
fprint(f"-> Adding discussings from MR '{mr.title}'...")
if self.dry_run:
fprint(f"{green(' OK')}\n", nested=False)
return None
new_mr_url = f"{URL}/{to_project.path_with_namespace}/-/merge_requests/{new_mr.iid}"
for issue in mr.closes_issues():
obj = {'body': f'Fixing MR moved to: {new_mr_url}'}
issue.discussions.create(obj)
mr_url = f"{URL}/{project.path_with_namespace}/-/merge_requests/{mr.iid}"
for discussion in mr.discussions.list():
# FIXME notes = [n for n in discussion.attributes['notes'] if n['type'] is not None]
notes = [n for n in discussion.attributes['notes']]
if not notes:
continue
new_discussion = None
for note in notes:
note = discussion.notes.get(note['id'])
note_url = f"{mr_url}#note_{note.id}"
when = dateparse.parse(
note.created_at).strftime('on %d, %b %Y')
body = f"**{note.author['name']} - {PING_SIGN}{note.author['username']} wrote [here]({note_url})** {when}:\n\n"
body += '\n'.join([line for line in note.body.split('\n')])
obj = {
'body': body,
'type': note.type,
'resolvable': note.resolvable,
}
if new_discussion:
new_discussion.notes.create(obj)
else:
new_discussion = new_mr.discussions.create(obj)
if not note.resolvable or note.resolved:
new_discussion.resolved = True
new_discussion.save()
fprint(f"{green(' OK')}\n", nested=False)
print(f"New MR available at: {bold(new_mr_url)}\n")
return new_mr
def push_branch(self, branch):
fprint(
f"-> Pushing branch {branch} to remote {self.gl.user.username}...")
if self.git("push", "--no-verify", self.gl.user.username, branch,
interaction_message=f"pushing {branch} to {self.gl.user.username} with:\n "
f" `$git push {self.gl.user.username} {branch}`") == "SKIP":
fprint(yellow("'SKIPPED' (couldn't push)"), nested=False)
return False
fprint(f"{green(' OK')}\n", nested=False)
return True
def create_branch_for_mr(self, branch, project, mr):
remote_name = project.name + '-' + self.gl.user.username
remote_branch = f"{MONOREPO_REMOTE_NAME}/{MONOREPO_BRANCH}"
if self.use_branch_if_exists:
try:
self.git("checkout", branch)
self.git("show", remote_branch + "..", call=True)
if self.dry_run:
fprint("Dry run... not creating MR")
return True
cont = input('\n Create MR [y/n]? ')
if cont.strip().lower() != 'y':
fprint("Cancelled")
return False
return self.push_branch(branch)
except subprocess.CalledProcessError as e:
pass
self.git("remote", "add", remote_name,
f"{URL}{self.gl.user.username}/{project.name}.git", can_fail=True)
self.git("fetch", remote_name)
if self.git("checkout", remote_branch, "-b", branch,
interaction_message=f"checking out branch with `git checkout {remote_branch} -b {branch}`") == "SKIP":
fprint(
bold(f"{red('SKIPPED')} (couldn't checkout)\n"), nested=False)
return False
# unset upstream to avoid to push to main (ie push.default = tracking)
self.git("branch", branch, "--unset-upstream")
for commit in reversed([c for c in mr.commits()]):
if self.git("cherry-pick", commit.id,
interaction_message=f"cherry-picking {commit.id} onto {branch} with:\n "
f" `$ git cherry-pick {commit.id}`",
revert_operation=["cherry-pick", "--abort"]) == "SKIP":
fprint(
f"{yellow('SKIPPED')} (couldn't cherry-pick).", nested=False)
return False
self.git("show", remote_branch + "..", call=True)
if self.dry_run:
fprint("Dry run... not creating MR\n")
return True
cont = input('\n Create MR [y/n]? ')
if cont.strip().lower() != 'y':
fprint(f"{red('Cancelled')}\n", nested=False)
return False
return self.push_branch(branch)
def move_mrs(self, from_projects, to_project):
failed_mrs = []
found_mr = None
for from_project in from_projects:
with nested(f'{bold(from_project.path_with_namespace)}'):
fprint(f'Fetching mrs')
mrs = [mr for mr in from_project.mergerequests.list(
all=True, author_id=self.gl.user.id) if mr.author['username'] == self.gl.user.username and mr.state == "opened"]
if not mrs:
fprint(f"{yellow(' None')}\n", nested=False)
continue
fprint(f"{green(' DONE')}\n", nested=False)
for mr in mrs:
if self.mr:
if self.mr != mr.iid:
continue
found_mr = True
fprint(
f'Moving {mr.source_branch} "{mr.title}": {URL}{from_project.path_with_namespace}/merge_requests/{mr.iid}... ')
if mr.source_branch in self.skipped_branches:
print(f"{yellow('SKIPPED')} (blacklisted branch)")
failed_mrs.append(
f"{URL}{from_project.path_with_namespace}/merge_requests/{mr.iid}")
continue
if self.list_mrs_only:
fprint("\n"f"List only: {yellow('SKIPPED')}\n")
continue
with nested(f'{bold(from_project.path_with_namespace)}: {mr.iid}'):
new_mr = self.recreate_mr(from_project, to_project, mr)
if not new_mr:
if not self.dry_run:
failed_mrs.append(
f"{URL}{from_project.path_with_namespace}/merge_requests/{mr.iid}")
else:
fprint(f"{green(' OK')}\n", nested=False)
self.close_mr(from_project, to_project, mr, new_mr)
fprint(
f"\n{yellow('DONE')} with {from_project.path_with_namespace}\n\n", nested=False)
if self.mr and not found_mr:
sys.exit(
bold(red(f"\n==> Couldn't find MR {self.mr} in {self.modules[0]}\n")))
for mr in failed_mrs:
fprint(f"Didn't move MR: {mr}\n")
def close_mr(self, project, to_project, mr, new_mr):
if new_mr:
new_mr_url = f"{URL}/{to_project.path_with_namespace}/-/merge_requests/{new_mr.iid}"
else:
new_mr_url = None
mr_url = f"{URL}/{project.path_with_namespace}/-/merge_requests/{mr.iid}"
cont = input(f'\n Close old MR {mr_url} "{bold(mr.title)}" ? [y/n]')
if cont.strip().lower() != 'y':
fprint(f"{yellow('Not closing old MR')}\n")
else:
obj = None
if new_mr_url:
obj = {'body': f"Moved to: {new_mr_url}"}
else:
ret = input(
f"Write a comment to add while closing MR {mr.iid} '{bold(mr.title)}':\n\n").strip()
if ret:
obj = {'body': ret}
if self.dry_run:
fprint(f"{bold('Dry run, not closing')}\n", nested=False)
else:
if obj:
mr.discussions.create(obj)
mr.state_event = 'close'
mr.save()
fprint(
f'Old MR {mr_url} "{bold(mr.title)}" {yellow("CLOSED")}\n')
def setup_repo(self):
fprint(f"Setting up '{bold(ROOT_DIR)}'...")
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:
exit(
f"Git repository{ROOT_DIR} is not clean. Clean it up before running {sys.argv[0]}\n ({e})")
self.git('remote', 'add', MONOREPO_REMOTE_NAME,
MONOREPO_REMOTE, can_fail=True)
self.git('fetch', MONOREPO_REMOTE_NAME)
self.git('remote', 'add', self.gl.user.username,
f"git@gitlab.freedesktop.org:{self.gl.user.username}/gstreamer.git", can_fail=True)
self.git('fetch', self.gl.user.username,
interaction_message=f"Setup your fork of {URL}gstreamer/gstreamer as remote called {self.gl.user.username}")
fprint(f"{green(' OK')}\n", nested=False)
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\n")
self.git("config", "merge.renameLimit", "999999")
def main():
mover = GstMRMover()
PARSER.parse_args(namespace=mover)
mover.run()
if __name__ == '__main__':
main()
+236
View File
@@ -0,0 +1,236 @@
#!/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. ie https://gitlab.freedesktop.org/user/gst-plugins-bad.git or /home/me/gst-build/subprojects/gst-plugins-bad")
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`")
ret = self.cherry_pick(tmpbranchname)
except Exception as e:
self.git("rebase", "--abort", can_fail=True)
self.git("checkout", prevbranch)
self.git("branch", "-D", tmpbranchname)
raise
if ret:
fprint(f"{green(' OK')}\n", nested=False)
else:
self.git("checkout", prevbranch)
self.git("branch", "-D", tmpbranchname)
fprint(f"{red(' ERROR')}\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')
try:
self.git("cherry-pick", sha,
interaction_message=f"cherry-picking {sha} onto {branch} with:\n "
f" `$ git cherry-pick {sha}`",
revert_operation=["cherry-pick", "--abort"])
except Exception as e:
fprint(f' - Cherry picking failed: {bold(sha)}\n')
return False
return True
def git(self, *args, can_fail=False, interaction_message=None, call=False, revert_operation=None):
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 Exception as e:
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 2`')}: stop the script and abandon rebasing your branch\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 == 2:
if revert_operation:
self.git(*revert_operation, can_fail=True)
raise
except Exception as e:
# 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()