65.9K
CodeProject 正在变化。 阅读更多。
Home

使用自动化将 Assembla 迁移到 Github

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (8投票s)

2019年9月30日

Apache

9分钟阅读

viewsIcon

9432

downloadIcon

105

如何将 Assembla 空间迁移到 Github,包括工单、贡献者、文件和源代码

引言

从一个代码管理和工单系统迁移到另一个系统可能非常痛苦。本文将介绍如何使用 3 个 Python 脚本、Assembla 的工单导出和 Github 仓库导入,从 Assembla 账户迁移到 Github 账户。

简而言之,这个过程包括:

  • 创建所有工单的备份(不幸的是,它不包含任何图片或附件)。
  • 从 Github 的新仓库导入 Assembla 仓库。
  • 根据本文的解释定制脚本。
  • 运行我们的脚本,它将通过自动化处理其余所有工作。

可选:

  • 更新 Github 仓库中的现有问题
  • 删除 Github 仓库中的所有问题

Assembla SVN

Assembla 是 Subversion 的代码托管提供商,提供托管和自托管的 SVN。在本文中,我们将把一个拥有 SVN 仓库的 Assembla 空间导出到拥有 Git 仓库的 Github 空间。为了本文的需要,我创建了一个测试 SVN,您可以在 这里找到它。

Assembla 工单

Assembla 有一个工单系统。除了文本之外,每个工单都可以包含我们需要导出的各种元素。其中一些元素当然是可选的,但如果存在,我们也希望将它们导出。

  1. 提交 - 对修订版(提交)的引用。此类引用通过在文本中插入 [[revision:1]] 来实现。
  2. 分配人 - 分配给该工单的用户。
  3. 相关工单 - Assembla 允许将一个工单与另一个工单关联,并定义它们之间的几种关系,例如“子”、“父”或“相关”。虽然我们可以使用对相关工单的引用作为工单正文和/或评论的一部分,并且它们可以导出到 Github 并从 Github 导入,但没有与 Assembla 相似的“相关问题”功能。
  4. 关注者 - 每个工单都可以有关注者,他们是其他有权访问该空间的用户。默认情况下,工单的创建者和分配人是关注者。

  5. 附件 - 每个工单都可以附加任何类型的文件。导出到 Github 时,某些文件可以按原样导入,而其他文件(例如:视频文件)必须先压缩为 .zip 文件。图片应作为工单的一部分内联显示。

第 1 步 - 导出 SVN

首先,您需要将 SVN 的地址复制到剪贴板。

Assembla 有时会以错误的格式显示地址。

格式应为:

例如

现在我们进入新的 Github 空间并导入它。

粘贴原始 Assembla SVN 的地址,然后按“**开始导入**”。

在此阶段,如果您的原始 Assembla 空间是私有的,系统会要求您输入 Assembla 的凭据,以便 Github 可以连接到您的旧空间并从中导入 SVN。

现在,导入完成后,您不仅成功传输了源代码,还传输了其所有修订。如果转到以下位置,您可以看到它们:

例如

第 2 步 - 导出工单

下一步是将工单导出到 Assembla 生成的 .bak 文件中。正如我们稍后将解释的,仅凭该文件不足以完成迁移,因为它不包含文件,包括作为工单一部分的图像。

首先转到工单设置。您可以使用以下链接:

或(因为您已经登录)

例如

或(如果您已登录)

按“**设置**”链接。

工单随后以类似 JSON 的自定义格式导出。工单附件未导出,我们需要一个自定义解决方案来导出附件。

首先,您会看到一条消息:“**备份已成功安排。**”,所以您需要返回此页面以收集导出的文件,该文件将以 .bak 结尾。

第 3 步 - 导出文件

您的空间 ID

我们的脚本会自动查找 space_id 标识符。此 ID 不是您为 Assembla 空间指定的名称,而是 Assembla 生成的字符和数字组合。space_idAssembla API 的主要构建块之一。

准备 Python 环境

其余过程的准备工作需要安装 Python 和几个库。

下载并安装 Python

  1. 使用以下 链接下载。
  2. 安装后,将安装位置的路径添加到 PATH 环境变量。

    默认位置将是:

    • C:\Users\<您的用户名>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Python 3.7-32

    您可以使用以下命令将此条目添加到 PATH

    set path "%path%;c:\Users\<YOUR USER NAME>\AppData\Local\Programs\Python\Python37-32"
  3. 打开命令提示符 (CMD) 并输入以下行:
    python -m pip install selenium pygithub requests webdriver_manager

    您可能会收到以下警告。为确保我们的脚本顺利运行,请添加以下条目:

    setx path "%path%;c:\Users\<YOUR USER NAME>\
    AppData\Local\Programs\Python\Python37-32\Scripts"

