git 笔记

注意: 本文档是为 fastai 的先前版本编写的。它尚未完全更新到 fastai v2。有关当前说明,请参阅开发者指南

在使用 fastai 时,您可能需要了解一些 git 知识——例如,如果您想为项目做出贡献,或者您想撤消代码树中的某些更改。本文档包含各种可能有用的配方,以帮助您的工作。

如何创建拉取请求 (PR)

虽然本指南主要适用于为任何 github 项目创建 PR,但它包含几个特定于 fastai 项目仓库的步骤。

以下说明使用 USERNAME 作为 github 用户名的占位符。遵循本指南的最简单方法是将其整个部分复制粘贴到一个文件中,将 USERNAME 替换为您的真实用户名,然后按照步骤操作。

本指南中的所有示例都是为使用 fastai 仓库 编写的。如果您想为其他 fastai 项目仓库做出贡献,只需在以下说明中将 fastai 替换为该其他仓库名称即可。

此外,不要混淆 fastai github 用户名、fastai 仓库和 fastai 模块目录(其中包含 python 代码)。以下 url 按提到的顺序显示了所有三个:

https://github.com/fastai/fastai/tree/master/fastai
                     |       |                  |
                 username reponame        modulename

下面您将找到创建 PR 的详细步骤。

步骤 1. 从同步的分支检出开始

1a. 第一次

如果您已经创建了所需仓库的分支,请继续执行第 1b 节。

如果是第一次,您只需创建原始仓库的分支。

1b. 后续时间

如果您在创建原始仓库的分支后立即创建 PR,则两个仓库是对齐的,您可以轻松创建 PR。如果时间过去,原始仓库开始与您的分支产生分歧,因此当您处理 PR 时,您需要保持主分支与原始仓库同步。

您可以通过访问 https://github.com/USERNAME/fastai 并查看类似内容来了解分支的状态:

此分支在 fastai:master 之后有 331 次提交。

因此,让我们同步这两个分支:

  1. 将自己置于分支仓库的 master 分支中。返回到您之前检出的仓库并切换到 master 分支:

    cd fastai
    git checkout master
  2. 将分支仓库与原始仓库同步:

    git fetch upstream
    git checkout master
    git merge --no-edit upstream/master
    git push

    现在您可以从这个同步的 master 分支创建分支。

    通过访问 https://github.com/USERNAME/fastai 并检查它是否显示以下内容来验证您的分支是否与原始仓库同步:

    此分支与 fastai:master 同步。

    现在您可以处理新的 PR。

步骤 2. 创建分支

始终在分支内工作非常重要。如果您在 master 分支中进行任何提交,您将无法同时创建多个 PR,并且您将无法在不进行重置的情况下将分支的 master 分支与原始仓库同步。如果您犯了错误并在 master 分支中提交,这并不是世界末日,只是您的生活变得更加复杂。本指南将解释如何处理这种情况。

  1. 使用您想要的任何名称创建一个分支,例如 new-feature-branch,并切换到该分支。然后设置此分支的上游,以便您可以在不需要传递任何更多参数的情况下执行 git push 和其他 git 命令。

    git checkout -b new-feature-branch
    git push --set-upstream origin new-feature-branch

步骤 6. 推送您的更改

  1. 当您对结果满意时,提交新代码:

    git commit -a

    -a 将自动提交对任何仓库文件的更改。

    如果您创建了新文件,请首先告诉 git 跟踪它们:

    git add newfile1 newdir2 ...

    然后提交。

  2. 最后,将更改推送到您的分支:

    git push

步骤 8. 通过 CI 测试

一旦提交了 PR,您将在 github 上看到我们正在 CI 服务器上运行各种测试来验证您的 PR。

如何保持功能分支最新

通常您不需要担心更新功能分支以与 fastai 代码库(上游主分支)同步。您必须执行更新的唯一时间是当您一直在处理的相同代码在主分支中发生了更改。因此,当您提交 PR 时,github 会告诉您存在合并冲突。

