python 脚本备份gitlab分支(打tag)

现在使用git作为版本控制的趋势已经很明显, gitlab又是用的比较多的一个开源的git服务端;开发过程中会有很多dev分支,在发布生产后一般会把这些分支备份(打tag), 然后合并到master分支,之后就可以删除分支了。下面的python脚本 就是自动化实现这一过程的。

需要修改 host为gitlab域名,git_user_name ,git_pwd git登录账号密码,group_project 需要处理的分组和项目,exclude_branch 需要排除的分支;
基于 # GitLab Community Edition 12.10.6版本

可以基于last_commit判断是否已经备份打tag了

"""
用于gitlab已发布版本分支, 打Tag归档, 可选删除归档分支
python3
pip install BeautifulSoup
pip install requests
pip install lxml
"""
import requests
from bs4 import BeautifulSoup
# gitlab的域名  需要自己更改
host = "https://git.xxx.com"
login_index = host + "/users/sign_in"
login_url = host + "/users/auth/ldapmain/callback"
project_index_url = host + "/{}/{}"
branches_api = host + "/api/v4/projects/{}/repository/branches/?page={}&per_page={}"
tags_api = host + "/api/v4/projects/{}/repository/tags/?page={}&per_page={}"
add_tag_url = project_index_url + "/-/tags"
tree_url = project_index_url + "/-/tree/{}"
graphql = host + "/api/graphql"
branches_url = project_index_url + "/branches"
delete_branch_url = branches_url + "/{}"

# git登录的账号密码
git_user_name = "root"
git_pwd = "*****"

# 需要处理的分组和项目, Administrator = root 组名可以打开gitlab项目首页 地址栏显示即是【https://git.xxx.com/组名/项目名】, 建议先fork项目到私人仓库测试,检查没问题再在上游仓库执行
# group_project = {'plat': ['trade'], 'root': ['pay']}
group_project = {'core': ['trade','settle','service-api',]}
# 需要排除创建tag的分支,未发布的分支无需归档
exclude_branch = ['master', 'release', 'dev-v5.8.5', 'dev-v5.8.4', 'dev-v5.8.6', 'dev-v5.8.4.1', 'dev-v5.8.3.5']
# 归档完成是否删除分支,第一次测试请改为False,受保护的分支需要受权限才能删除
delete_bak_branch = True