这将安装以下扩展:

运行 Assembla-Github

Assembla-Github_v5.py 是我们用于整个过程的 Python 脚本。

它连接到我们的 Assembla 账户,并根据我们在导出所有工单时生成的 .bak 文件,下载工单中引用的任何文件。

python Assembla-Github_v5.py -r user/repo --download

当我们的脚本扫描 .bak 文件时,您应该会看到以下响应:

首先,它会找出 space_id,然后打开一个带有 Assembla 网站的 Chrome 浏览器,以便您可以输入凭据。

在自动输入凭据后,它开始将数据导入 Github。

第 4 步 - 导入到 Github

下一步是在运行下一个 Python 脚本 Assembla-Github.py 之前进行的。

您需要编辑此脚本并添加/编辑以下部分:

添加凭据

在继续之前,请使用您的 Assembla 和 Github 用户名和密码更新 Credentias.py 文件。

class Credentials():
    github_user = <your Github user>
    github_password = <your Github password>
    assembla_user = <You Assembla user>
    assembla_password = <Your Assembla password>

添加所有贡献者

在以下代码块中,添加每个贡献者的 ID,用逗号分隔。如果不添加匹配的贡献者,“已创建的”问题”将显示“***”。请注意,问题是在 credentials.py 中使用的账户下生成的,因此它们会将凭据账户持有人视为“问题”的“创建者”,而有关每个原始工单/评论作者的信息将作为问题文本的一部分添加到后面。

val = {
#Place here your contributors, separated with commas. "user":"id"
"securedglobe": "c68pgUDuer4PiDacwqjQWU",
}

运行脚本

我们的脚本会扫描主文件夹内的子文件夹,因此我们可以选择我们想要处理的 Assembla 备份文件(.bak )。

解析工单

首先,我们根据 .bak 文件处理附件,从 Assembla 下载每个工单中提到的实际文件。为此,我们使用以下函数:

    print("Parsing tickets...")
    tickets_arr = []
    find_tickets = re.findall(r"^tickets,\s.*", tickets, re.MULTILINE)
    for item in find_tickets:
        arr = re.search(r"\[.*\]", item)
        fault_replace = str(arr.group(0)).replace(",null", ',"null"')
        array = ast.literal_eval(fault_replace)
        ticket_info = {
            "ticket_id": array[0],
            "ticket_number": array[1],
            "ticket_reporter_id": array[2],
            "ticket_assigned_id": array[3],
            "ticket_title": array[5],
            "ticket_priority": array[6],
            "ticket_description": array[7],
            "ticket_created_on": array[8],
            "ticket_milestone": array[10],
            "ticket_username": None,
            "status": "",
            "references": set(),
            "real_number": None,
            "ticket_comments": []
        }
        #["id","number","reporter_id",
        "assigned_to_id","space_id","summary",
        "priority","description",
        # "created_on","updated_at","milestone_id",
        "component_id","notification_list",
        # "completed_date","working_hours","is_story",
        "importance","story_importance","permission_type",
        # "ticket_status_id","state","estimate",
        "total_estimate","total_invested_hours","total_working_hours",
        # "status_updated_at","due_date","milestone_updated_at"]

        # get all comments belonging to specific ticket
        find_ticket_comments = re.findall(r"^ticket_comments,\s\[\d+\,
        {}.*".format(ticket_info["ticket_id"]), tickets, re.MULTILINE)
        for item in find_ticket_comments:
            arr = re.search(r"\[.*\]", item)
            fault_replace = re.sub(r",null", ',"null"', str(arr.group(0)))
            # transform comment array to python array
            array = ast.literal_eval(fault_replace)
            #ticket_comments:fields, ["id","ticket_id","user_id","created_on",
            "updated_at","comment","ticket_changes","rendered"]
            comment_info = {
                "id": array[0],
                "ticket_id": array[1],
                "user_id": array[2],
                "created_on": array[3],
                "updated_at": array[4],
                "comment": array[5],
                "ticket_changes": array[6],
                "rendered": array[7],
                "attachments": [],
                "username": None
            }
            ticket_info["ticket_comments"].append(comment_info)
    
        sorted_comments_array = sorted(
            ticket_info["ticket_comments"],
            key=lambda x: datetime.strptime(x['created_on'], '%Y-%m-%dT%H:%M:%S.000+00:00')
        )
        ticket_info["ticket_comments"] = sorted_comments_array
        tickets_arr.append(ticket_info)
    return tickets_arr

该函数返回一个包含所有工单的数组。

处理工单更新

每个 Assembla 工单(以及类似的每个 Github 问题)在很多方面都是一个“对话”,关注者和用户可以发表评论。下一个函数会扫描这些状态和评论,并将它们与原始工单关联起来。

# find statuses and link them to the original ticket
def parseStatus(tickets):
    print("Parsing tickets status data...")
    find_status = re.findall(r"^ticket_changes,\s.*", tickets, re.MULTILINE)
    status_arr = []
    for item in find_status:
        #ticket_changes:fields, ["id","ticket_comment_id",
        "subject","before","after",
        "extras","created_at","updated_at"]
        arr = re.search(r"\[.*\]", item)
        fault_replace = str(arr.group(0)).replace(",null", ',"null"')
        array = ast.literal_eval(fault_replace)
        ticket_status_info = {
            "id": array[0],
            "ticket_comment_id": array[1],
            "subject": array[2],
            "before": array[3],
            "after": array[4],
            "extras": array[5],
            "created_at": array[6],
            "updated_at": array[7],
            "ticket_comments": []
        }
        status_arr.append(ticket_status_info)
    return status_arr

处理工单附件

Assembla 中的每个工单可能包含一个或多个附件。如前所述,Assembla 对您可以附加的文件类型或大小没有限制,但是 Github 只允许某些文件类型成为“问题”的一部分,因此我们在脚本中定义了这些允许的类型,任何不属于这些类型的都会被打包成 .zip 文件然后上传。

EXTS = ['jpg', 'png', 'jpeg', 'docx', 'log', 'pdf', 'pptx', 'txt', 'zip']
# list of allowed extensions

从工单中获取的字段

以下字段是从每个工单中获取的:

  1. 工单号 - 我们需要考虑到,如果某个工单已从 Assembla 完全删除,而不仅仅被标记为“已修复”或“无效”,则导入到 Github 时,编号可能会有所不同。通过为每个已删除的 Assembla 工单添加“虚拟”工单来解决此问题,以保留编号。
  2. 报告人 ID - 此工单创建者的用户 ID
  3. 分配人 ID - 分配人的用户 ID
  4. 标题 - 工单的标题
  5. 优先级 - 工单的优先级
  6. 描述 - 工单正文的文本
  7. 创建日期 - 工单创建的日期
  8. 里程碑 - 如果工单分配了里程碑,则此处会显示
            "ticket_id": array[0],
            "ticket_number": array[1],
            "ticket_reporter_id": array[2],
            "ticket_assigned_id": array[3],
            "ticket_title": array[5],
            "ticket_priority": array[6],
            "ticket_description": array[7],
            "ticket_created_on": array[8],

            "ticket_milestone": array[10],

其他字段通过调用函数填充。

            "references": set(),
            "ticket_comments": []

为了处理所有附件,我们使用 **parseAttachmentsFromBak** 函数。

def parseAttachmentsFromBak(sid, tickets):
    filelist = []
    link = f"https://bigfiles.assembla.com/spaces/{sid}/documents/download/"
    # get all attachments from .bak file
    # save them in a separate list
    if not os.path.isfile('filenames.txt'):
        find_files = re.findall(r".*?\[\[(file|image):(.*?)(\|.*?)?\]\].*?", tickets)
        for file in find_files:
            if file:
                filelist.append(rf"{file[1]}")
    else:
        dirfile = glob.glob(f"{FILES_DIR}\**")
        with open("filenames.txt") as file:
            for line in file:
                filelist.append(line.strip())
        for file in dirfile:
            file = file.replace(f"{FILES_DIR}\\", "")
            file = file[:file.rfind(".")]
            for c, fi in enumerate(filelist):
                if fi in file:
                    del filelist[c]
                elif os.path.isfile(FILES_DIR+'\\'+fi):
                    del filelist[c]
    chrome_options = webdriver.ChromeOptions()
    path = os.path.dirname(os.path.realpath(__file__))
    chrome_options.add_experimental_option("prefs", {
    "download.default_directory": f"{path}\\temp",
    "download.prompt_for_download": False,
    "download.directory_upgrade": True,
    "safebrowsing.enabled": True
    })
    chrome_options.add_argument("user-data-dir=selenium") 
    chrome_options.add_argument("start-maximized")
    chrome_options.add_argument("--disable-infobars")
    try:
        driver = webdriver.Chrome(executable_path=ChromeDriverManager().install(), 
        options=chrome_options, service_log_path='NUL')
    except ValueError:
        print("Error opening Chrome. Chrome is not installed?")
        exit(1)
    FILE_SAVER_MIN_JS_URL = 
    "https://raw.githubusercontent.com/eligrey/FileSaver.js/master/src/FileSaver.js"
    file_saver_min_js = requests.get(FILE_SAVER_MIN_JS_URL).content
    driver.get("https://bigfiles.assembla.com/login")
    sleep(2)
    checklink = driver.execute_script("return document.URL;")
    if checklink == "https://bigfiles.assembla.com/login":
        login = driver.find_element_by_id("user_login")
        login.clear()
        login.send_keys(Credentials.assembla_user)
        passw = driver.find_element_by_id("user_password")
        passw.clear()
        passw.send_keys(Credentials.assembla_password)
        btn = driver.find_element_by_id("signin_button")
        btn.click()
        sleep(1)

    for file in filelist:
        # fetch all files from the filelist
        download_script = f"""
            return fetch('{file}',
                {{
                    "credentials": "same-origin",
                    "headers": {{"accept":"*/*;q=0.8",
                    "accept-language":"en-US,en;q=0.9"}},
                    "referrerPolicy": "no-referrer-when-downgrade",
                    "body": null,
                    "method": "GET",
                    "mode": "cors"
                }}
            ).then(resp => {{
                return resp.blob();
            }}).then(blob => {{
                saveAs(blob, '{file}');
            }});
            """
        driver.get(f"{link}{file}")
        sleep(1)
        try:
            # execute FileSaver.js if content == img
            loc = driver.find_element_by_tag_name('img')
            if loc:
                driver.execute_script(file_saver_min_js.decode("ascii"))
                driver.execute_script(download_script)
                WebDriverWait(driver, 120, 1).until(every_downloads_chrome)
            WebDriverWait(driver, 120, 1).until(every_downloads_chrome)
        except TimeoutException:
            pass
        except NoSuchElementException:
            pass
        except JavascriptException:
            pass
        sleep(8)
        temps = glob.glob(f"temp\**")
        try:
            for tm in temps:
                if tm.endswith('jpg'):
                    os.rename(tm, f"files\{file}.jpg")
                elif tm.endswith('png'):
                    os.rename(tm, f"files\{file}.png")
                elif tm.endswith('zip'):
                    os.rename(tm, f"files\{file}.zip")
                elif tm.endswith('pdf'):
                    os.rename(tm, f"files\{file}.pdf")
                elif tm.endswith('docx'):
                    os.rename(tm, f"files\{file}.docx")
                elif tm.endswith('txt'):
                    os.rename(tm, f"files\{file}.txt")
                else:
                    os.rename(tm, f"files\{file}")
        except FileExistsError:
            pass
    driver.close()
    driver.quit()

重命名附件文件

Assembla 中的工单附件以随机的字母数字名称(例如“c68pgUDuer4PiDacwqjQWU”)保存,我们需要将这些名称转换回每个工单附件的原始名称和扩展名。这可以通过 renameFiles 函数完成。

def renameFiles(sorted_tickets_array):
    print("Renaming files...")
    for item in sorted_tickets_array:
        for comment in item["ticket_comments"]:
            if comment["attachments"]:
                for attach in comment["attachments"]:
                    fname = attach["filename"]
                    fid = attach["file_id"]
                    if not fname.endswith('.png') and not fname.endswith('.jpg') \
                        and not fname.endswith('.PNG') and not fname.endswith('.JPG'):
                        dot = re.search(r"\..*", fname)
                        dot = "" if not dot else dot.group(0)
                        try:
                            get_file = glob.glob(f"{FILES_DIR}\{fid}.*")
                            if not get_file:
                                get_file = glob.glob(f"{FILES_DIR}\{fid}")
                            get_dot = re.search(r"\..*", get_file[0])
                            get_dot = "" if not get_dot else get_dot.group(0)
                            if get_dot and not dot:
                                dot = get_dot
                            if get_dot.endswith('.png') or 
                            get_dot.endswith('.jpg') or get_dot.endswith('.PNG') \
                                or get_dot.endswith('.JPG'):
                                pass
                            else:
                                if os.path.isfile(f"{FILES_DIR}\{fid}{dot}"):
                                    pass
                                else:
                                    print(f"Renaming: {fid} -> {fid}{dot}")
                                    os.rename(get_file[0], f"{FILES_DIR}\{fid}{dot}")
                                counter = 0
                                for ext in EXTS:
                                    if ext not in dot:
                                        counter += 1
                                    else:
                                        pass
                                if counter == len(EXTS) and not get_file[0].endswith(".htm"):
                                    # not attachable
                                    print(f"Making zip file -> {fid}.zip")
                                    if os.path.isfile(f"{FILES_DIR}\{fid}.zip"):
                                        os.remove(f"{FILES_DIR}\{fid}.zip")
                                    obj = zipfile.ZipFile(f"{FILES_DIR}\{fid}.zip", 'w')
                                    obj.write(f"{FILES_DIR}\{fid}{dot}")
                                    obj.close()
                        except Exception:
                            pass # doesn't exist

更新先前运行后的工单

如果您只需要更新现有仓库,并且已经从 Assembla 导出了数据,则可以使用 update 选项。

python Assembla-Github_v5.py -f <Your .bak file> -r <Github user>/<Github repo name> --update

例如

python Assembla-Github_v5.py 
-f My-Code-Project-Article-2019-09-27.bak -r haephrati/CodeProjectArticle-Test --update

然后,附加到工单的任何文件都将被下载并放置在名为“files”的文件夹中。无法直接作为“问题”添加到 Github 的文件类型将被压缩成 .zip 存档。

将数据上传到 Github

一切就绪后,我们可以将数据上传到 Github 仓库。我们可以更新现有仓库(其中已包含“问题”),或者从头开始为给定仓库创建所有问题。此类仓库必须已如前所述导入了仓库的源代码。

def uploadToGithub(dirfiles, tickets, working_repo):
    filelist = []
    ready_files = ""
    path = os.path.dirname(os.path.realpath(__file__))
    # filter attachments from .bak file to remove attachments not allowed or not existing
    find_files = re.findall(r".*?\[\[(file|image):(.*?)(\|.*?)?\]\].*?", tickets)
    
    for file in find_files:
        for dr in dirfiles:
            di = str(dr.replace(f"{FILES_DIR}\\", ""))
            di = di[:di.rfind('.')]
            if di in file[1]:
                filelist.append(f"{path}\{FILES_DIR}\{dr}")
    if os.path.isfile('files.txt'):
        print('files.txt exists, parsing existing links...')
        ex_files = ""
        # check for existing links and remove duplicates
        with open('files.txt', 'r') as file:
            ex_files = file.read()
        file_links = re.findall(r".*?\!\[(.*?)\]\((.*?)\).*?", ex_files)
        file_urls = re.findall(r".*?\[(.*?)\]\((.*?)\).*?", ex_files)
        get_img = re.findall(r"alt=\"(.*?)\"\ssrc=\"(.*?)\"", ex_files)
        file_links.extend(get_img)
        file_links.extend(file_urls)
        for flink in file_links:
            for co, fi in enumerate(filelist):
                if flink[0] in fi:
                    del filelist[co]
    if not filelist:
        print("uploadToGithub: Nothing to upload.")
        return 1
    # launch selenium to upload attachments to github camo
    chrome_options = Options()
    chrome_options.add_argument("user-data-dir=selenium") 
    chrome_options.add_argument("start-maximized")
    chrome_options.add_argument("--disable-infobars")
    try:
        driver = webdriver.Chrome(executable_path=ChromeDriverManager().install(), 
        options=chrome_options, service_log_path='NUL')
    except ValueError:
        print("Error opening Chrome. Chrome is not installed?")
        exit(1)
    driver.implicitly_wait(1000)
    driver.get(f"https://github.com/login")
    sleep(2)
    link = driver.execute_script("return document.URL;")
    if link == "https://github.com/login":
        login = driver.find_element_by_id("login_field")
        login.clear()
        login.send_keys(Credentials.github_user)
        passw = driver.find_element_by_id("password")
        passw.clear()
        passw.send_keys(Credentials.github_password)
        btn = driver.find_elements_by_xpath
        ("//*[@class='btn btn-primary btn-block']")
        btn[0].click()
        sleep(1)
    driver.get(f"https://github.com/{working_repo}/issues/")
    sleep(2)
    findButton = driver.find_elements_by_xpath("//*[@class='btn btn-primary']")
    findButton[0].click()
    sleep(2)
    # split filelist into chunks of 8 files
    chunks = [filelist[i:i + 8] for i in range(0, len(filelist), 8)]
    for chunk in chunks:
        chk = (' \n ').join(chunk)
        findBody = driver.find_element_by_id("issue_body")
        findBody.clear()
        findButton = driver.find_element_by_id("fc-issue_body")
        findButton.clear()
        if chk:
            findButton.send_keys(chk)
        print("Waiting for uploads to finish...")
        sleep(5)
        while True:
            chk = findBody.get_attribute('value')
            # [Uploading czo0qWjmmr5PZcdmr6CpXy.zip…]()
            if "]()" in chk:
                sleep(5)
            else:
                break
        # dump ready links with attachments to a separate file
        with open('files.txt', 'a+') as ff:
            ff.write(chk)
        ready_files += chk
    driver.close()
    driver.quit()
    return ready_files

结果

处理完成后,您应该会在新的 Github 仓库中看到您原始的 Assembla 工单(现在是问题),以及包含所有提交的源代码。然后,您可以看到对其他工单(问题)、附件和内联文件以及提交的引用的参考。

其他辅助函数

为了其他用途或需求,这里添加了一些附加函数到我们的脚本中:

删除仓库中的所有问题

通过使用 delete 参数,您可以删除给定仓库中的所有问题。

python Assembla-Github_v5.py -r user/repo --delete

例如

python Assembla-Github_v5.py -r haephrati/test --delete

以及执行此操作的源代码。

def deleteIssues(working_repo):
    chrome_options = Options()
    chrome_options.add_argument("user-data-dir=selenium") 
    chrome_options.add_argument("start-maximized")
    chrome_options.add_argument("--disable-infobars")
    try:
        driver = webdriver.Chrome(executable_path=ChromeDriverManager().install(), 
        options=chrome_options, service_log_path='NUL')
    except ValueError:
        print("Error opening Chrome. Chrome is not installed?")
        exit(1)
    driver.implicitly_wait(1000)
    driver.get(f"https://github.com/login")
    sleep(2)
    link = driver.execute_script("return document.URL;")
    if link == "https://github.com/login":
        login = driver.find_element_by_id("login_field")
        login.clear()
        login.send_keys(Credentials.github_user)
        passw = driver.find_element_by_id("password")
        passw.clear()
        passw.send_keys(Credentials.github_password)
        btn = driver.find_elements_by_xpath("//*[@class='btn btn-primary btn-block']")
        btn[0].click()
        sleep(1)
    driver.get(f"https://github.com/{working_repo}/issues/")
    while True:
        get_tab = driver.find_element_by_xpath
        ("//*[@class='js-selected-navigation-item 
        selected reponav-item']/child::*[@class='Counter']")
        if int(get_tab.text) == 0:
            print("No issues left. Exit.")
            break
        else:
            find_issues = driver.find_elements_by_xpath
            ("//*[@class='link-gray-dark v-align-middle no-underline h4 js-navigation-open']")
            link = find_issues[0].get_attribute("href")
            driver.get(link)
            find_notif = driver.find_elements_by_tag_name("summary")
            find_notif[len(find_notif)-1].click()
            sleep(1)
            find_button = driver.find_element_by_xpath
            ("//*[@class='btn btn-danger input-block float-none']")
            find_button.click()
            sleep(1)
            driver.get(f"https://github.com/{working_repo}/issues/")
    driver.close()
    driver.quit()

错误处理

在我们的脚本中,我们尝试处理可能的错误。

for ticket in sorted_tickets_array:
        try:
            issue_name = f"""{ticket["ticket_title"]}"""
            lock = None
            for check_issue in repo.get_issues(state='all'):
                if check_issue and check_issue.title == issue_name:
                    print("Issue exists; passing: [", check_issue.title, "]")
                    lock = 1
            if not lock:
                issue = createIssue(issue_name, ticket, repo, file_links)
                addComments(ticket, issue, file_links, repo)
        except RateLimitExceededException as e:
            # wait 1 hour for rate limit
            print(e, "Waiting 1 hour...")
            sleep(60*61)
            continue
        except Exception as e:
            print(e)
            pass

处理干扰自动化的安全措施

Github 与其他平台一样,已采取 措施来防止其 API 进行大规模操作,一段时间后,这些措施可能会阻止任何其他操作。我们识别这种情况,并在这种情况下将我们的操作暂停一小时。

这由以下异常处理:

except RateLimitExceededException as e: # wait 1 hour for rate limit print
       (e, "Waiting 1 hour...") sleep(60*61) continue 

关注点

历史

  • 2019 年 9 月 30 日:初始版本
© . All rights reserved.