Git

Git

2022年10月

Posted by franztao on October 26, 2022

跟踪工作的变化以实现可重复性和协作。

Intuition

无论是单独工作还是与团队一起工作,重要的是有一个系统来跟踪项目的变化,这样就可以恢复到以前的版本,这样其他人就可以重现transformers工作并为之做出贡献。Git是一个分布式版本控制系统,可以让做到这一点。Git 在transformers计算机上本地运行,它会跟踪transformers文件及其历史记录。为了与他人协作,可以使用远程主机(GitHubGitLabBitBucket等)来托管transformers文件及其历史记录。将使用 git 将transformers本地更改推送到远程主机或从远程主机拉取其他人的更改。

Git 传统上用于存储小于 100MB 的小文件(脚本、自述文件等)并对其进行版本控制,但是,仍然可以使用指向 blob 存储的文本指针对大型工件(数据集、模型权重等)进行版本控制。这些指针将包含assert所在位置、特定内容/版本(例如通过哈希)等信息。将在版本控制课程中看到这一点,将创建一个指向特定版本的指针transformers数据集和模型。

设置

初始化git

混帐环境

初始化一个本地存储库(.git目录)来跟踪transformers文件:

git init

Initialized empty Git repository in /Users/goku/Documents/madewithml/MLOps/.git/

可以看到哪些文件未被跟踪或尚未提交:

git status

On branch main

No commits yet

Untracked files:
  (use "git add ..." to include in what will be committed)
        .flake8
        .vscode/
        Makefile
        ...

.gitignore

可以看到有一些不想推送到远程主机的文件,比如transformers虚拟环境、日志、大数据文件等。可以创建一个.gitignore文件来确保不签入这些文件。

touch .gitignore

将以下文件添加到文件中:

# Data
logs/
stores/
data/

# Packaging
venv/
*.egg-info/
__pycache__/

# Misc
.DS_Store

现在,也将添加data到transformers.gitignore文件中,但这意味着其他人在从transformers远程主机上拉取时将无法生成相同的数据assert。为了解决这个问题,将在transformers版本控制课程中推送指向数据文件的指针,以便数据也可以完全按照在本地拥有的方式进行复制。

Tip

查看项目的.gitignore以获得更完整的示例,其中还包括许多通常不想推送到远程存储库的其他系统工件。transformers完整.gitignore文件基于 GitHub 的Python 模板,使用的是Mac,因此也添加了相关的全局文件名。

如果git status现在运行,不应该再看到在文件中定义的.gitignore文件。

Add to stage

接下来,将transformers工作从工作目录添加到暂存区。

  • 可以一次添加一个文件:

    git add <filename>

  • 可以一次添加所有文件:

    git add .

现在运行git status将向展示所有暂存文件:

git status

On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached ..." to unstage)
        new file:   .flake8
        new file:   .gitignore
        new file:   Makefile
        ...

承诺回购

现在准备将暂存区中的文件提交到本地存储库。默认分支(项目的一个版本)将被称为main.

git commit -m "added project files"

[main (root-commit) 704d99c] added project files
 47 files changed, 50651 insertions(+)
 create mode 100644 .flake8
 create mode 100644 .gitignore
 create mode 100644 Makefile
 ...

提交需要一条消息,指示发生了哪些更改。如果需要,可以git commit --amend用来编辑提交消息。如果进行git status检查,会发现暂存区没有其他内容可以提交。

git status

On branch main
nothing to commit, working tree clean

推送到远程

现在已准备好将更新从本地存储库推送到远程存储库。首先在GitHub (或任何其他远程存储库)上创建一个帐户,然后按照说明创建一个远程存储库(它可以是私有的或公共的)。在transformers本地存储库中,将设置transformers用户名和电子邮件凭据,以便可以将更改从本地存储库推送到远程存储库。

# Set credentials via terminal
git config --global user.name <USERNAME>
git config --global user.email <EMAIL>

可以像这样快速验证是否设置了正确的凭据:

# Check credentials
git config --global user.name
git config --global user.email

接下来,需要在本地和远程存储库之间建立连接:

# Push to remote
git remote add origin https://github.com/<USERNAME>/<REPOSITORY_NAME>.git
git push -u origin main  # pushing the contents of our local repo to the remote repo
                         # origin signifies the remote repository

Developing

现在准备好开始添加到transformers项目并提交更改。

cloning

如果(或其他人)还没有设置本地存储库并连接到远程主机,可以使用cloning命令:

git clone <REMOTE_REPO_URL> <PATH_TO_PROJECT_DIR>