您可以直接更新功能分支,但最好先更新分支的主分支。 * 第一步:同步你fork的master分支:

cd fastai
git fetch upstream
git checkout master
git merge --no-edit upstream/master
git push --set-upstream origin master
  • 第二步:更新你的特性分支my-cool-feature

    git checkout my-cool-feature
    git merge origin/master
  • 第三步:解决合并产生的任何冲突(使用编辑器或专门的合并工具),然后对有冲突的文件执行git add

  • 第四步:将更新推送到你的分支:

    git push

    如果你的PR已经打开,GitHub会自动更新其状态,显示新的提交,并且如果你按照上述步骤操作,冲突应该不再存在。

如何重置你fork的master分支

如果你没有小心地创建分支,并且直接在你fork的仓库的master分支上提交,你将无法在不重置的情况下与原始仓库同步。当你想要创建分支时,由于它将与分叉的原始仓库产生分歧,PR过程中会出现问题。

当然,最暴力的方法是去GitHub删除你的fork(这将删除你在这个fork上所做的所有工作,包括任何分支,所以如果你决定这样做,请非常小心,因为将无法恢复你的数据)。

一个更安全的方法是用原始仓库的HEAD重置你fork的masterHEAD

如果你还没有设置上游,现在就做: git remote add upstream git@github.com:fastai/REPONAME.git

然后进行重置: git fetch upstream git update-ref refs/heads/master refs/remotes/upstream/master git checkout master git stash git reset --hard upstream/master git push origin master --force

我在哪里?

现在你有了原始仓库、fork的仓库及其分支,你如何知道你当前在哪个仓库和分支中?

  • 我在哪个仓库?

    git config --get remote.origin.url | sed 's|^.*//||; s/.*@//; s/[^:/]\+[:/]//; s/.git$//'

    例如:stas00/fastai

  • 我在哪个分支?

    git branch | sed -n '/\* /s///p'

    例如:new-feature-branch7

  • 组合:

    echo $(git config --get remote.origin.url | sed 's|^.*//||; s/.*@//; s/[^:/]\+[:/]//; s/.git$//')/$(git branch | sed -n '/\* /s///p')

    例如:stas00/fastai/new-feature-branch7

但这不是一个非常高效的过程,总是问系统你在哪里。为什么不自动化这个过程并将其集成到你的bash提示符中(假设你使用bash)。

bash-git-prompt

进入bash-git-prompt,它不仅告诉你你在哪个虚拟环境中,以及你在哪个用户名仓库分支上,还提供了非常有用的视觉指示,显示你的git checkout的状态——有多少文件发生了变化,有多少提交等待推送,是否有任何上游更改,等等。

我目前正在处理4个不同的fastai项目仓库和4个相应的fork,以及所有这些仓库中的几个分支,所以我非常困惑,直到我开始使用这个工具。为了给你一个视觉上的各种提示,截至本文写作时:

(pytorch-dev) /fastai/ci-experiments [fastai/fastai:ci-experiments|·6]>

(pytorch-dev) /fastai/linkcheck [fastai/fastai:master]>

(pytorch-dev) /stas00/fork [stas00/fastai:master|·3]>

(pytorch-dev) /fastai/wip [fastai/fastai:master|+2?10·3]>

分支后面的数字是修改/未跟踪/暂存的数量。前面的(pytorch-dev)是当前激活的conda环境名称。

如果你不使用bashfish shell,搜索其他shell的类似工具。

