Update
This commit is contained in:
Executable
+27
@@ -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)
|
||||
@@ -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')
|
||||
@@ -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),
|
||||
}))
|
||||
@@ -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)
|
||||
Executable
+49
@@ -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
|
||||
|
||||
Executable
+81
@@ -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()
|
||||
Executable
+83
@@ -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--"
|
||||
Executable
+45
@@ -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
|
||||
Executable
+18
@@ -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
|
||||
Executable
+681
@@ -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()
|
||||
Executable
+236
@@ -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()
|
||||
Reference in New Issue
Block a user