Русский
Русский
English
Статистика
Реклама

Merge

Code review в Gitlab CE если Merge request approvals нет, но очень хочется

24.06.2020 18:08:11 | Автор: admin

Одной из самых нужных функций, которой нет в бесплатной версии GitLab, является возможность контролировать Merge request (MR), используя обязательный code review.

Сделаем минимальный функционал сами запретим Merge, пока несколько разработчиков не поставят палец вверх на MR.



Зачем это вообще?



Наша организация вполне может позволить себе купить лицензию GitLab. Но, так как разработка ведется в закрытом контуре без доступа в интернет, и есть жесткое планирование бюджета, закупка лицензий self-managed с нужным функционалом может затянуться на многие месяцы, а работать нужно уже сейчас.

В итоге приходится:
  • либо совсем запрещать Merge в защищенные ветки для части разработчиков, но тогда разработчики, имеющие право на Merge, получают конфликты при слиянии чужих MR как бонус;
  • либо давать возможность делать бесконтрольные слияния с вашей мастер-веткой без code review, даже если это Junior, устроившийся только вчера.


Первым же делом отправился гуглить, полагая, что уж точно кто-то что-то подобное уже сделал (без доработки кода), но оказалось, что подобной реализации в community версии еще не было.

Общая схема работы



В качестве примера настроим Merge request approvals на тестовом репозитории myapp
gitlab.com/gitlab-ce-mr-approvals/myapp

  1. Создадим токен для доступа к API GitLab (через него будем получать информацию о количестве голосов за и против)
  2. Добавим токен в переменные GitLab
  3. Запретим Merge при ошибках в пайплайне (если голосов за недостаточно)
  4. Настроим проверку голосов как часть пайплайна CI/CD
  5. Запретим делать коммиты в защищенные ветки, все изменения проводим только через MR
  6. Проверим, что получилось в итоге


1. Создаем токен для доступа к API



Заходим в Настройки пользователя Токены доступа и записываем токен


Учетная запись для получения токена
Доступ к API позволяет делать практически все с вашими репозиториями, поэтому советую создать отдельную учетную запись Gitlab, дать ей минимальные права на ваши репозтории (например, Reporter) и получить токен для этой учетной записи.



2. Добавляем токен в переменные Gitlab



Например, на предыдущем шаге мы получили токен QmN2Y0NOUFlfeXhvd21ZS01aQzgK

Открываем Настройки CI/CD Переменные Добавить переменную GITLAB_TOKEN_FOR_CI



В итоге получим:



Это можно сделать как на одном репозитории, так и на группе репозиториев.

3. Ставим запрет на Merge, если не получены одобрения коллег после проведенного code review



В нашем случае запретом на Merge будет являться то, что сборочный конвейер вернет ошибку при недостаточном количестве голосов.

Заходим в Настройки Основные Запросы на слияние Проверки слияния и включаем опцию Сборочные линии должны успешно выполниться


4. Настраиваем пайплайн



Если вы еще не делали CI/CD конвейер для вашего приложения
Создаем в корне репозитория файл .gitlab-ci.yml с простейшим содержанием

stages:  - build  - testvariables:  NEED_VOTES: 1include:  - remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"run-myapp:  stage: build  script: echo "Hello world"




Отдельный репозиторий для конфигурации CI/CD
Я бы рекомендовал сделать отдельный репозиторий, в котором необходимо создать файл myapp.gitlab-ci.yml для настройки конвейера. Так вы сможете лучше контролировать доступ участников, которые могут изменить конвейер сборки и получить токен доступа.

Расположение нового файла конвейера нужно будет указать, зайдя в репозиторий myapp Настройки CI/CD Сборочные линии Пользовательский путь конфигурации CI указать новый файл, например myapp-ci.gitlab-ci.yml@gitlab-ce-mr-approvals/ci


Совет: используйте линтер для внесения изменений в файлы пайплайна
Даже если вы работаете один, хорошим помощником выступит работа через MR, прогоняя все ваши изменения файлов пайплайна через линтер. Если вы ошибетесь в синтаксисе YAML-файла, это не даст вам сломать рабочий конвейер, а просто заблокирует Merge.