GitHub快捷方式

  • 按作者显示提交:?author=github_username

    你可以在提交视图中通过附加参数?author=github_username按作者过滤提交。

    例如,链接https://github.com/fastai/fastai/commits/master?author=jph00显示了jph00对fastai仓库的提交列表。

  • 按范围显示提交:master@{time}..master

    你可以通过使用URL github.com/user/repo/compare/{range}在GitHub中创建比较视图。范围可以是两个SHA,如sha1…sha2,或两个分支名称,如master…my-branch。范围也足够智能,可以考虑时间。

    例如,你可以通过使用格式master@{1.day.ago}…master过滤自昨天的提交列表。链接https://github.com/fastai/fastai/compare/master@{1.day.ago}…master,例如,获取fastai仓库自昨天的所有提交:

  • 显示.diff & .patch 在比较视图、拉取请求或提交页面的URL后添加.diff.patch,以获取文本格式的差异或补丁。

    例如,链接https://github.com/fastai/fastai/compare/master@{1.day.ago}…master.patch获取自昨天以来fastai仓库中所有提交的补丁。

  • 行链接

    在任何文件视图中,当你点击一行或多行(按住SHIFT键)时,URL会发生变化以反映你的选择。你可以通过该链接告诉他人查看特定的代码行或代码块。

  • 删除一个分支

    1. 前往github.com/USERNAME/FORKED-REPO-NAME/
    2. 点击设置
    3. 向下滚动并点击[删除此仓库]

    USERNAME替换为你的GitHub用户名,将FORKED-REPO-NAME替换为仓库名称

修订

相对引用

^      - 每次一个提交(指定提交的父提交)
master^  = master的第一个父提交
master^^ = master的第一个祖父提交
~<num> - 多个提交

操作

添加

git add [文件夹/文件]

移除

git rm [文件夹/文件]

仅移除远程文件副本。例如,移除已提交但保留本地副本的database.yml。这对于移除已推送但保留本地副本的忽略文件非常有用。

git rm --cached database.yml

状态

git status

简要状态

git status -s

推送

git push

试运行(执行所有操作,但不实际发送数据)

git push --dry-run

但它不会显示任何有用信息——请参见下面的命令以获取将要发生的事情的视觉提示

显示哪些文件已更改,并与远程主分支HEAD进行比较

git diff --stat --patch origin master

待推送的文件列表

git diff --stat --cached [远程/分支]

显示待推送文件的代码差异

git diff [远程仓库/分支]

显示将要更改的文件的完整路径

git diff --numstat [远程仓库/分支]

提交

git commit -a

-a是关键,否则你需要为每个更改的文件执行git add

还有-A,但要小心使用,因为它会添加任何已跟踪的文件,这通常不是你想要的。最好忘记这个选项。

认证

缓存认证

git config --global credential.helper cache

调整缓存时间

git config --global credential.helper 'cache --timeout=36000'

更新

git pull

git pull是以下命令的简写

git fetch
git merge FETCH_HEAD

在拉取/推送前显示传入/传出的更改

git log ^master origin/master
git log master ^origin/master

搜索/替换

如何在git仓库中使用CLI安全高效地搜索/替换文件。操作不得触及.git/下的任何内容

find . -type d -name ".git" -prune -o -type f -exec perl -pi -e 's|OLDSTR|NEWSTR|g' {} \;

但它会触及所有文件,从而减慢git端的速度

因此我们希望仅对实际包含旧模式的文件进行操作

grep --exclude-dir=.git -lIr "OLDSTR" . | xargs -n1 perl -pi -e 's|OLDSTR|NEWSTR|g'

git GUI

git

git gui

gitk

gitk --all

贡献者

按提交次数排序的贡献者列表。类似于GitHub的贡献者视图。

git shortlog -sn

搜索git历史

查找提交消息中包含指定单词的所有提交,使用

git log --grep=要搜索的单词

在git历史中搜索字符串

git log -S要搜索的单词

这将找到任何添加或移除字符串password的提交。以下是一些额外选项:

  • -p:将显示差异。如果提供文件(-p file),它将为你生成补丁。
  • -G:查找添加或移除的行匹配给定正则表达式的差异,与-S相反,后者“查找引入或移除字符串实例的差异”。
  • --all:搜索所有分支和标签;或者使用--branches[=<pattern>]--tags[=<pattern>]

从结果中排除某些路径:

排除子文件夹foo

git log -- . ":(exclude)foo"

排除多个子文件夹

git log -- . ":(exclude)foo" ":(exclude)bar"

排除该子文件夹中的特定元素

git log -- . ":(exclude)foo/bar/file"

排除该子文件夹中的任何给定文件

git log -- . ":(exclude)foo/*file"
git log -- . ":(exclude,glob)foo/*file"

使排除不区分大小写

git log -- . ":(exclude,icase)FOO"

哪个分支包含指定的sha键

git branch –contains SHA

挑选

从另一个分支(例如PR)中选择一个提交rev,并将其合并到当前检出中

git show <commit>        # 检查这是正确的rev
git cherry-pick <commit> # 将其合并到当前检出中
git push

合并一系列提交:

git cherry-pick <commit1>..<commitN>

选择性挑选提交的部分内容(仅部分段落/块,而非整个文件):

git cherry-pick -n <commit> # 获取补丁,但不提交(-n = --no-commit)
git reset                   # 取消从挑选的提交中暂存的更改
git add -p                  # 做出所有选择(添加你想要的更改)
git commit                  # 提交更改!

与上述四条命令类似——交互式挑选(-p == –patch):

git checkout -p <commit>

如果只需要特定文件的更改:

git checkout -p <commit> -- path/to/file_a path/to/file_b

从另一个Git仓库挑选提交(可以使用sha1代替FETCH_HEAD):

git fetch <remote-git-url> <branch> && git cherry-pick FETCH_HEAD

中止已经开始的挑选过程,这将恢复到之前的状态:

git cherry-pick --abort

检出

检出特定提交:

git checkout <sha1>/or-short-hash

检出特定分支:

git clone https://github.com/vidartf/nbdime -b optimize-diff2

覆盖本地更改

如果你想从工作副本中移除所有本地更改,只需暂存它们:

git stash push --keep-index

或者,如果很重要,可以命名:

git stash push "你的消息在这里"

在执行git pull后合并使用git stash push保存的本地更改:

git stash pop

如果合并失败,它不会从暂存中移除。

一旦手动解决合并冲突,需要手动调用:

git stash drop

如果你不再需要它们,现在可以删除该暂存:

git stash drop

覆盖所有本地更改,且不需要身份验证:

git reset --hard
git pull

或者:

git checkout -t -f remote/branch
git pull

丢弃特定文件的本地更改:

git checkout dirs-or-files
git pull

通过在重置前从主分支创建一个新分支来保留当前本地提交:

git checkout master
git branch new-branch-to-save-current-commits
git fetch --all
git reset --hard origin/master

从上游拉取并盲目接受所有更改:

git pull --strategy theirs

列出现有暂存:

git stash list

查看暂存:

最新:

git stash show -p

特定暂存:

git stash show -p stash@{0}

使用一条命令显示每个暂存的内容:

git show $(git stash list | cut -d":" -f 1)

与特定暂存进行差异比较:

git diff stash@{0}

与特定暂存的文件名进行差异比较:

git diff stash@{0} my/file.ipynb

比较两个暂存:

git diff stash@{0}..stash@{1}

查看nbdime - Jupyter Notebook的差异和合并工具: https://nbdime.readthedocs.io/en/stable/

分支

删除Git分支(当不在即将被删除的分支内时):

git branch -d branch_name

通过GitHub删除分支——在分支已合并到上游主分支后,现在可以在github.com上删除我的fork中的分支:

1. https://github.com/stas00/fastai/branches

或者前往 https://github.com/stas00/fastai/ (并点击[NN branches]按钮上方[New pull request]按钮):

1. 点击分支旁边的垃圾桶按钮以删除

列出已合并或尚未合并到当前分支的分支。这是在任何合并发生前的有用检查:

git branch –merged
git branch –no-merged

切换回上一个分支(类似于cd -):

git checkout -

@{-1}是一种引用你所在的上一个分支的方式。-@{-1}的简写。 git branch --track mybranch @{-1}git merge @{-1}git rev-parse --symbolic-full-name @{-1}将按预期工作。

在同一仓库中比较两个分支:

git diff --stat --color master..branch_name

或者:

git difftool -d master branch_name

找到从它们的共同祖先到测试的差异,可以使用...代替..

git diff --stat --color master...branch_name

仅比较特定文件:

git diff branch1 branch2 -- myfile1.js myfile2.js

比较不同提交之间的子目录或特定文件:

git diff <rev1>..<rev2> -- dir1 file2

在不同仓库中比较两个分支(例如原始仓库和GitHub fork):

给定两个检出/path/to/repoA/path/to/repoB

cd /path/to/repoA
GIT_ALTERNATE_OBJECT_DIRECTORIES=/path/to/repoB/.git/objects git diff $(git --git-dir=/path/to/repoB/.git rev-parse --verify HEAD) HEAD

另一种使用GUI与meld(apt install meld)的方法:

meld /f1/br/stas00/master/ /f1/br/fastai/master

找到两个分支之间的最佳共同祖先,通常是分支点:

git merge-base master origin/branch_name

同样,但返回短修订版本而不是长版本:

git rev-parse --short $(git merge-base master origin/branch_name)

替代方法(并不总是有效):

git merge-base --fork-point master origin/branch_name

注意,一旦分支被合并到主分支后,‘git merge-base’ 将不再返回输出。

分支点与分支 HEAD 之间的差异

git diff $(git merge-base --fork-point master origin/branch_name)..origin/branch_name

分支点与分支 HEAD 之间的提交记录

git log  --oneline $(git merge-base --fork-point master origin/branch_name)..origin/branch_name

查找包含该提交的分支

git branch --contains <commit>

查找提交何时被合并到一个或多个分支中。

https://github.com/mhagger/git-when-merged

git when-merged [OPTIONS] COMMIT [BRANCH...]

一些关于分支策略的好文档: https://nvie.com/posts/a-successful-git-branching-model/

恢复/重置/撤销

这里有很多场景: https://blog.github.com/2015-06-08-how-to-undo-almost-anything-with-git/

恢复最后一次提交

git revert HEAD

恢复从 HEAD 到提交哈希 0766c053 之间的所有内容

git revert --no-commit 0766c053..HEAD
git commit

这将恢复从 HEAD 到提交哈希之间的所有内容,意味着它将在工作树中重新创建该提交状态,就像自那时以来的每个提交都被回退了一样。然后你可以提交当前的树,它将创建一个全新的提交,本质上等同于你“恢复”到的提交。

--no-commit 标志让 git 一次性恢复所有提交——否则你将被提示为范围内的每个提交输入消息,这会在历史记录中留下不必要的新提交。)

这是一种安全且简单的方式来回滚到之前的状态。历史记录不会被破坏,因此可以用于已经公开的提交。

如果之前发生了合并,恢复可能会失败并要求通过 -m 标志指定使用哪个主线分支。

详细信息:http://schacon.github.io/git/howto/revert-a-faulty-merge.txthttps://stackoverflow.com/questions/5970889/why-does-git-revert-complain-about-a-missing-m-option

恢复仓库到特定修订版本

git checkout <rev>

仅将仓库的某些部分恢复到特定修订版本

git checkout <rev> -- dir1 dir2 file1 file2

将分支的 HEAD 重置为给定的提交哈希

如果分支的 HEAD 不知何故被搞乱并移动到了主分支的某个位置,当有人错误地将它合并到主分支时,这里是如何将其重置回来。在这个例子中,我们将使用分支 imagenette-noise-lb。

  1. 找到本应是 HEAD 的最后一次提交,例如:https://github.com/fastai/fastai/commit/3ac14751101ff3997bc6b3e26f612d1b6d0ac9ea

    可以使用以下命令帮助找到正确的提交:

    git log origin/imagenette-noise-lb

    或者使用 GitHub 上给定标签的分支浏览(例如 imagenette-noise-lb)。

  2. 现在将分支的 HEAD 重置到该提交:

    git checkout imagenette-noise-lb
    git reset --hard g52ff7b4
    git push --force origin imagenette-noise-lb

忽略

临时忽略某个文件的更改,运行:

git update-index --assume-unchanged <file>

重新跟踪更改:

git update-index --no-assume-unchanged <file>

追踪和调试

检查配置来自哪里

git config --list --show-origin

显示特定路径的 git 属性

git check-attr -a dev_nb/001b_fit.ipynb

更多信息在这里:https://git-scm.com/book/en/v2/Git-Tools-Debugging-with-Git

追踪

GIT_TRACE=1 git pull origin master

非常详细

set -x; GIT_TRACE=2 GIT_CURL_VERBOSE=2 GIT_TRACE_PERFORMANCE=2 GIT_TRACE_PACK_ACCESS=2 GIT_TRACE_PACKET=2 GIT_TRACE_PACKFILE=2 GIT_TRACE_SETUP=2 GIT_TRACE_SHALLOW=2 git pull origin master -v -v; set +x

不同的选项:

    GIT_TRACE 用于一般追踪,
    GIT_TRACE_PACK_ACCESS 用于追踪包文件访问,
    GIT_TRACE_PACKET 用于网络操作的包级别追踪,
    GIT_TRACE_PERFORMANCE 用于记录性能数据,
    GIT_TRACE_SETUP 用于获取与仓库及其交互环境的发现信息,
    GIT_MERGE_VERBOSITY 用于调试递归合并策略(值:0-5),
    GIT_CURL_VERBOSE 用于记录所有curl消息(等同于 curl -v),
    GIT_TRACE_SHALLOW 用于调试浅层仓库的获取/克隆。

可能的值包括:

    true, 1 或 2 写入标准错误,
    以 / 开头的绝对路径将追踪输出到指定文件。

状态与信息

事件的简短日志

git log --oneline

显示树的图形,展示合并的分支结构

git log --graph --decorate --pretty=oneline --abbrev-commit

添加 --all 以显示所有分支

显示分支中不在 HEAD 中的所有提交。例如,显示所有在 master 中但尚未合并到当前功能分支的提交。

git log ..master

覆盖 Git 配置

git -c http.proxy=someproxy clone https://github.com/user/repo.git
git -c user.email=email@domain.fr -c user.name='Your Name'

覆盖 git diff:

git diff --no-ext-diff

合并驱动程序没有此类选项。

修复问题

修复错误的合并:https://stackoverflow.com/questions/307828/how-do-you-fix-a-bad-merge-and-replay-your-good-commits-onto-a-fixed-merge

“fatal: Unknown index entry format 61740000”.

当索引损坏时,通常可以删除索引文件并重置它。

rm -f .git/index
git reset

或者你可以重新克隆仓库。

合并策略

通过定义合并过滤器 ‘ours’ 告诉 Git 不要合并某些文件(即保留本地版本)。

https://stackoverflow.com/a/5895890/9201239

  1. 添加到 .gitattributes:
database.xml merge=ours
  1. 设置 git 合并驱动程序为不做任何操作但返回成功
git config merge.ours.name '"always keep ours" merge driver'
git config merge.ours.driver 'touch %A'
git config merge.ours.driver true

工作流程

使用上游更改更新本地检出 https://stackoverflow.com/questions/457927/git-workflow-and-rebase-vs-merge-questions?rq=1

克隆远程仓库
git checkout -b my_new_feature
..工作并提交一些内容
git rebase master
..工作并提交一些内容
git rebase master
..完成功能,提交
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

别名

最好使用编辑器手动添加,但也可以使用 CLI

.gitconfig
   [alias]

例如

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status

取消暂存文件(相当于:git reset HEAD -- fileA):

git config --global alias.unstage 'reset HEAD --'

查看最后一次提交

git config --global alias.last 'log -1 HEAD'

在别名中使用 ! 表示非 git 子命令,例如:

git config --global alias.visual '!gitk'

其他配方

有用的资源