tag_suffix = '-Tag'
header = {
    'authority': 'git.xxx.com',
    'method': 'POST',
    'path': '/api/graphql',
    'scheme': 'https',
    'accept': '*/*',
    'accept-encoding': 'gzip, deflate, br',
    'accept-language': 'zh-CN,zh;q=0.9',
    'content-type': 'application/json',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-origin',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}


def delete_branch(group, project, branch, session):
    if delete_bak_branch:
        # print('开始删除 Group=【%s】 Project=【%s】 Branch=【%s】' % (group, project, branch))
        token = obtain_token(session, branches_url.format(group, project))
        header['x-csrf-token'] = token
        result = session.delete(url=delete_branch_url.format(group, project, branch), headers=header)
        if is_success(result.status_code):
            print('完成删除 Group=【%s】 Project=【%s】 Branch=【%s】' % (group, project, branch))
    pass


def obtain_project(k, v, session):
    result = session.get(project_index_url.format(k, v))
    if not is_success(result.status_code):
        raise Exception('获取项目id失败')
    soup = BeautifulSoup(result.content, 'lxml')
    project_id = soup.body.attrs['data-project-id']
    link = soup.find_all(attrs={'name': 'csrf-token'})[0]
    token = link['content']
    return {'token': token, 'project_id': project_id}


def main():
    session = requests.Session()
    # 获取token
    token = obtain_token(session, login_index)
    # 登录
    user_name = login(session, token)
    for k in group_project:
        for v in group_project[k]:
            tags = []
            branches = []
            for p in range(1, 10):
                pair = obtain_project(k, v, session)
                header['x-csrf-token'] = pair['token']
                branch_result = session.get(url=branches_api.format(pair['project_id'], p, 100), headers=header)
                tag_result = session.get(url=tags_api.format(pair['project_id'], p, 100), headers=header)
                if not is_success(branch_result.status_code) or not is_success(tag_result.status_code):
                    raise Exception('obtain branch and tags error')
                branch_page = branch_result.json()
                tag_page = tag_result.json()
                have_more = False
                if branch_page:
                    for x in branch_page:
                        branches.append(x['name'])
                    have_more = True
                if tag_page:
                    for y in tag_page:
                        tags.append(y['name'])
                    have_more = True
                if not have_more:
                    break
            deal_branches = set(branches) - set(exclude_branch)
            print('过滤之后待处理分支列表=%s' % (str(deal_branches)))
            for branch in deal_branches:
                rel_add_tag_url = add_tag_url.format(k, v)
                add_token_url = rel_add_tag_url + '/new'
                new_tag = obtain_tag_name(branch, tags, k, v, session)
                if not new_tag:
                    delete_branch(k, v, branch, session)
                    continue
                add_tag_param = {'utf8': '✓', 'tag_name': new_tag, 'ref': branch,
                                 'message': '自动归档创建Tag-by-' + user_name,
                                 'authenticity_token': obtain_token(session, add_token_url)}
                # print('开始归档 Group=【%s】 Project=【%s】 Branch=【%s】Tag=【%s】' % (k, v, branch, new_tag))
                result = session.post(rel_add_tag_url, add_tag_param)
                if is_success(result.status_code):
                    print('完成归档 Group=【%s】 Project=【%s】 Branch=【%s】Tag=【%s】' % (k, v, branch, new_tag))
                    delete_branch(k, v, branch, session)
                else:
                    print('归档失败 Group=【%s】 Project=【%s】 Branch=【%s】Tag=【%s】' % (k, v, branch, new_tag))

    print("over")


def is_success(code):
    if 200 <= code < 300:
        return True
    else:
        return False


def check_commit(branch, branch_last_commit, tag_name, tag_last_commit):
    code1 = branch_last_commit.status_code
    code2 = tag_last_commit.status_code
    flag = is_success(code1) and is_success(code2)
    if not flag:
        # 检查失败, 认为不是同一个分支
        print('请求获取最后提交记录请求失败branch_last_commit=【%d】【%d】' % (code1, code2))
        return False
    try:
        branch_json = branch_last_commit.json()
        tag_json = tag_last_commit.json()
        sha1 = branch_json['data']['project']['repository']['tree']['lastCommit']['sha']
        sha2 = tag_json['data']['project']['repository']['tree']['lastCommit']['sha']
        if sha1 == sha2:
            print('判断Branch=【%s】 Tag=【%s】最新Commit=【%s】记录相同,不再生成新的Tag' % (branch, tag_name, sha1))
            return True
    except Exception as e:
        print(e)
    return False


def obtain_tag_name(branch, tags, group, project_name, session):
    tag_name = branch + tag_suffix
    project_path = group + '/' + project_name
    if tag_name in tags:
        params = "{\"operationName\":\"pathLastCommit\",\"variables\":{\"projectPath\":\"" + project_path + "\",\"ref\":\"" + branch + "\",\"path\":\"\"},\"query\":\"query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {\\n  project(fullPath: $projectPath) {\\n    repository {\\n      tree(path: $path, ref: $ref) {\\n        lastCommit {\\n          sha\\n          title\\n          description\\n          message\\n          webUrl\\n          authoredDate\\n          authorName\\n          authorGravatar\\n          author {\\n            name\\n            avatarUrl\\n            webUrl\\n            __typename\\n          }\\n          signatureHtml\\n          pipelines(ref: $ref, first: 1) {\\n            edges {\\n              node {\\n                detailedStatus {\\n                  detailsPath\\n                  icon\\n                  tooltip\\n                  text\\n                  group\\n                  __typename\\n                }\\n                __typename\\n              }\\n              __typename\\n            }\\n            __typename\\n          }\\n          __typename\\n        }\\n        __typename\\n      }\\n      __typename\\n    }\\n    __typename\\n  }\\n}\\n\"}"
        token = obtain_token(session, project_index_url.format(group, project_name))
        header['x-csrf-token'] = token
        branch_last_commit = session.request(method='POST', url=graphql, data=params.encode('utf-8'), headers=header)
        tree_token = obtain_token(session, tree_url.format(group, project_name, tag_name))
        tree_params = "{\"operationName\":\"pathLastCommit\",\"variables\":{\"projectPath\":\"" + project_path + "\",\"ref\":\"" + tag_name + "\",\"path\":\"\"},\"query\":\"query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {\\n  project(fullPath: $projectPath) {\\n    repository {\\n      tree(path: $path, ref: $ref) {\\n        lastCommit {\\n          sha\\n          title\\n          description\\n          message\\n          webUrl\\n          authoredDate\\n          authorName\\n          authorGravatar\\n          author {\\n            name\\n            avatarUrl\\n            webUrl\\n            __typename\\n          }\\n          signatureHtml\\n          pipelines(ref: $ref, first: 1) {\\n            edges {\\n              node {\\n                detailedStatus {\\n                  detailsPath\\n                  icon\\n                  tooltip\\n                  text\\n                  group\\n                  __typename\\n                }\\n                __typename\\n              }\\n              __typename\\n            }\\n            __typename\\n          }\\n          __typename\\n        }\\n        __typename\\n      }\\n      __typename\\n    }\\n    __typename\\n  }\\n}\\n\"}"
        header['x-csrf-token'] = tree_token
        tag_last_commit = session.request(method='POST', url=graphql, data=tree_params.encode('utf-8'), headers=header)
        flag = check_commit(branch, branch_last_commit, tag_name, tag_last_commit)
        if flag:
            return None
        suffix = '({})'
        count = 0
        while True:
            count = count + 1
            new_tag_name = tag_name + suffix.format(count)
            if new_tag_name not in tags:
                return new_tag_name
            else:
                tree_token = obtain_token(session, tree_url.format(group, project_name, new_tag_name))
                tree_params = "{\"operationName\":\"pathLastCommit\",\"variables\":{\"projectPath\":\"" + project_path + "\",\"ref\":\"" + new_tag_name + "\",\"path\":\"\"},\"query\":\"query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {\\n  project(fullPath: $projectPath) {\\n    repository {\\n      tree(path: $path, ref: $ref) {\\n        lastCommit {\\n          sha\\n          title\\n          description\\n          message\\n          webUrl\\n          authoredDate\\n          authorName\\n          authorGravatar\\n          author {\\n            name\\n            avatarUrl\\n            webUrl\\n            __typename\\n          }\\n          signatureHtml\\n          pipelines(ref: $ref, first: 1) {\\n            edges {\\n              node {\\n                detailedStatus {\\n                  detailsPath\\n                  icon\\n                  tooltip\\n                  text\\n                  group\\n                  __typename\\n                }\\n                __typename\\n              }\\n              __typename\\n            }\\n            __typename\\n          }\\n          __typename\\n        }\\n        __typename\\n      }\\n      __typename\\n    }\\n    __typename\\n  }\\n}\\n\"}"
                header['x-csrf-token'] = tree_token
                tag_last_commit = session.request(method='POST', url=graphql, data=tree_params.encode('utf-8'),
                                                  headers=header)
                flag = check_commit(branch, branch_last_commit, tag_name, tag_last_commit)
                if flag:
                    return None
    else:
        return tag_name


def login(session, token):
    params = {'utf8': '✓', 'username': git_user_name, 'password': git_pwd, 'remember_me': '1',
              'authenticity_token': token}
    response = session.post(login_url, params)
    if not is_success(response.status_code):
        raise Exception('登录git失败')
    soup = BeautifulSoup(response.content, 'lxml')
    user_name = soup.find_all(attrs={'class': 'user-name'})[0].text
    return user_name


def obtain_token(session, url):
    response = session.get(url)
    if not is_success(response.status_code):
        raise Exception('获取Token失败')
    content = response.text
    soup = BeautifulSoup(content, 'lxml')
    link = soup.find_all(attrs={'name': 'csrf-token'})[0]
    token = link['content']
    return token


if __name__ == '__main__':
    main()


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,000评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,745评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,561评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,782评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,798评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,394评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,952评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,852评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,409评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,483评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,615评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,303评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,979评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,470评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,571评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,041评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,630评论 2 359

推荐阅读更多精彩内容