Пример контейнеров с линтерами, которые вы можете встроить в ваш пайплайн:
hub.docker.com/r/gableroux/gitlab-ci-lint
hub.docker.com/r/sebiwi/gitlab-ci-validate

И пример стадии проверки:

stages:  - lintlint:  image: sebiwi/gitlab-ci-validate:1.3.0  variables:    GITLAB_HOST: https://gitlab.com  script:    - CI_FILES=(./*.yml)    - for f in "${CI_FILES[@]}"; do        gitlab-ci-validate $f;      done;




Осталось добавить в ваш пайплайн несколько параметров, чтобы все заработало:

stages:
- test

variables:
NEED_VOTES: 1

include:
- remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"


Переменная NEED_VOTES определяет сколько пальцев вверх должно быть у MR, чтобы был доступен Merge. Значение, равное единице, означает, что вы сами можете одобрить свой MR, лайкнув его.

include подключает стадию test, проверяющую количество лайков.

Простейший пайплайн на примере myapp.gitlab-ci.yml
stages:
- build
- test

variables:
NEED_VOTES: 0

include:
- remote: "https://gitlab.com/gitlab-ce-mr-approvals/ci/-/raw/master/check-approve.gitlab-ci.yml"

run-myapp:
stage: build
image: openjdk
script:
- echo CI_MERGE_REQUEST_TARGET_BRANCH_NAME $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- java HelloWorld.java



Содержание check-approve.gitlab-ci.yml
ci-mr:
stage: test
script:
- echo ${CI_API_V4_URL}
- echo "CI_PROJECT_ID ${CI_PROJECT_ID}"
- echo "CI_COMMIT_SHA ${CI_COMMIT_SHA}"
- "export MR_ID=$(curl --silent --request GET --header \"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq \".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .id else {} end\" | grep --invert-match {})"
- "export MR_TITLE=$(curl --silent --request GET --header \"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq \".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .title else {} end\" | grep --invert-match {})"
- "export MR_WIP=$(curl --silent --request GET --header \"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq \".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .work_in_progress else {} end\" | grep --invert-match {})"
- "export MR_UPVOTES=$(curl --silent --request GET --header \"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq \".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .upvotes else {} end\" | grep --invert-match {})"
- "export MR_DOWNVOTES=$(curl --silent --request GET --header \"PRIVATE-TOKEN: $GITLAB_TOKEN_FOR_CI\" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests | jq \".[] | if .sha == \\\"${CI_COMMIT_SHA}\\\" then .downvotes else {} end\" | grep --invert-match {})"
- MR_VOTES=$(expr ${MR_UPVOTES} - ${MR_DOWNVOTES})
- NEED_VOTES_REAL=${NEED_VOTES:-1}
- echo "MR_ID ${MR_ID} MR_TITLE ${MR_TITLE} MR_WIP ${MR_WIP} MR_UPVOTES ${MR_UPVOTES} MR_DOWNVOTES ${MR_DOWNVOTES}"
- echo "MR_VOTES ${MR_VOTES} Up vote = 1, down vote = -1, MR OK if votes >=${NEED_VOTES_REAL}"
- if [ "${MR_VOTES}" -ge "$(expr ${NEED_VOTES_REAL})" ];
then
echo "MR OK";
else
echo "MR ERROR Need more votes";
exit 1;
fi
image: laptevss/gitlab-api-util
rules:
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^release\/.*$/'