也可以cloning存储库的特定分支:

git clone -b <BRANCH> <REMOTE_REPO_URL> <PATH_TO_PROJECT_DIR>

  • <REMOTE_REPO_URL>是远程仓库的位置(例如 https://github.com/GokuMohandas/mlops-course)。
  • <PATH_TO_PROJECT_DIR>是要将项目cloning到的本地目录的名称。

创建分支

当想要添加或更改某些内容时,例如添加功能、修复错误等,在开发之前创建一个单独的分支始终是最佳实践。这在与团队合作时尤为重要,这样就可以在讨论和审查后将transformers工作干净利落地合并到main分支机构中。

将从创建一个新分支开始:

git checkout -b <NEW_BRANCH_NAME>

可以使用以下命令查看创建的所有分支,其中 * 表示当前的分支:

git branch

* convnet
main

可以使用以下方法轻松地在现有分支之间切换:

git checkout <BRANCH_NAME>

进入分支后,可以对项目进行更改并提交这些更改。

git add .
git commit -m "update model to a convnet"
git push origin convnet

请注意,正在将此分支推送到transformers远程存储库,该存储库尚不存在,因此 GitHub 将相应地创建它。

拉取请求(PR)

当将新分支推送到远程存储库时,需要创建一个拉取请求 (PR) 以与另一个分支(例如main本例中的分支)合并。将transformers工作与另一个分支(例如 main)合并时,它被称为拉取请求,因为请求该分支拉取提交的工作。可以使用此处概述的步骤创建拉取请求:创建拉取请求

在github上合并

note

可以使用 git CLI 命令合并分支和解决冲突,但最好使用在线界面,因为可以轻松地将更改可视化,与队友进行讨论等。

# Merge via CLI
git push origin convnet
git checkout main
git merge convnet
git push origin main

Pull

一旦接受了拉取请求,transformersmain分支现在就更新了transformers更改。然而,更新只发生在远程存储库上,所以也应该将这些更改拉到transformers本地main分支。

git checkout main
git pull origin main

Delete branches

一旦完成了一个分支的工作,就可以删除它以防止transformers存储库混乱。可以使用以下命令轻松删除分支的本地和远程版本:

# Delete branches
git branch -d <BRANCH_NAME>  # local
git push origin --delete <BRANCH_NAME>  # remote

合作

到目前为止,集成迭代开发的工作流程非常顺利,但在协作环境中,可能需要解决冲突。假设有两个分支(ab)是从main分支创建的。这是要尝试模拟的内容:

  1. 开发人员 A 和 B 分叉main分支进行一些更改
  2. 开发人员 A 进行更改并向main分支提交 PR。
  3. 开发者 B 对与开发者 A 相同的行进行更改,并向 提交 PR main
  4. 现在有合并冲突,因为两个开发人员都更改了同一行。
  5. 选择要保留的代码版本并解决冲突。

main当尝试合并第二个 PR 时,必须解决这个新 PR 与分支中已经存在的冲突。

解决github上的冲突

main可以通过选择保留哪些内容(与分支合并的当前内容a或本b分支)并删除另一个内容来解决冲突。然后可以成功合并 PR 并更新本地的main分支。

<<<<<< BRANCH_A
<CHANGES FROM BRANCH A>
======
<CHANGES FROM BRANCH B>
>>>>>> BRANCH_B

一旦冲突得到解决并且合并了 PR,就可以更新transformers本地存储库以反映这些决定。

git checkout main
git pull origin main

note

只是有冲突,因为这两个分支都是从该main分支的先前版本派生出来的,并且它们碰巧都更改了相同的内容。如果先创建一个分支,然后在创建第二个分支之前更新主分支,就不会有任何冲突。但在协作环境中,不同的开发人员可能随时分叉出相同版本的分支。

需要了解的一些更重要的命令包括rebasestash

检查

Git 允许在许多不同的层次上检查工作的当前和以前的状态。让探索最常用的命令。

Status

已经多次使用 status 命令,因为它对于快速查看工作树的状态非常有用。

# Status
git status
git status -s  # short format

日志

如果想查看所有提交的日志,可以使用 log 命令。也可以通过在 Git 在线界面上检查特定的分支历史来做同样的事情。

# Log
git log
git log --oneline  # short version

704d99c (HEAD -> main) added project files

提交 ID 的长度为 40 个字符,但可以用前几个字符来表示它们(Git SHA 的默认值是七位数字)。如果有歧义,Git 会通知,可以简单地添加更多的提交 ID。

差异

如果想知道两次提交之间的区别,可以使用 diff 命令。

# Diff
git diff  # all changes between current working tree and previous commit
git diff <COMMIT_A> <COMMIT_B>  # diff b/w two commits
git diff <COMMIT_A>:<PATH_TO_FILE> <COMMIT_B>:<PATH_TO_FILE>  # file diff b/w two commits

diff --git a/.gitignore b/.gitignore
index 288973d..028aa13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
 # Data
 logs/
 stores/
-data/

Blame

最有用的检查命令之一是 blame,它允许查看文件中每一行的提交。

# Blame
git blame <PATH_TO_FILE>
git blame -L 1,3 <PATH_TO_FILE>  # blame for lines 1 and 3

Time travel

有时可能做了一些希望可以改变的事情。在生活中并不总是可以做到这一点,但在 Git 的世界里,它是!

恢复

有时可能只想撤消添加或暂存文件,可以使用restore命令轻松完成。

# Restore
git restore -- <PATH_TO_FILE> <PATH_TO_FILE> # will undo any changes
git restore --stage <PATH_TO_FILE>  # will remove from stage (won't undo changes)

重置

现在,如果已经提交但还没有推送到远程,可以通过将分支指针移动到该提交来重置到之前的提交。请注意,这将撤消自上次提交以来所做的所有更改。

# Reset
git reset <PREVIOUS_COMMIT_ID>  # or HEAD^

HEAD是引用上一次提交的快速方法。两者HEAD和任何先前的提交 ID 都可以带有一个^~符号作为相对引用。^n 指的是提交的第 n 个父级,而~n指的是第n个祖父母。当然,总是可以明确地使用提交 ID,但是这些短手可以派上用场,无需git log检索提交 ID 即可进行快速检查。

恢复

但是可以通过添加一个新的提交来恢复某些以前的提交来继续前进,而不是将分支指针移动到之前的提交。

# Revert
git revert <COMMIT_ID> ...  # rollback specific commits
git revert <COMMIT_TO_ROLLBACK_TO>..<COMMIT_TO_ROLLBACK_FROM>  # range

Checkout

有时可能想暂时切换回以前的提交,只是为了探索或提交一些更改。最好在单独的分支中执行此操作,如果想保存更改,需要创建一个单独的 PR。请注意,如果您确实检查了以前的提交并提交了 PR,则可以覆盖两者之间的提交。

# Checkout
git checkout -b <BRANCH_NAME> <COMMIT_ID>

最佳实践

有很多不同的工作可以与 git 一起工作,有时当其他开发人员遵循不同的做法时,它会很快变得不守规矩。在处理提交和分支时,这里有一些被广泛接受的最佳实践。

提交

  • 经常提交,这样每个提交都有一个明确的关联更改,您可以批准/回滚。

  • 如果在推送到远程主机之前有多个提交,请尝试压缩提交。

  • 避免整体提交(即使您经常存储和变基),因为它会导致许多组件崩溃并造成代码审查的噩梦。

  • 将有意义的消息附加到提交中,以便开发人员确切地知道 PR 需要什么。

  • 使用标签来表示有意义且稳定的应用程序版本。

    # Tags git tag -a v0.1 -m "initial release"

  • 不要删除提交历史(重置),而是使用revert回滚并提供推理。

分支机构

  • 在处理功能、错误等时创建分支,因为这使得添加和恢复到main分支变得非常容易。
  • 避免使用神秘的分支名称。
  • 将您的分支维护main为始终有效的“演示就绪”分支。
  • 用规则保护分支机构(尤其是main分支机构)。

标签

利用 git标签标记重要的发布提交。可以通过终端或在线远程界面创建标签,这也可以对以前的提交完成(以防忘记)。

# Tags
git tag  # view all existing tags
git tag -a <TAG_NAME> -m "SGD"  # create a tag
git checkout -b <BRANCH_NAME> <TAG_NAME>  # checkout a specific tag
git tag -d <TAG_NAME>  # delete local tag
git push origin --delete <TAG_NAME>  # delete remote tag
git fetch --all --tags  # fetch all tags from remote

标签名称通常遵循版本命名约定,例如v1.4.2数字从左到右表示主要、次要和错误更改。


更多干货,第一时间更新在以下微信公众号:

您的一点点支持,是我后续更多的创造和贡献

转载到请包括本文地址 更详细的转载事宜请参考文章如何转载/引用

本文主体源自以下链接:

@article{madewithml,
    author       = {Goku Mohandas},
    title        = { Made With ML },
    howpublished = {\url{https://madewithml.com/}},
    year         = {2022}
}