Пишем скрипт для автоматизации коммитов GitHub - LEFT JOIN

Свяжитесь с нами в любой удобной для вас форме

Менеджер

Написать в телеграмм

Онлайн
Телеграмм
или
Заполните форму

3 минут чтения

*

7 апреля 2021 г.

Пишем скрипт для автоматизации коммитов GitHub

У GitHub есть API, позволяющий делать всё, что можно сделать руками: создавать репозитории, делать коммиты файлов и переключаться между ветками. Не все используют GUI при работе с системой контроля версий, и для коммита файлов приходится вводить не одну команду в терминал, а смена веток нередко приводит к запутанности действий. Сегодня мы поэкспериментируем с GitHub API и напишем скрипт, который сам собирает все файлы текущей директории раз в час и отправляет в отдельную ветку на GitHub при помощи get, post и put-запросов к методу /repos.

Получение access token

Для работы с GitHub API нужно получить токен, который выдаётся каждому пользователю в настройках. Для создания нового токена нажимаем на «Generate new token»:

В процессе работы с GitHub API токен может просрочиться и в ответ придёт ошибка «Bad credentials». Для решения проблемы можно создать новый токен или получить токен приложения

Следом нужно оставить название для токена и отметить области доступа ключу. Для наших целей достаточно дать доступ только к разделу repo.

После в зелёном окошке появится токен:

Отправляем запросы

Подключим нужные библиотеки и введём некоторые константные значения: логин, токен, название для репозитория и ветки, в которую в автоматическом режиме будут отправляться файлы:


import requests
import base64
import json
import os

username = ‘leftjoin’
repo = ‘leftjoin’
token = «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»
new_branch_name = ‘automatic’

Создадим новый репозиторий post-запросом. В поле data передаем название репозитория, в auth — логин и токен:


r = requests.post(‘https://api.github.com/user/repos’,
data=json.dumps({‘name’:repo}),
auth=(username, token),
headers={«Content-Type»: «application/json»})

Теперь загрузим файл README.md, в котором будет строка “Hello, world!”. Для этого строку нужно перекодировать в base64 – GitHub принимает данные в такой кодировке:


content = «Hello, world!»
b_content = content.encode(‘utf-8’)
base64_content = base64.b64encode(b_content)
base64_content_str = base64_content.decode(‘utf-8’)

Теперь создадим коммит в формате словаря: он состоит из пути (пока его оставим пустым), сообщения коммита и содержания:


f = {‘path’:»,
     ‘message’: ‘Automatic update’,
     ‘content’: base64_content_str
}

Отправим файл в репозиторий put-запросом, передав путь до README.md прямо в ссылке запроса:


f_resp = requests.put(f’https://api.github.com/repos/{
auth=(username, token),
headers={ «Content-Type»: «application/json» },
data=json.dumps(f))

Теперь в репозитории в ветке main есть один файл README.md, который содержит строку «Hello, world!»:

Работа с ветками

Так как наш скрипт подразумевает работу в фоновом режиме, не всегда будет возможность контролировать, какие версии файлов он отправляет. Чтобы не засорять основную ветку будем отправлять все файлы в специальную, из которой при надобности можно будет достать обновления. Логика такая: проверяем, есть ли в репозитории такая ветка и, если нет, создаём её. Затем получаем все файлы текущей директории, построчно считываем, перекодируем в base64, коммитим и отправляем в ветку для автоматических обновлений.

Начнём с функции, которая определяет, существует ли ветка с таким названием. Она отправит запрос на получение всех веток репозитория, и вернёт False, если ветки не существует:


def branch_exist(branch_name):

    branches = requests.get(f’https://api.github.com/repos/{username}/{repo}/git/refs’).json()

    try:

        for branch in branches:

            if branch[‘ref’] == ‘refs/heads/’ + branch_name:

                return True

        return False

    except Exception:

        return False

У каждой ветки, файлов и репозиториев на GitHub есть уникальный идентификатор, получаемый путём хэш-суммы алгоритмом SHA. Он необходим как для модификации файлов, так и для смены веток — опишем функцию, которая по названию ветки получает SHA для неё:


def get_branch_sha(branch_name):

    try:

        branches = requests.get(f’https://api.github.com/repos/{username}/{repo}/git/refs’).json()

        for branch in branches:

            sha = None

            if branch[‘ref’] == ‘refs/heads/’ + branch_name:

                sha = branch[‘object’][‘sha’]

            return sha

    except Exception:

        return None

Следом опишем функцию создания новой ветки с названием из переменной new_branch_name:


def create_branch():

    main_branch_sha = get_branch_sha(‘main’)

    requests.post(f’https://api.github.com/repos/{username}/{repo}/git/refs’,

             auth=(username, token),

             data=json.dumps({

                 ‘ref’:f’refs/heads/{new_branch_name}’,

                 ‘sha’:main_branch_sha

             })).json()

Для модификации файлов тоже необходимо знать его идентификатор:


def get_sha(path):

    r = requests.get(f’https://api.github.com/repos/{username}/{repo}/contents/{path}’,

                    auth=(username, token),

                    data=json.dumps({

                        ‘branch’: new_branch_name

                    }))

    sha = r.json()[‘sha’]

    return sha

Наконец, опишем ряд главных функций — первая по аналогии с примером из начала материала принимает содержимое файла и формирует коммит в отдельную ветку, а вторая отправляет файл в указанный репозиторий:


def make_file_and_commit(content, sha=None):

    b_content = content.encode(‘utf-8’)

    base64_content = base64.b64encode(b_content)

    base64_content_str = base64_content.decode(‘utf-8’)

    f = {‘path’:»,

     ‘message’: ‘Automatic update’,

     ‘content’: base64_content_str,

     ‘sha’:sha,

     ‘branch’: new_branch_name}

    return f

def send_file(path, data):

    f_resp = requests.put(f’https://api.github.com/repos/{username}/{repo}/contents/{path}’,

                    auth=(username, token),

                    headers={ «Content-Type»: «application/json» },

                    data=json.dumps(data))

    print(f_resp.json())

    return f_resp

И ещё две функции напоследок: первая нужна, потому что мы должны знать, модифицируем мы уже существующий файл или новый. В случае с новым нет необходимости получать SHA для файла — его просто не существует. Вторая функция считывает всё содержимое файла в строку.


def file_exist(path):

    r = requests.get(f’https://api.github.com/repos/{username}/{repo}/contents/{path}’,

                    auth=(username, token))

    return r.ok

def file_reader(path):

    lines = «»

    with open(path, ‘r’) as f:

        for line in f:

            lines += line

    return lines

Соберём всё вместе в функцию main(). Проверяем, существует ли ветка для автоматических коммитов, затем получаем все файлы директории, проверяем их существование в репозитории и отправляем:


def main():

    if not branch_exist(new_branch_name):

        create_branch()

    files = [os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser(«.»)) for f in fn]

    for path in files:

        print(path)

        sha = None

        if file_exist(path):

            sha = get_sha(path)

        lines = file_reader(path)

        f = make_file_and_commit(lines, sha)

        r = send_file(path, f)

Перевести скрипт в автоматический режим может помочь библиотека schedule, которую мы уже использовали в материале «Анализ рынка вакансий аналитики и BI: дашборд в Tableau»


import schedule
schedule.every().hour.do(main)
while True:
   schedule.run_pending()

Вот и всё: мы написали скрипт, который самостоятельно каждый час отправляет все файлы текущей директории в ветку на GitHub.

319 просмотров

Добавить комментарий

[ Рекомендации ]

Читайте также

[ Дальше ]