Python封裝git命令的流程步驟
上一篇文章介紹了Python 封裝 gradle 命令,這一篇將介紹 Python 封裝 git 命令,用于查看某個(gè)版本某個(gè)作者的所有提交更改和查看某個(gè)提交第一次出現(xiàn)的 release 分支。
執(zhí)行 git 命令
在日常的 Android 項(xiàng)目開發(fā)中,一般只會(huì)使用到: git add, git commit, git push, git pull, git rebase, git merge, git diff
等常規(guī)命令。但是使用 git 命令,還可以做一些特別的事情,比如查看某個(gè)版本某個(gè)作者的所有提交更改,方便自己或其他人進(jìn)行 code review;比如查看某個(gè)提交第一次出現(xiàn)的版本,方便排查問題。下面將介紹使用 python 封裝 git 命令。
查看某個(gè)版本某個(gè)作者的所有提交更改
在某個(gè)版本迭代中,不管是單人還是多人開發(fā),如果想在 mr 之前 或 之后,或者 release 之前 或 之后,隨時(shí)查看自己本次版本迭代中的所有提交更改(隨時(shí)對(duì)自己編寫的代碼進(jìn)行自我 code review),現(xiàn)只能使用 git 命令:git log branch1...branch2 --author=wangjiang --name-status --oneline 等進(jìn)行簡(jiǎn)單查看,而且較麻煩。我們期望有一個(gè)工具,能夠展示自己當(dāng)前分支提交的所有代碼更改內(nèi)容?,F(xiàn)利用 python 可以實(shí)現(xiàn)這個(gè)工具。
雖然創(chuàng)建的 mr 也能查看自己在本版本迭代中提交的所有代碼更改,但是在本次迭代中提交了多個(gè) mr 或者過了很久也想查看自己在某個(gè)版本中的更改時(shí),用 mr 查看就很不方便。
git 命令介紹
要比對(duì)兩個(gè)分支(branch)的提交歷史,可以使用 git log 命令并指定不同的分支名稱。
比對(duì)兩個(gè)具體的分支
git log branch1..branch2
該命令只顯示 branch2 相對(duì)于 branch1 的提交歷史,也就是:
- 顯示在 branch2 中而不在 branch1 中的提交歷史
- 只會(huì)顯示 branch2 中相對(duì)于 branch1 的新增提交
- 不包括 branch1 中相對(duì)于 branch2 的新增提交
這個(gè)命令用于比對(duì)開發(fā)分支與拉出開發(fā)分支的主分支,比如從 master 分支 拉出 feature/7.63.0-wangjiang 分支,那么使用: git log master..feature/7.63.0-wangjiang --author=wangjiang --name-status --oneline
可以查看自己在 feature/7.63.0-wangjiang 分支上的所有提交記錄。
顯示共同的祖先以及兩個(gè)分支的不同
git log branch1...branch2
顯示兩個(gè)分支的共同祖先以及它們之間的不同,也就是:
- 顯示兩個(gè)分支之間的差異,包括它們各自相對(duì)于共同祖先的所有提交
- 顯示兩個(gè)分支的共同祖先以及它們之間的不同
- 如果兩個(gè)分支有共同的提交,... 語法將顯示兩個(gè)分支最新的共同提交之后的提交
這個(gè)命令用于比對(duì)兩個(gè) release 分支,比如當(dāng)前要發(fā)布的版本分支 release/7.63.0,上一個(gè)發(fā)布的版本分支 release/7.62.0,那么使用: git log release/7.63.0...release/7.62.0 --author=wangjiang --name-status --oneline
可以查看自己在 release/7.63.0 分支上的所有提交記錄,包含本次迭代提交的所有 feature。
如果上面比對(duì)開發(fā)分支與拉出開發(fā)分支的主分支使用 ... :git log master...feature/7.63.0-wangjiang --author=wangjiang --name-status --oneline
,那么其它 feature 分支合并到 master 的提交記錄,也會(huì)顯示。
另外,對(duì)于 git log branch1..branch2
或 git log branch1...branch2
添加 --name-status --oneline
會(huì)顯示:
d7a42a90c feat:--story=1004796 --user=王江 Python封裝 git 命令需求 M music/src/main/java/com/music/upper/module/fragment/MusicAlbumPickerFragment.kt A music/src/main/res/drawable-xxhdpi/ic_draft.png M music/src/main/res/layout/music_album_choose_container_fragment.xml D music/src/main/res/drawable-xxhdpi/ic_save.png R098 music/src/main/java/com/music/upper/module/fragment/MusicVideoPickerFragment.kt music/src/main/java/com/music/upper/module/fragment/MusicVideoListPickerFragment.kt
其中 M 表示修改文件,A 表示添加文件,D 表示刪除文件,R098 表示重命名文件。
檢查文件是否存在
上面提交記錄里會(huì)有修改、添加、刪除、重命名文件,那么需要查看某個(gè)分支上某個(gè)文件是否存在。
git ls-tree branch file-path
該命令這將列出 branch 分支上指定路徑的文件信息。如果文件存在,將顯示相關(guān)信息;如果文件不存在,則命令不會(huì)有輸出。
顯示指定分支上指定文件的詳細(xì)更改信息
git show branch:file-path
這個(gè)命令會(huì)顯示指定分支上指定文件的詳細(xì)更改信息,包括修改的內(nèi)容。如果文件存在,將顯示文件內(nèi)容信息;如果文件不存在,則命令會(huì)輸出錯(cuò)誤信息。
了解了上面的 git 命令后,使用 python 將這些命令組合,并輸出自己當(dāng)前分支提交的所有代碼更改內(nèi)容的 html 文檔報(bào)告。
期望執(zhí)行的 python 腳本命令:
python3 diff_branch.py android_project_path current_branch target_branch 例如: python3 diff_branch.py /Users/wangjiang/Public/software/android-workplace/Demo release/7.63.0 release/7.62.0 python3 diff_branch.py /Users/wangjiang/Public/software/android-workplace/Demo feature/wangjiang master
首先定義一個(gè)執(zhí)行 git 命令的基礎(chǔ)方法:
def run_git_command(command): """ :param command: 實(shí)際相關(guān)命令 :return: 執(zhí)行命令結(jié)果 """ try: result = subprocess.run(command, check=True, text=True, capture_output=True, encoding='utf-8') return result.stdout except subprocess.CalledProcessError as e: print(f"Error executing command: {e}") return None
第一步:同步目標(biāo)分支
- 獲取遠(yuǎn)程分支:
git fetch origin branch
- 切換到分支:
git checkout branch
- 更新分支:
git pull origin branch
def sync_branch(branch): """ 同步分支到最新代碼 :param branch: 分支名稱 :return: 檢查結(jié)果 """ result = run_git_command( ['git', 'fetch', 'origin', branch]) if result is None: return None result = run_git_command( ['git', 'checkout', branch]) if result is None: return None result = run_git_command( ['git', 'pull', 'origin', branch]) return result def check_branch(target_branch, current_branch): """ 檢查分支 :param current_branch: 當(dāng)前分支 :param target_branch: 要比對(duì)的分支 :return: 檢查分支結(jié)果,True表示成功,否則失敗 """ if sync_branch(target_branch) is None: print(f"Sync branch: {target_branch} Failed") return False if sync_branch(current_branch) is None: print(f"Sync branch: {current_branch} Failed") return False return True
第二步:獲取自己的 git 賬號(hào)名稱
- 獲取 git 賬號(hào)名稱:
git config --get user.name
def get_git_user(): """ 獲取自己的 git user name :return: git 賬戶名稱 """ return run_git_command(['git', 'config', '--get', 'user.name']).strip()
第三步:比較 current_branch 和 target_branch,獲取提交的文件列表
- 比對(duì)分支:git log branch1..branch2 或 git log branch1...branch2
def get_commit_file_path_set(target_branch, current_branch, author): """ 比對(duì) branch,獲取提交的文件相對(duì)路徑列表 :param target_branch: 要比對(duì)的分支 :param current_branch: 當(dāng)前分支 :param author: git user.name :return: 提交的文件相對(duì)路徑列表 """ try: # 如果當(dāng)前開發(fā)分支與master或release分支比對(duì),使用 git log master..feature if (target_branch == 'master' or target_branch.startswith('release')) and not current_branch.startswith( 'release'): branch_command = f"{target_branch}..{current_branch}" else: # 否則都是用 git log branch1...branch2 branch_command = f"{current_branch}...{target_branch}" command = ['git', 'log', branch_command, f"--author={author}", '--name-status', '--oneline'] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) file_path_list = set() rename_file_path_list = set() while True: output = process.stdout.readline() if output == '' and process.poll() is not None: process.kill() break if output: text = output.strip().replace("\t", "") if text.startswith('M') or text.startswith('A') or text.startswith('D'): file_path = text[1:] file_path_list.add(file_path) else: # 重命名文件 if output.strip().startswith('R'): rename_file_path = output.strip().split('\t')[1] rename_file_path_list.add(rename_file_path) if len(file_path_list) == 0: print(f"{' '.join(command)}: No commit files") return None for rename_file_path in rename_file_path_list: file_path_list.remove(rename_file_path) return file_path_list except subprocess.CalledProcessError as e: print(f"Error executing command: {e}") return None
第四步:根據(jù)文件列表獲取文件內(nèi)容
- 查看分支上是否有該文件:
git ls-tree branch-name file-path
- 顯示分支上該文件內(nèi)容:
git show branch:file-path
def get_commit_content(commit_file_path_set, target_branch, current_branch): """ 獲取提交的內(nèi)容 :param commit_file_path_set: 提交的文件相對(duì)路徑列表 :param target_branch: 要比對(duì)的分支 :param current_branch: 當(dāng)前分支 :return: 要比對(duì)的分支內(nèi)容,當(dāng)前分支內(nèi)容 """ target_content_lines = [] current_content_lines = [] for file_path in commit_file_path_set: try: file_in_target_branch = run_git_command(['git', 'ls-tree', target_branch, file_path]) if file_in_target_branch.find('blob') >= 0: target_content = run_git_command( ['git', 'show', target_branch + ":" + file_path]) if target_content is not None: target_content_lines += target_content.splitlines() except UnicodeDecodeError as e: target_content_lines += [file_path + '\n'] try: file_in_current_branch = run_git_command(['git', 'ls-tree', current_branch, file_path]) if file_in_current_branch.find('blob') >= 0: current_content = run_git_command( ['git', 'show', current_branch + ":" + file_path]) if current_content is not None: current_content_lines += current_content.splitlines() except UnicodeDecodeError as e: current_content_lines += [file_path + '\n'] return target_content_lines, current_content_lines
第五步:生成 html 報(bào)告文件
- 生成 html 報(bào)告文件:difflib.HtmlDiff
def make_html_file(project_path, target_branch_content, current_branch_content, target_branch, current_branch, author): """ 生成 html 文件報(bào)告 :param project_path: 項(xiàng)目路徑 :param target_branch_content: 要比對(duì)的分支內(nèi)容 :param current_branch_content: 當(dāng)前分支內(nèi)容 :param target_branch: 要比對(duì)的分支 :param current_branch: 當(dāng)前分支 :param author: git user.name :return: html 文件報(bào)告路徑 """ html_report_dir = f"{project_path}{os.path.sep}build{os.path.sep}reports{os.path.sep}diff{os.path.sep}{author}" if not os.path.exists(html_report_dir): os.makedirs(html_report_dir) html_file_path = f"{html_report_dir}{os.path.sep}{current_branch.replace('/', '_')}-diff-{target_branch.replace('/', '_')}.html" d = difflib.HtmlDiff(wrapcolumn=120) diff_html = d.make_file(target_branch_content, current_branch_content, target_branch, current_branch, context=True) if os.path.exists(html_file_path): os.remove(html_file_path) with open(html_file_path, 'w', encoding='utf-8') as html_file: html_file.write(diff_html) html_file.close() print(f"{project_path} Html Report Path: {html_file_path}") return html_file_path
第六步:在瀏覽器中打開 html 文檔報(bào)告
def open_file(file_path): """ 在電腦上打開截屏文件 :param file_path: 電腦上的截屏文件地址 """ system = platform.system().lower() if system == "darwin": # macOS subprocess.run(["open", file_path]) elif system == "linux": # Linux subprocess.run(["xdg-open", file_path]) elif system == "windows": # Windows subprocess.run(["start", file_path], shell=True) else: print("Unsupported operating system.")
第七步:將上面步驟組合在一起執(zhí)行
if __name__ == "__main__": args = sys.argv[1:] if len(args) > 0: project_path = args[0] if len(args) > 1: current_branch = args[1] if len(args) > 2: target_branch = args[2] os.chdir(project_path) # 第一步:同步目標(biāo)分支 if not check_branch(target_branch, current_branch): exit(1) # 第二步:獲取自己的git賬戶名稱 author = get_git_user() if author is None: exit(1) # 第三步:比較 current_branch 和 target_branch,獲取提交的文件列表 commit_file_path_set = get_commit_file_path_set(target_branch, current_branch, author) if commit_file_path_set is None or len(commit_file_path_set) == 0: exit(0) # 第四步:根據(jù)文件列表獲取文件內(nèi)容 last_branch_content, current_branch_content = get_commit_content(commit_file_path_set, target_branch, current_branch) # 第五步:生成 html 報(bào)告文件 report_html_file_path = make_html_file(project_path, last_branch_content, current_branch_content, target_branch, current_branch, author) # 第六步:打開 html 報(bào)告文件 open_file(report_html_file_path)
將上面的代碼封裝成 diff_branch.py,在命令行執(zhí)行:
python3 diff_branch.py /Users/wangjiang/Public/software/android-workplace/Demo release/7.63.0 release/7.62.0
輸出結(jié)果,這里比較私密,就不展示了。
查看某個(gè)提交第一次出現(xiàn)的 release 分支
在日常的 Android 項(xiàng)目開發(fā)中,如果想排查問題,或查看 feature 在哪個(gè)版本上線的,那么查看某個(gè) commit 第一次出現(xiàn)的 release 分支,能夠輔助你得到更多有用的信息。
第一步:查找包含 commit id 的所有分支名稱
- 查找包含 commit id 的所有分支:
git branch --contains commit-id -all
def find_commit(commit_id): """ 查找包含 commit id 的所有分支名稱 :param commit_id: commit id 值 :return: 分支列表 """ result = run_git_command( ['git', 'branch', '--contains', commit_id, '--all']) if result is not None: return result.splitlines() return None
第二步:找到 commit id 第一次出現(xiàn)的 release 分支
def compare_versions(version1, version2): """ 比較版本號(hào) :param version1: 7.63.0 :param version2: 7.64.0 :return: 如果 version1<version2,返回-1;如果version1>version2,返回1;如果version1=version2,返回0 """ parts1 = list(map(int, version1.split('.'))) parts2 = list(map(int, version2.split('.'))) length = max(len(parts1), len(parts2)) for i in range(length): part1 = parts1[i] if i < len(parts1) else 0 part2 = parts2[i] if i < len(parts2) else 0 if part1 < part2: return -1 elif part1 > part2: return 1 return 0 def find_min_release_branch(branch_list): """ 篩選出版本最低的 release branch,也就是找到 commit id 第一次出現(xiàn)的 release branch :param branch_list: 分支列表 :return: 版本最低的 release branch """ min_version_name = None min_branch = None release_prefix = 'remotes/origin/release/' for branch in branch_list: index = branch.find(release_prefix) if index >= 0: version_name = branch[index + len(release_prefix):] if min_version_name is None: min_version_name = version_name min_branch = branch else: if compare_versions(min_version_name, version_name) > 0: min_version_name = version_name min_branch = branch return min_branch.strip()
第三步:獲取 commit 信息
- 獲取 commit 信息:
git show commit-id
def get_commit_info(commit_id): """ 獲取提交的信息 :param commit_id: commit id值 :return: commit 信息,包含文件更改信息 """ return run_git_command( ['git', 'show', commit_id])
第四步:生成 html 文檔報(bào)告
def make_html_file(project_path, commit_id, title, content): """ 生成 html 文件報(bào)告 :param project_path: 項(xiàng)目路徑 :param commit_id: commit id值 :param title: html 文檔標(biāo)題 :param content: html 文檔內(nèi)容 :return: html 文件報(bào)告路徑 """ html_content = f""" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> body {{ font-family: 'Arial', sans-serif; background-color: #272822; color: #f8f8f2; margin: 20px; }} pre {{ white-space: pre-wrap; font-size: 14px; line-height: 1.5; background-color: #1e1e1e; padding: 20px; border: 1px solid #333; border-radius: 5px; overflow-x: auto; }} .header {{ color: #66d9ef; }} .bordered-div {{ border: 1px solid #000; padding: 10px; }} </style> </head> <body> <h1>{title}</h1> <pre> {content} </pre> </body> </html> """ html_report_dir = f"{project_path}{os.path.sep}build{os.path.sep}reports{os.path.sep}diff{os.path.sep}commit_id" if not os.path.exists(html_report_dir): os.mkdir(html_report_dir) html_file_path = f"{html_report_dir}{os.path.sep}{commit_id}.html" if os.path.exists(html_file_path): # 如果文件存在,刪除文件 os.remove(html_file_path) with open(html_file_path, 'w') as html_file: html_file.write(html_content) html_file.close() print(f"Html Report Path: {html_file_path}") return html_file_path
第五步:在瀏覽器中打開 html 文檔報(bào)告
def open_file(file_path): """ 在電腦上打開截屏文件 :param file_path: 電腦上的截屏文件地址 """ system = platform.system().lower() if system == "darwin": # macOS subprocess.run(["open", file_path]) elif system == "linux": # Linux subprocess.run(["xdg-open", file_path]) elif system == "windows": # Windows subprocess.run(["start", file_path], shell=True) else: print("Unsupported operating system.")
第六步:將上面步驟組合在一起執(zhí)行
if __name__ == "__main__": args = sys.argv[1:] if len(args) > 0 and os.path.exists(args[0]): project_path = args[0] if len(args) > 1 : commit_id = args[1] os.chdir(project_path) # 第一步:查找包含 commit id 的所有分支名稱 branch_list = find_commit(commit_id) # 第二步:找到 commit id 第一次出現(xiàn)的 release 分支 min_release_branch = find_min_release_branch(branch_list) title = f"<p>Project: {project_path}</p>The commit id: {commit_id} first appears in the release branch: {min_release_branch}" # 第三步:獲取 commit 信息 content = get_commit_info(commit_id) # 第四步:生成 html 文檔報(bào)告 html_file_path = make_html_file(project_path, commit_id, title, content) # 第五步:打開 html 文檔報(bào)告 open_file(html_file_path)
將上面的代碼封裝成 find_commit.py,在命令行執(zhí)行:
python3 find_commit.py /Users/wangjiang/Public/software/android-workplace/Demo 00b9d42d70
小結(jié)
使用 python 執(zhí)行相關(guān) git 命令,主要是生成可視化的 html 文檔報(bào)告。比對(duì)分支操作,在開發(fā) feature 合并到主分支前,可以查看自己當(dāng)前分支提交的所有代碼更改內(nèi)容;在本迭代版本 release 前,可以反復(fù)查看自己的所有更改,進(jìn)行代碼 double check,防止出現(xiàn)線上 bug。在排查問題或者代碼回溯中,可以快速找到 commit id 第一次出現(xiàn)的 release 版本,得到有用關(guān)鍵信息。總之,利用 python 組合 git 命令,可以在開發(fā)中做很多意想不到的事情。
另外,沒有提供上面的完整的代碼,但是依照步驟去做,就可以完全實(shí)現(xiàn)。
寫在最后,使用 python 不止可以封裝 adb, gradle, git 命令,還可以做 json 比對(duì),代碼靜態(tài)分析(利用detekt,pmd等的cli),下線或升級(jí)某個(gè)庫查看庫在項(xiàng)目中的代碼分布情況,業(yè)務(wù)和技術(shù)指標(biāo)可視化報(bào)告,查看pb文件,用戶日志定制化分析等。學(xué)習(xí) python,對(duì)于日常 Android 開發(fā),非常有用,能幫助省去很多瑣碎時(shí)間。
以上就是Python封裝git命令的流程步驟的詳細(xì)內(nèi)容,更多關(guān)于Python封裝git命令的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Pytorch.nn.conv2d 過程驗(yàn)證方式(單,多通道卷積過程)
今天小編就為大家分享一篇Pytorch.nn.conv2d 過程驗(yàn)證方式(單,多通道卷積過程),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-01-01基于Python實(shí)現(xiàn)微博抓取GUI程序
在前面的分享中,我們制作了一個(gè)天眼查 GUI 程序,今天我們?cè)谶@個(gè)的基礎(chǔ)上,繼續(xù)開發(fā)新的功能,微博抓取工具,感興趣的可以了解一下2022-09-09利用pyshp包給shapefile文件添加字段的實(shí)例
今天小編就為大家分享一篇利用pyshp包給shapefile文件添加字段的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-12-12python執(zhí)行shell并獲取結(jié)果的詳細(xì)示例
在Python中執(zhí)行Shell命令并獲取其結(jié)果,通??梢允褂胹ubprocess模塊,這個(gè)模塊允許我們啟動(dòng)新的進(jìn)程,連接到它們的輸入/輸出/錯(cuò)誤管道,并獲取它們的返回碼,下面是一個(gè)詳細(xì)的示例,展示了如何使用subprocess.run()函數(shù)來執(zhí)行Shell命令并獲取其輸出,需要的朋友可以參考下2024-07-07深入淺析python中的多進(jìn)程、多線程、協(xié)程
這篇文章主要介紹了深入淺析python中的多進(jìn)程、多線程、協(xié)程 的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06