Подробнее о том, что происходит при проверке:
  • установлено ограничение, что проверка будет только при создании MR в ветки master или release/*
  • используя API GitLab, получаем количество лайков и дизлайков
  • вычисляем разность между положительными и отрицательными откликами
  • если разность меньше заданного нами значения в NEED_VOTES, то блокируем возможность сделать слияние


5. Запрещаем коммиты в защищенные ветки



Определяем ветки, для которых мы должны проводить code review и указываем, что работать с ними можно только через MR.

Для этого заходим в Настройки Репозиторий Protected Branches


6. Проверяем



Зададим NEED_VOTES: 0

Делаем MR и ставим дизлайк.



В логах сборки:


Теперь ставим лайк и запускаем повторную проверку:
Подробнее..

Слияние списков на python

14.07.2020 20:14:29 | Автор: admin

Сравнение различных методов слияния двух отсортированных списков


Пусть у нас есть два списка (для простоты из целых чисел), каждый из которых отсортирован. Хотим объединить их в один список, который тоже должен быть отсортирован. Эта задача наверняка всем знакома, используется, например, при сортировке слиянием.



Способов реализации (особенно на python) достаточно много. Давайте разберем некоторые из них и сравним затачиваемое время на разных входных данных.


Основная идея алгоритма заключается в том, что, поместив по одной метке в начале каждого списка, будем сравнивать отмеченные элементы, брать меньший из них и передвигать метку в его списке на следующее число. Когда один из списков кончается, нужно добавить остаток второго в конец.


Входные данные не меняются


Пусть есть два списка list1 и list2.
Начнем с самого простого алгоритма: обозначим метки за i и j и будем брать меньший из list1[i], list2[j] и увеличивать его метку на единицу, пока одна из меток не выйдет за границу списка.


При первом сравнении мы выберем минимальный элемент из двух минимальных в своем списке и подвинемся на следующий элемент, поэтому наименьший элемент из двух списков будет стоять на нулевом месте результирующего. Дальше несложно по индукции доказать, что далее слияние пройдет верно.


Перейдем к коду:


def simple_merge(list1, list2):    i, j = 0, 0    res = []    while i < len(list1) and j < len(list2):        if list1[i] < list2[j]:            res.append(list1[i])            i += 1        else:            res.append(list2[j])            j += 1    res += list1[i:]    res += list2[j:]     # один из list1[i:] и list2[j:] будет уже пустой, поэтому добавится только нужный остаток    return res

Заметим, что в данном коде используется только перемещение вперед по списку. Поэтому будет достаточно работать с итераторами. Перепишем алгоритм с помощью итераторов.


Еще изменим обработку концов списков, так как теперь мы не умеем копировать сразу до конца. Будем обрабатывать элементы до того, когда оба итератора дойдут до конца, при этом, если один уже оказался в конце, будем просто брать из второго.


def iter_merge(list1, list2):    result, it1, it2 = [], iter(list1), iter(list2)    el1 = next(it1, None)    el2 = next(it2, None)    while el1 is not None or el2 is not None:        if el1 is None or (el2 is not None and el2 < el1):            result.append(el2)            el2 = next(it2, None)        else:            result.append(el1)            el1 = next(it1, None)    return result

В этой реализации можно вместо добавления по одному элементу (result.append()) собрать генератор, а потом из него получить список. Для этого напишем отдельную функцию, которая будет строить генератор, а основная функция сделает из него список.


def gen_merge_inner(it1, it2):    el1 = next(it1, None)    el2 = next(it2, None)    while el1 is not None or el2 is not None:        if el1 is None or (el2 is not None and el2 < el1):            yield el2            el2 = next(it2, None)        else:            yield el1            el1 = next(it1, None)def gen_merge(list1, list2):    return list(gen_merge_inner(iter(list1), iter(list2))) # из генератора получаем список

Встроенные реализации


Рассмотрим еще несколько способов слияния через встроенные в python функции.


  • merge из heapq. Как говорит документация, эта функция делает именно то, что мы хотим, и больше: объединяет несколько итерируемых объекта, можно задать ключ, можно сортировать в обратном порядке.


    Тогда нам нужно просто импортировать и использовать:


    from heapq import mergedef heapq_merge(list1, list2):return list(merge(list1, list2)) # тоже возвращает генератор
    

  • Counter из collections. Counter умеет считать количество вхождений каждого из элементов, выдавать их в тех количествах, в которых они входят, и еще несколько полезных вещей, которые сейчас не нужны (например, несколько самых часто встречающихся элементов).


    Воспользуемся gen_merge_inner для слияния элементов Counter(list1) и Counter(list2):


    def counter_merge(list1, list2):return list(gen_merge_inner(Counter(list1).elements(), Counter(list2).elements()))
    

  • И, наконец, просто сортировка. Объединяем и сортируем заново.


    def sort_merge(list1, list2):return sorted(list1 + list2)
    


Если можно менять исходные списки


Предположим, что после слияния старые списки больше не нужны (как обычно и случается). Тогда можно написать еще один способ. Будем как и раньше сравнивать нулевые элементы списков и вызывать pop(0) у списка с меньшим, пока один из списков не закончится.


def pop_merge(list1, list2):    result = []    while list1 and list2:        result.append((list1 if list1[0] < list2[0] else list2).pop(0))    return result + list1 + list2

Получили простенькую функцию на 4 строчки, но использовать дальше исходные списки не получится. Можно их скопировать, потом работать с копиями, но это потребует много дополнительного времени. Здесь будут проблемы с тем, что удаление нулевого элемента очень дорогое. Поэтому еще одна модификация будет заключаться в том, что мы будем вместо удаления из начала списка использовать удаление из конца, но придется в конце развернуть списки.


def reverse_pop_merge(list1, list2):    result = []    while list1 and list2:        result.append((list1 if list1[-1] > list2[-1] else list2).pop(-1))    return (result + list1[-1::-1] + list2[-1::-1])[-1::-1]

Сравнение


Пора перейти к самому интересному.
Составим список функций, которые будем сравнивать:


  • simple_merge
  • iter_merge
  • gen_merge
  • heapq_merge
  • counter_merge
  • sort_merge
  • pop_merge
  • reverse_pop_merge

Будем измерять время работы с помощью модуля timeit. Код можно посмотреть здесь.


Разберем несколько ситуаций: оба списка примерно одинакового размера, один список большой, а второй маленький, количество вариантов элементов большое, количество вариантов маленькое. Кроме этого проведем просто общий случайный тест.


Тест первый


Проведем общий тест, размеры от $1$ до $10^5$, элементы от $1$ до $10^6$.


Отдельно сравним pop и reverse_pop:

pop_merge тратит колоссально больше времени в общем случае, как и ожидалось.


Не будем учитывать здесь огромный pop_merge, чтобы лучше видеть разницу между другими:

reverse_pop_merge показал себя относительно неплохо по сравнению с ручной реализацией и heapq_merge.


Методы на итераторах работают еще быстрее, при этом видно, что получилось выгоднее построить генератор, чем добавлять элементы в список.


Тест второй, сравнимые размеры


Размеры будут принадлежать отрезку $[50x, 50(x+1))$, а $x$ увеличиваем, начиная с $1$. Шаг $50$.

Как уже можно видеть pop_merge при небольшом размере списков еще ведет себя как heapq_merge, а дальше обгоняет всех.


Тест третий, один маленький, второй большой


Размер первого равен $x$, размер второго $10^4 + 100x$.

В самом начале (на очень маленьких списках) reverse_pop_merge обгоняет всех, кроме sort_merge, но на чуть больших все выходит на стандартные позиции.


Тест четвертый, много повторных


Размеры фиксированы, а количество элементов увеличивается на $5$, начиная с $1$.

Как видно, на достаточно малых количествах counter_merge оказывается быстрее reverse_pop_merge и heapq_merge, но потом он отстает.


Итоги


Абсолютным победителем оказался sort_merge! Гораздо быстрее просто отсортировать список заново, чем использовать вроде бы линейные от длины списков функции.


На втором месте в подавляющем большинстве случаев идет gen_merge, за ним следует iter_merge.


Остальные методы используют еще больше времени, но некоторые в каких-то крайних случаях достигают результатов 2-3 мест.


P.S.


Код, тесты, jupyter notebook c графиками можно найти на gitlab.


Возможно этот анализ неполон, буду рад добавить к сравнению ваши варианты, предлагайте.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru