From 876936e036ff6260900c435d10d93c5bba05a7dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E5=B0=8F=E5=A2=A8?=
 <2291200076@qq.com>
Date: Thu, 17 Mar 2022 01:16:26 +0800
Subject: [PATCH] =?UTF-8?q?=E5=8D=95=E4=B8=80py=E6=96=87=E4=BB=B6=E6=8B=86?=
 =?UTF-8?q?=E5=88=86=E4=B8=BApy=E6=96=87=E4=BB=B6=E6=A8=A1=E5=9D=97?=
 =?UTF-8?q?=EF=BC=8C=E5=8A=9F=E8=83=BD=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore                              |  18 +-
 assets/common.py                        |  13 +
 assets/defineWorkingDirCleanFunction.py |  20 +
 assets/giteeOauth.py                    |  94 +++++
 assets/githubOauth.py                   |  99 +++++
 assets/prepareWorkingDir.py             |  60 +++
 assets/readConfigFile.py                |  13 +
 assets/repoMatching.py                  | 111 ++++++
 assets/transferRepos.py                 |  86 ++++
 code.py                                 | 507 ++++++------------------
 config.example.ini                      |  26 ++
 mapping.sample.json                     |  15 +
 12 files changed, 685 insertions(+), 377 deletions(-)
 create mode 100644 assets/common.py
 create mode 100644 assets/defineWorkingDirCleanFunction.py
 create mode 100644 assets/giteeOauth.py
 create mode 100644 assets/githubOauth.py
 create mode 100644 assets/prepareWorkingDir.py
 create mode 100644 assets/readConfigFile.py
 create mode 100644 assets/repoMatching.py
 create mode 100644 assets/transferRepos.py
 create mode 100644 config.example.ini
 create mode 100644 mapping.sample.json

diff --git a/.gitignore b/.gitignore
index a6c57f5..0820af8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,17 @@
-*.json
+# Python 运行时文件
+__pycache__
+*.pyc
+
+# 临时文件
+temp
+
+# 需要用户自行配置的文件
+config.ini
+mapping.json
+
+# 用户运行时产生文件
+GlobalVars.json
+commands.bat
+commands.txt
+# 克隆的Git仓库
+*.git
\ No newline at end of file
diff --git a/assets/common.py b/assets/common.py
new file mode 100644
index 0000000..e1966d9
--- /dev/null
+++ b/assets/common.py
@@ -0,0 +1,13 @@
+import os
+import json
+
+def saveJSON(data, filename):
+    with open(filename, 'w', encoding='utf-8') as f:
+        json.dump(data, f, ensure_ascii=False, indent=4)
+
+def readJSON(filename):
+    with open(filename, 'r', encoding='utf-8') as f:
+        return json.load(f)
+
+def fileExists(filename):
+    return os.path.exists(filename)
\ No newline at end of file
diff --git a/assets/defineWorkingDirCleanFunction.py b/assets/defineWorkingDirCleanFunction.py
new file mode 100644
index 0000000..b365fd5
--- /dev/null
+++ b/assets/defineWorkingDirCleanFunction.py
@@ -0,0 +1,20 @@
+import signal
+
+def defineWorkingDirCleanFunction(saveJSONFunction):
+    """
+    定义Ctrl+C退出时 工作目录清理函数
+    """
+    def cleanWhenExit(signum, frame):
+        print()
+        print('程序正在清理工作目录,准备退出...')
+        os.rmdir(WorkingDir)
+        print('完成!')
+        print()
+        for countdown in range(3, 0, -1):
+            print('\r' + "{} 秒后退出{}".format(countdown, '.' * (4 - countdown)), end='')
+            time.sleep(1)
+        exit()
+    # 注册信号处理函数
+    signal.signal(signal.SIGINT, cleanWhenExit)
+    signal.signal(signal.SIGTERM, cleanWhenExit)
+    print("[info] 工作目录清理函数准备完成")
\ No newline at end of file
diff --git a/assets/giteeOauth.py b/assets/giteeOauth.py
new file mode 100644
index 0000000..440fbdf
--- /dev/null
+++ b/assets/giteeOauth.py
@@ -0,0 +1,94 @@
+import webbrowser
+import requests
+import time
+
+def giteeOauth(GiteeClientID, GiteeClientSecret, giteeProxies, timeout):
+    print("################################ Gitee 授权 ################################")
+    input("按回车开始进行Gitee账号授权:")
+
+    # ######################################## 获取Gitee用户的 access_token ########################################
+    # Api文档: https://gitee.com/api/v5/oauth_doc#/
+    # 认证地址
+    oauth_url = 'https://gitee.com/oauth/authorize?client_id={ClientID}&redirect_uri={redirect_uri}&response_type=code&scope=user_info%20projects' \
+        .format(ClientID=GiteeClientID, redirect_uri='https://www.only4.work/appHelper/gitee_show_code_param.php')
+
+    # 打开浏览器让用户授权
+    webbrowser.open(oauth_url)
+
+    # 让用户手动输入授权码
+    print("请在新打开的页面进行授权,授权完成后,请将授权码拷贝到此处,然后按回车继续")
+    code = input("您的授权码:")
+
+    r = None # 定义变量
+
+    tryTime = 10
+    while(tryTime > 0):
+        tryTime = tryTime - 1
+        try:
+            print("正在获取Gitee access_token...")
+            r = requests.post('https://gitee.com/oauth/token', params = {
+                "grant_type": "authorization_code",
+                "code": code,
+                "client_id": GiteeClientID,
+                "redirect_uri": "https://www.only4.work/appHelper/gitee_show_code_param.php",
+                "client_secret": GiteeClientSecret,
+            }, proxies = giteeProxies, timeout = timeout)
+            break
+        except Exception as err:
+            print(err)
+            print("出错啦,正在重试,还剩{}次机会".format(tryTime))
+            time.sleep(1)
+            continue
+
+    print("[info] Gitee账号授权完成")
+
+    # print(r.text)
+    access_token = r.json()['access_token']
+    print("您的access_token为:" + access_token)
+    # {"access_token":"83b5de53fc28079d80830983be587774","token_type":"bearer","expires_in":86400,"refresh_token":"6d445558db02909f8755383193bfb87648b897b6f92654e4f5d3e6ad61b0d515","scope":"user_info projects","created_at":1647013348}
+
+    # ######################################## 获得用户仓库信息 ########################################
+    # Api文档: https://gitee.com/api/v5/swagger#/getV5UserRepos
+    tryTime = 10
+    while(tryTime > 0):
+        tryTime = tryTime - 1
+        try:
+            r = requests.get('https://gitee.com/api/v5/user/repos', params = {
+                "access_token": access_token,
+                "visibility": "all",
+                "sort": "full_name",
+                "page": 1,
+                "per_page": 100,
+            }, proxies = giteeProxies, timeout = timeout)
+            break
+        except Exception as err:
+            print(err)
+            print("出错啦,正在重试,还剩{}次机会".format(tryTime))
+            time.sleep(1)
+            continue
+
+    # print(r.text)
+    print()
+    print("################################ 成功获取到您的仓库信息 ################################")
+    giteeRepos = {}
+    for repo in r.json():
+        print(repo['human_name'])
+        print(repo['html_url'])
+        print(repo['full_name'])
+        print()
+        # giteeRepos.append()
+        giteeRepos[repo['full_name']] = {
+            'human_name': repo['human_name'],
+            'html_url': repo['html_url'],
+            'ssh_url': repo['ssh_url'],
+            'full_name': repo['full_name'],
+            # 'path': repo['path'], # 仓库的路径  如 /only4/appHelper 的 appHelper
+            'name': repo['name'], # 仓库名称  如 仓库同步助手
+            # 'public': repo['public'], # 是否公开
+            'private': repo['private'], # 是否私有
+            # 'internal': repo['internal'], # 是否内部仓库
+            # 'empty_repo': repo['empty_repo'], # 是否为空仓库
+            'pushed_at': repo['pushed_at'], # 最后提交时间
+        }
+    print("[info] Gitee仓库读取完成")
+    return [r.json(), giteeRepos]
\ No newline at end of file
diff --git a/assets/githubOauth.py b/assets/githubOauth.py
new file mode 100644
index 0000000..6fdd00d
--- /dev/null
+++ b/assets/githubOauth.py
@@ -0,0 +1,99 @@
+import webbrowser
+import requests
+import time
+
+def githubOauth(GitHubClientID, GitHubClientSecret, githubProxies, timeout):
+    print("################################ GitHub 授权 ################################")
+    input("按回车开始进行GitHub账号授权:")
+
+    # ######################################## 获取GitHub用户的 access_token ########################################
+    # Api文档:
+    # https://docs.github.com/cn/developers/apps/building-oauth-apps/authorizing-oauth-apps
+    # https://docs.github.com/cn/developers/apps/building-oauth-apps/scopes-for-oauth-apps
+    # 认证地址
+    oauth_url = 'https://github.com/login/oauth/authorize?client_id={ClientID}&redirect_uri={redirect_uri}&response_type=code&scope=user,repo' \
+        .format(ClientID=GitHubClientID, redirect_uri='https://www.only4.work/appHelper/github_show_code_param.php')
+
+    # 打开浏览器让用户授权
+    webbrowser.open(oauth_url)
+
+    # 让用户手动输入授权码
+    print("请在新打开的页面进行授权,授权完成后,请将授权码拷贝到此处,然后按回车继续")
+    code = input("您的授权码:")
+
+    r = None # 定义变量
+
+    tryTime = 10
+    while(tryTime > 0):
+        tryTime = tryTime - 1
+        try:
+            r = requests.post('https://github.com/login/oauth/access_token', headers = {
+                'Accept': 'application/json'
+            }, params = {
+                "code": code,
+                "client_id": GitHubClientID,
+                "redirect_uri": "https://www.only4.work/appHelper/github_show_code_param.php",
+                "client_secret": GitHubClientSecret,
+            }, proxies = githubProxies, timeout = timeout)
+            break
+        except Exception as err:
+            print(err)
+            print("出错啦,正在重试,还剩{}次机会".format(tryTime))
+            time.sleep(1)
+            continue
+
+    print("[info] GitHub账号授权完成")
+
+    # print(r.text)
+    access_token = r.json()['access_token']
+    print("您的access_token为:" + access_token)
+    # {"access_token":"ghu_NgIJEFtrQz4FtTqfewVaHlR9Xnb30R26oMwM","expires_in":28800,"refresh_token":"ghr_mu8iw6A33ae1AoIo3hMFVX7VssbPmGIlfSKyc2CTQIPootRSMnr48c3WVevQpYfwLL9MaQ0vWvTR","refresh_token_expires_in":15897600,"token_type":"bearer","scope":""}
+
+    # ######################################## 获得用户仓库信息 ########################################
+    tryTime = 10
+    while(tryTime > 0):
+        tryTime = tryTime - 1
+        try:
+            # Api文档: https://docs.github.com/cn/rest/reference/repos#list-repositories-for-the-authenticated-user
+            r = requests.get('https://api.github.com/user/repos', headers = {
+                'Accept': 'application/vnd.github.v3+json',
+                "Authorization": "token " + access_token,
+                # 👆 https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/
+            }, params = {
+                "access_token": access_token,
+                "visibility": "all",
+                "sort": "full_name",
+                "page": 1,
+                "per_page": 100,
+            }, proxies = githubProxies, timeout = timeout)
+            break
+        except Exception as err:
+            print(err)
+            print("出错啦,正在重试,还剩{}次机会".format(tryTime))
+            time.sleep(1)
+            continue
+
+    # print(r.text)
+    print()
+    print("################################ 成功获取到您的仓库信息 ################################")
+    githubRepos = {}
+    for repo in r.json():
+        print(repo['full_name'])
+        print(repo['html_url'])
+        # githubRepos.append()
+        githubRepos[repo['full_name']] = {
+            # 'human_name': repo['human_name'],
+            'html_url': repo['html_url'],
+            'ssh_url': repo['ssh_url'],
+            'full_name': repo['full_name'],
+            # 'path': repo['path'], # 仓库的路径  如 /only4/appHelper 的 appHelper
+            'name': repo['name'], # 仓库名称  如 仓库同步助手
+            # 'public': repo['public'], # 是否公开
+            'private': repo['private'], # 是否私有
+            # 'internal': repo['internal'], # 是否内部仓库
+            # 'empty_repo': repo['empty_repo'], # 是否为空仓库
+            'pushed_at': repo['pushed_at'], # 最后提交时间
+        }
+        print()
+    print("[info] GitHub仓库读取完成")
+    return [r.json(), githubRepos]
diff --git a/assets/prepareWorkingDir.py b/assets/prepareWorkingDir.py
new file mode 100644
index 0000000..af6c9ff
--- /dev/null
+++ b/assets/prepareWorkingDir.py
@@ -0,0 +1,60 @@
+import os;
+import shutil;
+
+def prepareWorkingDir(CurrentDir, WorkingDir):
+    """
+    准备工作目录
+    """
+    print("################################ 正在准备工作目录 ################################")
+    print()
+    print("当前目录", CurrentDir) # 获取当前文件所在目录
+    print("工作目录", WorkingDir)
+    # print(os.getcwd()) # 获取当前脚本运行目录
+
+    if os.path.exists(WorkingDir):
+        pass
+        # # 工作目录已存在,如果非空就创建
+        # if not os.listdir(WorkingDir):
+        #     print("工作目录已存在且为空,无需创建")
+        # else:
+        #     # print('工作目录已存在且非空,请检查是否有其他程序正在使用,如果确认无其他程序在使用,请手动删除工作目录,然后重试!')
+        #     # print()
+        #     # print("是否删除工作目录中的文件? (Y: 删除, N: 不删除)")
+        #     # userInput = ''
+        #     # while userInput == '':
+        #     #     userInput = input("\r>").strip().lower ()
+        #     # if userInput in ['y', 'yes']:
+        #     #     # os.rmdir(WorkingDir) # 只能删除空文件夹
+        #     #     shutil.rmtree(WorkingDir)    #递归删除文件夹
+        #     #     os.mkdir(WorkingDir)
+        #     #     print("成功清空工作目录", WorkingDir)
+        #     # else:
+        #     #     input('按回车键退出...')
+        #     #     exit()
+        #     print()
+        #     print('工作目录已存在且非空,是否在上次同步的基础上继续?')
+        #     while True:
+        #         userInput = input("y: 继续, n: 清空工作目录 (y): ").strip().lower ()
+        #         if userInput in ['', 'y', 'yes']:
+        #             break
+        #         elif userInput in ['n', 'no']:
+        #             # os.rmdir(WorkingDir) # 只能删除空文件夹
+        #             shutil.rmtree(WorkingDir)    #递归删除文件夹
+        #             os.mkdir(WorkingDir)
+        #             print("成功清空工作目录", WorkingDir)
+        #             break
+        #         else:
+        #             input('按回车键退出...')
+        #             exit()
+    else:
+        # 工作目录不存在,创建该目录
+        os.mkdir(WorkingDir)
+        print("成功创建工作目录", WorkingDir)
+    print("[info] 工作目录准备完成")
+
+
+    # os.mkdir(WorkingDir)
+    # os.makedirs(WorkingDir)
+
+    # os.system("chdir")
+    # print(__file__)
\ No newline at end of file
diff --git a/assets/readConfigFile.py b/assets/readConfigFile.py
new file mode 100644
index 0000000..92b48fe
--- /dev/null
+++ b/assets/readConfigFile.py
@@ -0,0 +1,13 @@
+import configparser
+
+def readConfigFile(configFilePath):
+    # 可以不考虑配置文件不存在的情况
+
+    #  实例化configParser对象
+    config = configparser.ConfigParser()
+    # -read读取ini文件
+    config.read(configFilePath, encoding='UTF-8') # GB18030
+    # -sections得到所有的section,并以列表的形式返回
+    # configSections = config.sections()
+    return config
+    print("[info] 配置文件读取完成")
\ No newline at end of file
diff --git a/assets/repoMatching.py b/assets/repoMatching.py
new file mode 100644
index 0000000..3eae908
--- /dev/null
+++ b/assets/repoMatching.py
@@ -0,0 +1,111 @@
+import copy
+
+def repoMatching(repo1, repo2):
+    # 对字典进行深拷贝
+    repo1 = copy.deepcopy(repo1)
+    repo2 = copy.deepcopy(repo2)
+
+    # 配对字典
+    matchList = {
+        'match': [],
+        'mismatch': {
+            'repo1': [],
+            'repo2': [],
+        },
+    }
+
+    # 定义映射决策
+    from .common import readJSON, fileExists
+    if fileExists("mapping.json"):
+        mapping = readJSON("mapping.json")
+        orgNameMap = mapping.get('orgNameMap')
+        repoNameMap = mapping.get('repoNameMap')
+        fullNameMap = mapping.get('fullNameMap')
+    else:
+        mapping = None # 不需要映射
+    def mapRepoName(fullName):
+        if mapping == None:
+            return fullName
+        elif fullName in fullNameMap.keys():
+            return fullNameMap.get(fullName)
+        else:
+            [orgName, repoName] = fullName.split('/')
+            if orgName in orgNameMap.keys():
+                orgName = orgNameMap.get(orgName)
+            if repoName in repoNameMap.keys():
+                repoName = repoNameMap.get(repoName)
+            return orgName + "/" + repoName
+    # # 测试
+    # print(mapRepoName("only4/thisisarepo"))
+    # print(mapRepoName("only-4/this-is-a-repo"))
+    # print(mapRepoName("only4/this-is-a-repo"))
+    # print(mapRepoName("only-4/thisisarepo"))
+    # print(mapRepoName("only4/repo1"))
+    # exit()
+
+    # def judgeRepoEqual(name1, name2):
+    #     if mapping == None: # 没有配置映射
+    #         return name1 == name2
+    #     else:
+    #         for i in fullNameMap: # 比较 full_name 映射 比如 org1/repo1 -> org2/repo2
+    #             if name1 == i[0]:
+    #                 if name2 == i[1]:
+    #                     return True # 如果 name1 和 name2 在 full_name 映射中,则匹配
+    #                 else:
+    #                     return False # 如果 name1 在 full_name 映射中,但是 name2 不在,则不匹配
+
+    #         # 接下来同时比较 org repo 映射,将 org1 repo1进行映射,如果 name2 与 (name1+映射)相等,则匹配
+    #         [org1, repo1] = name1.split('/')
+    #         [org2, repo2] = name2.split('/')
+    #         for i in orgNameMap: # 将 org1 进行映射    备注:如果不存在映射,则相当于始终没有进这个for循环中的if条件
+    #             if org1 == i[0]:
+    #                 org1 = i[1]
+    #                 break
+    #         if org1 != org2: # 如果 org1 和 org2 不相等,则一定不匹配
+    #             return False
+
+    #         # org1 与 org2 映射后相等,现在比较 repo1 和 repo2
+    #         for i in repoNameMap: # 将 repo1 进行映射    备注:如果不存在映射,则相当于始终没有进这个for循环中的if条件
+    #             if repo1 == i[0]:
+    #                 repo1 = i[1]
+    #                 break
+    #         if org1 != org2: # 如果 org1 和 org2 不相等,则一定不匹配
+    #             return False
+    #         return True
+    # # 测试
+    # print(judgeRepoEqual("only4/thisisarepo", "only-4/this-is-a-repo")) # True
+    # print(judgeRepoEqual("only4/thisisarepo", "only4/this-is-a-repo")) # False
+    # print(judgeRepoEqual("only4/thisisarepo", "only-4/thisisarepo")) # True
+    # print(judgeRepoEqual("only4/thisisarepo", "only4/thisisarepo")) # False
+    # print(judgeRepoEqual("only4/repo1", "only5/repo2")) # True
+    # exit()
+    for repo1FullName in repo1.keys(): # 遍历repo1仓库名
+        # print(repo1FullName)
+        repo1FullName_map = mapRepoName(repo1FullName)
+        if(repo1FullName_map in repo2.keys()): # 如果在repo2中,则说明匹配成功
+            matchList.get('match').append({
+                'from': repo1[repo1FullName],
+                'to': repo2[repo1FullName_map],
+            })
+            del repo2[repo1FullName_map]
+        else:
+            matchList.get('mismatch')['repo1'].append(repo1[repo1FullName])
+
+    matchList.get('mismatch')['repo2'] = list(repo2.values())
+    return matchList
+
+def printMatchintInfo(matchList, repo1Name = "repo1", repo2Name = "repo2"):
+    print("################## 以下仓库不会同步 ################## ")
+    print("{repo1Name} 有但是 {repo2Name} 没有的仓库".format(repo1Name = repo1Name, repo2Name = repo2Name))
+    for i in matchList.get('mismatch').get('repo1'):
+        print('    ' + i['html_url'])
+    print()
+    print("{repo2Name} 有但是 {repo1Name} 没有的仓库".format(repo1Name = repo1Name, repo2Name = repo2Name))
+    for i in matchList.get('mismatch').get('repo2'):
+        print('    ' + i['html_url'])
+    print()
+    print("################## 以下仓库会被同步 ################## ")
+    print("{repo1Name} 与 {repo2Name} 匹配相同的仓库".format(repo1Name = repo1Name, repo2Name = repo2Name))
+    for i in matchList.get('match'):
+        print('    ' + i.get("from").get("full_name").ljust(30,' ') + " -> " + i.get("to").get("full_name"))
+    print()
diff --git a/assets/transferRepos.py b/assets/transferRepos.py
new file mode 100644
index 0000000..804334a
--- /dev/null
+++ b/assets/transferRepos.py
@@ -0,0 +1,86 @@
+import os
+
+def transferRepos(matchList, WorkingDir, fromRepoProtocol = 'https', toRepoProtocol = 'https'):
+    # print(matchList)
+    # 切换路径
+    targetPath = os.path.abspath(WorkingDir)
+    os.chdir(targetPath)
+    if(os.path.abspath(os.getcwd()) != targetPath): # 展开为绝对路径,然后进行比较
+        print("[error] 切换路径失败")
+        print("当前路径", os.path.abspath(os.getcwd()))
+        print("想要切换到的路径", targetPath)
+        input("按回车键退出...")
+        exit()
+
+    commands = []
+    commands.append("@echo off") # echo off
+    # commands.append("cls") # 清屏
+    # commands.append('')
+    for repo in matchList:
+        # 查看当前目录
+        # commands.append('echo 当前目录')
+        # commands.append('chdir')
+        # 克隆仓库
+        localRepoFolder = repo['from']['full_name'].split('/')[-1] + ".git"
+        if not os.path.exists(WorkingDir + "/" + localRepoFolder):
+            print(WorkingDir + "/" + localRepoFolder)
+            repo_url = repo['from']['html_url']
+            if toRepoProtocol != 'https':
+                repo_url = repo['from']['ssh_url']
+            # commands.append('echo 克隆仓库')
+            commands.append("git clone --mirror {repo_url}".format(repo_url = repo_url))
+        # 切换到仓库目录
+        # commands.append('echo 切换到仓库目录')
+        commands.append("cd {folder_name}".format(folder_name = localRepoFolder))
+        # 更新本地仓库
+        # 不可以使用 git fetch --all  如果仓库中有hidden ref,则推送时会报错
+        # commands.append('echo 更新本地仓库')
+        commands.append("git remote update")
+        # 本地存储库GC (没有必要)
+        # commands.append("git gc")
+        # 同步仓库
+        repo_url = repo['to']['html_url']
+        if toRepoProtocol != 'https':
+            repo_url = repo['to']['ssh_url']
+        # commands.append('echo 推送仓库到远程({repo_url})'.format(repo_url = repo_url))
+        commands.append("git push --mirror {repo_url}".format(repo_url = repo_url))
+        # 切换回上一级目录
+        # commands.append('echo 回到工作目录')
+        commands.append("cd ../")
+        # commands.append('echo 当前仓库克隆完成,等待用户确认,按任意键进行下一步操作 & pause')
+        # commands.append("pause")
+        # 空行
+        commands.append('')
+    commands.append('echo 命令执行完成')
+    commands.append("pause")
+
+    print("本项目还处于测试阶段,出于安全考虑,我们采用生成命令文件的方式对仓库进行操作,以免",
+          "由于脚本错误造成数据丢失。我们强烈建议您在继续前先手动备份您的仓库,以免丢失代码。",
+          "由于代码错误或您自己失误造成的代码仓库丢失,项目开发者不承担责任。在执行脚本前,请",
+          "务必确认您知晓该行命令的执行结果,切勿盲目执行您不知道的命令!", sep = "\n")
+    print("\033[1;37;41m继续前请务全量必备份仓库!\033[0m")
+    print("\033[1;37;41m继续前请务全量必备份仓库!\033[0m")
+    print("\033[1;37;41m继续前请务全量必备份仓库!\033[0m")
+    print("继续操作代表您已阅读上述内容,程序将在工作目录下生成一个批处理脚本")
+    if input("按<回车>键继续,或输入run直接执行(不推荐): ") == "run":
+        for commandForExecute in commands:
+            print("[正在执行]", commandForExecute)
+            os.system(commandForExecute)
+    else:
+        commandTxtPath = os.path.abspath(WorkingDir + "/commands.bat")
+        f=open(commandTxtPath, "w")
+        f.write('\n'.join(commands))
+        f.close()
+        print("命令文件生成完毕,请查看:", commandTxtPath)
+
+    # for command in commands:
+    #     print(command)
+    #     os.system(command)
+
+    # :: 创建文件夹
+    # mkdir D:\gitTransTempDir
+
+    # :: 如果之前有没删除的话就删除
+    # rd /s /q ./chrome-extension.git
+
+    # rd /s /q D:\gitTransTempDir
\ No newline at end of file
diff --git a/code.py b/code.py
index 2efadeb..0b88704 100644
--- a/code.py
+++ b/code.py
@@ -24,417 +24,172 @@
 #   但受由于执行环境的不同,不排除脚本出现错误等情况。
 #   若因脚本执行错误或者您的失误导致数据丢失,我们不承担责任,感谢您的理解!
 # #####################################################################################
+
 import os # 执行系统命令
 import requests # 发送请求
-import signal
+import signal # 捕获Ctrl+C退出信号
 import webbrowser # 打开浏览器
 import time # 时间模块 (倒计时退出,创建带时间戳的工作目录)
 import shutil # 文件夹操作 (递归删除文件夹)
 import json # 读写JSON
-
-# ######################################## 配置 ########################################
-# Gitee
-GiteeClientID = 'ce043c6768fcddf97b334f5d3615ff20e9448eedf8a0c2701015de3e831f3f59' # 'YOUR_CLIENT_ID'
-GiteeClientSecret = '334742850f8dd650239cc8d3c7522090d22c6f7aeab12a340a532bc8de0d2b0d' # 'YOUR_CLIENT_SECRET'
-
-# GitHub
-GitHubClientID = 'Iv1.0c2a4dbf1b897e8f' # 'YOUR_CLIENT_ID'
-GitHubClientSecret = 'bcc97770136b22abce5f4bb0444df0621c4386b2' # 'YOUR_CLIENT_SECRET'
-
-# Gitee请求代理,默认为不使用代理  { "http": None, "https": None }
-giteeProxies = { "http": None, "https": None }
-
-# GitHub请求代理,默认为使用本地代理  { "http": "127.0.0.1:15732", "https": "127.0.0.1:15732" }
-githubProxies = { "http": "127.0.0.1:15732", "https": "127.0.0.1:15732" }
-# python3 requests使用proxy代理,cookies https://www.cnblogs.com/lshan/p/11878638.html
-
-# GitHub请求超时时间
-timeout = 5
+import configparser # 读取ini配置文件
+import copy # 字典深拷贝
 
 # ######################################## 工作路径 ########################################
 # 当前目录
 CurrentDir = os.path.dirname(os.path.abspath(__file__))
 
 # 临时工作目录
-TempWorkingDirName = "TempWorkingDir" # './temp-' + str(int(time.time() * 10 ** 6))
-TempWorkingDir = os.path.abspath(os.path.join(CurrentDir, TempWorkingDirName))
+WorkingDirName = "WorkingDir" # './temp-' + str(int(time.time() * 10 ** 6))
+WorkingDir = os.path.abspath(os.path.join(CurrentDir, WorkingDirName))
 
 # 设置保存路径
-GlobalVarsSavePath = os.path.abspath(os.path.join(TempWorkingDir, "../GlobalVars.json"))
-RepoMatchSavePath = os.path.abspath(os.path.join(TempWorkingDir, "../RepoMatch.json"))
+GlobalVarsSavePath = os.path.abspath(os.path.join(WorkingDir, "GlobalVars.json"))
 
-# ######################################## 全局变量 ########################################
-giteeRepos = {}
-githubRepos = {}
+# 准备工作目录
+from assets.prepareWorkingDir import prepareWorkingDir
+prepareWorkingDir(CurrentDir = CurrentDir, WorkingDir = WorkingDir)
 
-GlobalVars = {
-    'lastRunTime': time.time(),
-    'giteeRepos': giteeRepos,
-    'githubRepos': githubRepos
-}
+# ######################################## 读取配置 ########################################
+# 读取配置文件
+from assets.readConfigFile import readConfigFile
+config = readConfigFile(os.path.abspath("config.ini"))
 
-def prepareWorkingDir():
-    """
-    准备工作目录
-    """
-    print("################################ 正在准备工作目录 ################################")
-    print("当前目录", CurrentDir) # 获取当前文件所在目录
-    print("工作目录", TempWorkingDir)
-    # print(os.getcwd()) # 获取当前脚本运行目录
+if not 'Gitee' in config or not 'GitHub' in config:
+    print("[error] 配置文件读取失败,请检查配置文件是否正确")
+    exit()
 
-    if os.path.exists(TempWorkingDir):
-        # 工作目录已存在,如果非空就创建
-        if not os.listdir(TempWorkingDir):
-            print("工作目录已存在且为空,无需创建")
-        else:
-            print('工作目录已存在且非空,请检查是否有其他程序正在使用,如果确认无其他程序在使用,请手动删除工作目录,然后重试!')
-            print()
-            print("是否删除工作目录中的文件? (Y: 删除, N: 不删除)")
-            userInput = ''
-            while userInput == '':
-                userInput = input("\r>").strip().lower ()
-            if userInput in ['y', 'yes']:
-                # os.rmdir(TempWorkingDir) # 只能删除空文件夹
-                shutil.rmtree(TempWorkingDir)    #递归删除文件夹
-                os.mkdir(TempWorkingDir)
-                print("成功清空工作目录", TempWorkingDir)
-            else:
-                input('按回车键退出...')
-                exit()
-    else:
-        # 工作目录不存在,创建该目录
-        os.mkdir(TempWorkingDir)
-        print("成功创建工作目录", TempWorkingDir)
+if not 'ClientID' in config['Gitee'] or not 'ClientSecret' in config['Gitee']:
+    print("[error] Gitee ClientID或ClientSecret配置错误,请检查配置文件是否正确")
+    exit()
+if not 'ClientID' in config['GitHub'] or not 'ClientSecret' in config['GitHub']:
+    print("[error] GitHub ClientID或ClientSecret配置错误,请检查配置文件是否正确")
+    exit()
 
+# ClientID 与 ClientSecret
+# Gitee
+GiteeClientID = config['Gitee']['ClientID']
+GiteeClientSecret = config['Gitee']['ClientSecret']
+# GitHub
+GitHubClientID =config['GitHub']['ClientID']
+GitHubClientSecret = config['GitHub']['ClientSecret']
 
-    # os.mkdir(TempWorkingDir)
-    # os.makedirs(TempWorkingDir)
+# Protocol: https or ssh
+GiteeProtocol = 'https'
+GitHubProtocol = 'https'
+# Gitee
+if 'Protocol' in config['Gitee'] and config['Gitee']['Protocol'] == 'ssh':
+    GiteeProtocol = "ssh"
+# GitHub
+if 'Protocol' in config['GitHub'] and config['GitHub']['Protocol'] == 'ssh':
+    GitHubProtocol = "ssh"
 
-    # os.system("chdir")
-    # print(__file__)
+# 代理
+# Gitee请求代理,默认为不使用代理  { "http": None, "https": None }
+giteeProxies = { "http": None, "https": None }
+if 'Proxy' in config['Gitee'] and config['Gitee']['Proxy'] != "":
+    giteeProxies['http'] = config['Gitee']['Proxy']
+    giteeProxies['https'] = config['Gitee']['Proxy']
+# GitHub请求代理,默认为使用本地代理  { "http": "127.0.0.1:15732", "https": "127.0.0.1:15732" }
+# python3 requests使用proxy代理,cookies https://www.cnblogs.com/lshan/p/11878638.html
+githubProxies = { "http": None, "https": None }
+if 'Proxy' in config['GitHub'] and config['GitHub']['Proxy'] != "":
+    githubProxies['http'] = config['GitHub']['Proxy']
+    githubProxies['https'] = config['GitHub']['Proxy']
 
-def defineWorkingDirCleanFunction():
-    """
-    定义Ctrl+C退出时 工作目录清理函数
-    """
-    def cleanWhenExit(signum, frame):
-        print()
-        print('程序正在清理工作目录,准备退出...')
-        os.rmdir(TempWorkingDir)
-        print('完成!')
-        print()
-        for countdown in range(3, 0, -1):
-            print('\r' + "{} 秒后退出{}".format(countdown, '.' * (4 - countdown)), end='')
-            time.sleep(1)
-        exit()
-    # 注册信号处理函数
-    signal.signal(signal.SIGINT, cleanWhenExit)
-    signal.signal(signal.SIGTERM, cleanWhenExit)
-
-def giteeOauth():
-    print("################################ Gitee 授权 ################################")
-    input("按回车开始进行Gitee账号授权:")
-
-    # ######################################## 获取Gitee用户的 access_token ########################################
-    # Api文档: https://gitee.com/api/v5/oauth_doc#/
-    # 认证地址
-    oauth_url = 'https://gitee.com/oauth/authorize?client_id={ClientID}&redirect_uri={redirect_uri}&response_type=code&scope=user_info%20projects' \
-        .format(ClientID=GiteeClientID, redirect_uri='https://www.only4.work/appHelper/gitee_show_code_param.php')
-
-    # 打开浏览器让用户授权
-    webbrowser.open(oauth_url)
-
-    # 让用户手动输入授权码
-    print("请在新打开的页面进行授权,授权完成后,请将授权码拷贝到此处,然后按回车继续")
-    code = input("您的授权码:")
-
-    while(True):
-        try:
-            r = requests.post('https://gitee.com/oauth/token', params = {
-                "grant_type": "authorization_code",
-                "code": code,
-                "client_id": GiteeClientID,
-                "redirect_uri": "https://www.only4.work/appHelper/gitee_show_code_param.php",
-                "client_secret": GiteeClientSecret,
-            }, proxies = giteeProxies, timeout = timeout)
-            break
-        except:
-            continue
-
-    # print(r.text)
-    access_token = r.json()['access_token']
-    print("您的access_token为:" + access_token)
-    # {"access_token":"83b5de53fc28079d80830983be587774","token_type":"bearer","expires_in":86400,"refresh_token":"6d445558db02909f8755383193bfb87648b897b6f92654e4f5d3e6ad61b0d515","scope":"user_info projects","created_at":1647013348}
-
-    # ######################################## 获得用户仓库信息 ########################################
-    # Api文档: https://gitee.com/api/v5/swagger#/getV5UserRepos
-    while(True):
-        try:
-            r = requests.get('https://gitee.com/api/v5/user/repos', params = {
-                "access_token": access_token,
-                "visibility": "all",
-                "sort": "full_name",
-                "page": 1,
-                "per_page": 100,
-            }, proxies = giteeProxies, timeout = timeout)
-            break
-        except:
-            continue
-
-    # print(r.text)
-    print()
-    print("################################ 成功获取到您的仓库信息 ################################")
-    for repo in r.json():
-        print(repo['human_name'])
-        print(repo['html_url'])
-        print(repo['full_name'])
-        print()
-        # giteeRepos.append()
-        giteeRepos[repo['full_name']] = {
-            'human_name': repo['human_name'],
-            'html_url': repo['html_url'],
-            'full_name': repo['full_name'],
-            # 'path': repo['path'], # 仓库的路径  如 /only4/appHelper 的 appHelper
-            'name': repo['name'], # 仓库名称  如 仓库同步助手
-            # 'public': repo['public'], # 是否公开
-            'private': repo['private'], # 是否私有
-            # 'internal': repo['internal'], # 是否内部仓库
-            # 'empty_repo': repo['empty_repo'], # 是否为空仓库
-            'pushed_at': repo['pushed_at'], # 最后提交时间
-        }
-    print("######################################################################################")
-
-def githubOauth():
-    print("################################ GitHub 授权 ################################")
-    input("按回车开始进行GitHub账号授权:")
-
-    # ######################################## 获取GitHub用户的 access_token ########################################
-    # Api文档:
-    # https://docs.github.com/cn/developers/apps/building-oauth-apps/authorizing-oauth-apps
-    # https://docs.github.com/cn/developers/apps/building-oauth-apps/scopes-for-oauth-apps
-    # 认证地址
-    oauth_url = 'https://github.com/login/oauth/authorize?client_id={ClientID}&redirect_uri={redirect_uri}&response_type=code&scope=user,repo' \
-        .format(ClientID=GitHubClientID, redirect_uri='https://www.only4.work/appHelper/github_show_code_param.php')
-
-    # 打开浏览器让用户授权
-    webbrowser.open(oauth_url)
-
-    # 让用户手动输入授权码
-    print("请在新打开的页面进行授权,授权完成后,请将授权码拷贝到此处,然后按回车继续")
-    code = input("您的授权码:")
-
-    while(True):
-        try:
-            r = requests.post('https://github.com/login/oauth/access_token', headers = {
-                'Accept': 'application/json'
-            }, params = {
-                "code": code,
-                "client_id": GitHubClientID,
-                "redirect_uri": "https://www.only4.work/appHelper/github_show_code_param.php",
-                "client_secret": GitHubClientSecret,
-            }, proxies = githubProxies, timeout = timeout)
-            break
-        except:
-            continue
-
-    print(r.text)
-    access_token = r.json()['access_token']
-    print("您的access_token为:" + access_token)
-    # {"access_token":"ghu_NgIJEFtrQz4FtTqfewVaHlR9Xnb30R26oMwM","expires_in":28800,"refresh_token":"ghr_mu8iw6A33ae1AoIo3hMFVX7VssbPmGIlfSKyc2CTQIPootRSMnr48c3WVevQpYfwLL9MaQ0vWvTR","refresh_token_expires_in":15897600,"token_type":"bearer","scope":""}
-
-    # ######################################## 获得用户仓库信息 ########################################
-    while(True):
-        try:
-            # Api文档: https://docs.github.com/cn/rest/reference/repos#list-repositories-for-the-authenticated-user
-            r = requests.get('https://api.github.com/user/repos', headers = {
-                'Accept': 'application/vnd.github.v3+json',
-                "Authorization": "token " + access_token,
-                # 👆 https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/
-            }, params = {
-                "access_token": access_token,
-                "visibility": "all",
-                "sort": "full_name",
-                "page": 1,
-                "per_page": 100,
-            }, proxies = githubProxies, timeout = timeout)
-            break
-        except:
-            continue
-
-    # print(r.text)
-    print()
-    print("################################ 成功获取到您的仓库信息 ################################")
-    for repo in r.json():
-        print(repo['full_name'])
-        print(repo['html_url'])
-        # githubRepos.append()
-        githubRepos[repo['full_name']] = {
-            # 'human_name': repo['human_name'],
-            'html_url': repo['html_url'],
-            'full_name': repo['full_name'],
-            # 'path': repo['path'], # 仓库的路径  如 /only4/appHelper 的 appHelper
-            'name': repo['name'], # 仓库名称  如 仓库同步助手
-            # 'public': repo['public'], # 是否公开
-            'private': repo['private'], # 是否私有
-            # 'internal': repo['internal'], # 是否内部仓库
-            # 'empty_repo': repo['empty_repo'], # 是否为空仓库
-            'pushed_at': repo['pushed_at'], # 最后提交时间
-        }
-        print()
-    print("######################################################################################")
-
-
-def repoMatching(giteeRepos, githubRepos):
-    matchingList = {
-        'matchedList': {},
-        'mismatchedList': {
-            'gitee': [],
-            'github': [],
-        },
-    }
-    for giteeRepoFullName in giteeRepos.keys():
-        # print(giteeRepoFullName)
-        if(giteeRepoFullName in githubRepos.keys()):
-            matchingList.get('matchedList')[giteeRepoFullName] = {
-                'from': giteeRepos[giteeRepoFullName],
-                'to': githubRepos[giteeRepoFullName],
-            }
-            # del giteeRepos[giteeRepoFullName]
-            del githubRepos[giteeRepoFullName]
-        else:
-            # del giteeRepos[giteeRepoFullName]
-            matchingList.get('mismatchedList').get('gitee').append(giteeRepos[giteeRepoFullName])
-
-    for githubRepoFullName in githubRepos.keys():
-        # print(githubRepoFullName)
-        matchingList.get('mismatchedList').get('github').append(githubRepos[githubRepoFullName])
-        # del githubRepos[githubRepoFullName]
-    return matchingList
-
-def printMatchintInfo(matchingList):
-    print("  ################## 以下仓库会被同步 ################## ")
-    print("  Gitee 与 GitHub 两侧相同的仓库")
-    for i in matchingList.get('matchedList'):
-        print('      ' + i)
-    print()
-    print("  ################## 以下仓库不会同步 ################## ")
-    print("  Gitee 有但是 GitHub 没有的仓库")
-    for i in matchingList.get('mismatchedList').get('gitee'):
-        print('      ' + i['html_url'])
-    print()
-    print("  GitHub 有但是 Gitee 没有的仓库")
-    for i in matchingList.get('mismatchedList').get('github'):
-        print('      ' + i['html_url'])
-
-def transferRepos(matchedList, TempWorkingDir):
-    # print(matchedList)
-    # 切换路径
-    targetPath = os.path.abspath(TempWorkingDir)
-    os.chdir(targetPath)
-    if(os.path.abspath(os.getcwd()) != targetPath):
-        print("[error] 切换路径失败")
-        print("当前路径", os.path.abspath(os.getcwd()))
-        print("想要切换到的路径", targetPath)
-        input("按回车键退出...")
-        exit()
-
-    for repoFullName in matchedList:
-        repo = matchedList[repoFullName]
-        print(repo['from'])
-        preCommand = [
-            # "cd .", # 切换到当前目录下
-            # "mkdir temp_repo_dir", # 创建临时目录
-            # "cd temp_repo_dir", # 切换到临时目录
-            'chdir',
-            "git clone --mirror {repo_url}".format(repo_url = repo['from']['html_url']), # 下载仓库
-            "cd {folder_name}".format(folder_name = repo['from']['name'] + ".git"), # 切换到仓库目录
-            "git push --mirror {repo_url}".format(repo_url = repo['to']['html_url']), # 同步仓库
-        ]
-        for commandForExecute in preCommand:
-            print(commandForExecute)
-            os.system(commandForExecute)
-
-        os.system("pause")
-
-    # @echo off
-
-    # :: 创建文件夹
-    # mkdir D:\gitTransTempDir
-
-    # :: 切换到文件夹
-    # D:
-    # cd D:\gitTransTempDir
-
-    # :: 如果之前有没删除的话就删除
-    # rd /s /q D:\gitTransTempDir\chrome-extension.git
-    # cls
-
-    # git clone --mirror https://gitee.com/bitdance-team/chrome-extension
-
-    # cd ./chrome-extension.git
-
-    # git push --mirror git@github.com:bitdance-team/chrome-extension.git
-
-    # cd ../../
-
-    # rd /s /q D:\gitTransTempDir
-
-    # pause
-
-def saveJSON(data, filename):
-    with open(filename, 'w', encoding='utf-8') as f:
-        json.dump(data, f, ensure_ascii=False, indent=4)
-
-def readJSON(filename):
-    with open(filename, 'r', encoding='utf-8') as f:
-        return json.load(f)
-
-def fileExists(filename):
-    return os.path.exists(filename)
+# 请求超时时间
+timeout = 10
+if 'RequestTimeout' in config['Common'] and config['Common']['RequestTimeout'].isdigit():
+    timeout = int(config['Common']['RequestTimeout'])
+    print("[info] 请求超时时间设置为:" + str(timeout) + "秒")
 
+# ######################################## 主函数 ########################################
 if __name__ == '__main__':
     """
     主函数
     """
-    if fileExists(GlobalVarsSavePath):
-        print("[info] JSON文件已存在,跳过获取仓库信息")
+    from assets.common import saveJSON, readJSON, fileExists
+
+    GlobalVars = {
+        'lastRunTime': time.time(),
+        'giteeRepos': {},
+        'githubRepos': {},
+        'RepoMatch': {},
+    }
+
+    # 如果存在之前的获取结果,则首先导入先前的结果
+    importFileOrNot = ''
+    if not fileExists(WorkingDir): # 如果工作目录不存在,那么不可导入
+        importFileOrNot = 'n'
+    if fileExists(GlobalVarsSavePath): # 如果JSON文件存在,那么询问用户否导入
+        while(importFileOrNot != 'y' and importFileOrNot != 'n'):
+            importFileOrNot = input("[info] 发现上次运行结果,是否读取?(y/n) 默认为y ")
+            if importFileOrNot == '':
+                importFileOrNot = 'y'
+    else: # 如果JSON文件不存在,那么不可导入
+        importFileOrNot = 'n'
+
+    if importFileOrNot == 'y': # 导入
+        # 读取上次的结果
         GlobalVars = readJSON(GlobalVarsSavePath)
-        giteeRepos = GlobalVars['giteeRepos']
-        githubRepos = GlobalVars['githubRepos']
-        print("[info] JSON文件加载完成")
-    else:
-        prepareWorkingDir()
-        print("[info] 工作目录准备完成")
+        print("[info] 导入JSON文件成功")
 
-        defineWorkingDirCleanFunction()
-        print("[info] 工作目录清理函数准备完成")
+    # 注册 Ctrl+C 退出处理程序
+    # from assets.defineWorkingDirCleanFunction import defineWorkingDirCleanFunction
+    # defineWorkingDirCleanFunction(saveJSONFunction = saveJSON)
 
-        giteeOauth()
-        print("[info] Gitee账号授权完成")
-
-        githubOauth()
-        print("[info] GitHub账号授权完成")
+    # 询问用户是否重新获取Gitee仓库列表
+    if GlobalVars['giteeRepos']:
+        userInput = 'unknown'
+        while userInput != 'y' and userInput != 'n' and userInput != '':
+            userInput = input("[info] 已经获取过Gitee仓库列表,是否重新获取?(y/n) 默认为n ")
+        if userInput == 'y':
+            GlobalVars['giteeRepos'] = {} # 清空之前的记录
 
+    if not GlobalVars['giteeRepos']:
+        # 读取 Gitee 仓库列表
+        from assets.giteeOauth import giteeOauth
+        [GlobalVars['giteeReposOrigin'], GlobalVars['giteeRepos']] = \
+            giteeOauth(GiteeClientID = GiteeClientID, GiteeClientSecret = GiteeClientSecret, giteeProxies = giteeProxies, timeout = timeout)
+        print("[info] 获取到 {} 个Git仓库".format(len(GlobalVars['giteeRepos'])))
+        # 保存JSON文件
         saveJSON(GlobalVars, GlobalVarsSavePath)
         print("[info] 保存JSON文件完成")
 
-    matchingList = repoMatching(giteeRepos, githubRepos)
-    print("[info] 仓库匹配完成")
+    # 询问用户是否重新获取Gitee仓库列表
+    if GlobalVars['githubRepos']:
+        userInput = 'unknown'
+        while userInput != 'y' and userInput != 'n' and userInput != '':
+            userInput = input("[info] 已经获取过GitHub仓库列表,是否重新获取?(y/n) 默认为n ")
+        if userInput == 'y':
+            GlobalVars['githubRepos'] = {} # 清空之前的记录
+
+    if not GlobalVars['githubRepos']:
+        # 读取 GitHub 仓库列表
+        from assets.githubOauth import githubOauth
+        [GlobalVars['githubReposOrigin'], GlobalVars['githubRepos']] = \
+            githubOauth(GitHubClientID = GitHubClientID, GitHubClientSecret = GitHubClientSecret, githubProxies = githubProxies, timeout = timeout)
+        print("[info] 获取到 {} 个Git仓库".format(len(GlobalVars['githubRepos'])))
+        # 保存JSON文件
+        saveJSON(GlobalVars, GlobalVarsSavePath)
+        print("[info] 保存JSON文件完成")
+
+    # 匹配仓库
+    from assets.repoMatching import repoMatching, printMatchintInfo
+    matchList = repoMatching(repo1 = GlobalVars['giteeRepos'], repo2 = GlobalVars['githubRepos'])
+    GlobalVars['RepoMatch'] = matchList
+
+    # 保存JSON文件
+    saveJSON(GlobalVars, GlobalVarsSavePath)
+    print("[info] 保存JSON文件完成")
 
     # 打印匹配信息
-    printMatchintInfo(matchingList)
+    printMatchintInfo(matchList, repo1Name = "Gitee", repo2Name = "GitHub")
 
     # 转移仓库
-    transferRepos(matchingList.get('matchedList'), TempWorkingDir)
-    exit()
-
-    # 保存匹配信息
-    saveJSON(matchingList, RepoMatchSavePath)
-
-    # print("[info] 开始同步仓库")
-
-
-    # input("程序执行完毕,按回车键继续...")
-
-input("按回车键退出...")
-exit()
+    input("[info] 确认无误后按回车继续: ")
+    print("[info] 开始同步仓库")
+    from assets.transferRepos import transferRepos
+    transferRepos(matchList.get('match'), WorkingDir, fromRepoProtocol = GiteeProtocol, toRepoProtocol = GitHubProtocol)
+    print("程序执行完毕,将退出")
+    os.system("pause")
diff --git a/config.example.ini b/config.example.ini
new file mode 100644
index 0000000..46bf4e2
--- /dev/null
+++ b/config.example.ini
@@ -0,0 +1,26 @@
+; Please use UTF-8 encoding format to save the file.
+
+; Gitee 配置
+[Gitee]
+; 应用 ClientID 与 ClientSecret
+ClientID=YOUR_CLIENT_ID
+ClientSecret=YOUR_CLIENT_SECRET
+; 使用 ssh 还是 https 进行仓库 pull /push  可选项:https / ssh (填写其他值默认使用 https)
+Protocol=https
+; 代理 例如:127.0.0.1:15732 不使用代理请留空
+Proxy=
+
+; GitHub 配置
+[GitHub]
+; 应用 ClientID 与 ClientSecret
+ClientID=YOUR_CLIENT_ID
+ClientSecret=YOUR_CLIENT_SECRET
+; 使用 ssh 还是 https 进行仓库 pull /push  可选项:https / ssh (填写其他值默认使用 https)
+Protocol=https
+; 代理 例如:127.0.0.1:15732 不使用代理请留空
+Proxy=
+
+[Common]
+; 请求超时时间,单位为秒,默认值为 10
+RequestTimeout=10
+
diff --git a/mapping.sample.json b/mapping.sample.json
new file mode 100644
index 0000000..64b0c6b
--- /dev/null
+++ b/mapping.sample.json
@@ -0,0 +1,15 @@
+{
+    "_": "请注意,在此处设置映射后将使用映射后的值进行比较,映射前的值将不再参与比较",
+    "orgNameMap": {
+        "from1": "to1",
+        "from2": "to2"
+    },
+    "repoNameMap": {
+        "from1": "to1",
+        "from2": "to2"
+    },
+    "fullNameMap": {
+        "from1": "to1",
+        "from2": "to2"
+    }
+}
\ No newline at end of file