git 笔记
注意:本文档是为 fastai 的早期版本编写的。尚未完全更新至 fastai v2。有关当前说明,请参阅开发者指南。
使用 fastai 时,您可能需要了解一些 git 知识——例如,如果您想为项目做贡献,或者您想撤销代码树中的某些更改。本文档包含各种有用的技巧,可能对您的工作有所帮助。
如何提交拉取请求 (PR)
虽然本指南主要适用于为任何 github 项目创建 PR,但它包含一些特定于 fastai
项目仓库的步骤。
以下说明使用 USERNAME
作为 github 用户名占位符。遵循本指南的最简单方法是将整个部分复制粘贴到一个文件中,将 USERNAME
替换为您真实的用户名,然后按照步骤进行操作。
本指南中的所有示例都是针对使用 fastai 仓库而编写的。如果您想为其他 fastai
项目仓库做贡献,只需在下面的说明中将 fastai
替换为该仓库的名称即可。
另外,不要混淆 fastai
的 github 用户名、fastai
仓库以及 Python 代码所在的 fastai
模块目录。下面的 url 按提及顺序显示了这三者:
https://github.com/fastai/fastai/tree/master/fastai
| | |
username reponame modulename
下面您将找到创建 PR 的详细步骤。
步骤 1. 从同步的 Fork Checkout 开始
1a. 首次操作
如果您已经 fork 了所需的仓库,请跳到 1b 部分。
如果这是您第一次操作,您只需要 fork 原仓库。
1b. 后续操作
如果您在 fork 原仓库后立即创建 PR,那么这两个仓库是同步的,您可以轻松创建 PR。如果时间推移,原仓库会开始与您的 fork 出现分歧,因此当您处理 PR 时,需要保持您的 master fork 与原仓库同步。
您可以通过访问 https://github.com/USERNAME/fastai 并看到类似如下内容来判断您的 fork 的状态:
This branch is 331 commits behind fastai:master.
那么,让我们同步这两个仓库:
定位到 fork 的仓库的
master
分支。返回您之前检出的仓库并切换到master
分支:cd fastai git checkout master
将 fork 的仓库与原仓库同步:
git fetch upstream git checkout master git merge --no-edit upstream/master git push
现在您可以从这个同步的
master
分支创建新分支。通过访问 https://github.com/USERNAME/fastai 并检查是否显示如下内容来验证您的 fork 与原仓库是否同步:
This branch is even with fastai:master.
现在您可以处理一个新的 PR 了。
步骤 2. 创建分支
非常重要的是,您始终要在分支内工作。如果您向 master
分支提交任何更改,您将无法同时创建多个 PR,并且无法在不进行重置的情况下将您的 fork 的 master
分支与原仓库同步。如果您不小心提交到了 master
分支,这并非世界末日,只是让您的操作变得更复杂了。本指南将解释如何处理这种情况。
创建一个任意名称的分支,例如
new-feature-branch
,并切换到该分支。然后设置该分支的上游,以便您无需传递更多参数即可执行git push
和其他 git 命令。git checkout -b new-feature-branch git push --set-upstream origin new-feature-branch
步骤 6. 推送更改
当您对结果满意时,提交新代码:
git commit -a
-a
将自动提交对仓库中任何已修改文件的更改。如果您创建了新文件,请首先告诉 git 跟踪它们:
git add newfile1 newdir2 ...
然后提交。
最后,将更改推送到您的 fork 的分支:
git push
步骤 8. 通过 CI 测试
您的 PR 提交后,您将在 github 上看到我们在 CI 服务器上运行各种测试来验证您的 PR。
如何保持你的功能分支与时俱进
通常,您无需担心更新您的功能分支以与 fastai 代码库(上游 master)同步。您必须执行更新的唯一情况是当您一直在处理的同一代码在 master 分支中发生了更改。因此,当您提交 PR 时,github 会告诉您存在合并冲突。
您可以直接更新您的功能分支,但最好先更新您的 fork 的 master 分支。
步骤 1:同步您的 fork 的
master
分支cd fastai git fetch upstream git checkout master git merge --no-edit upstream/master git push --set-upstream origin master
步骤 2:更新您的功能分支
my-cool-feature
git checkout my-cool-feature git merge origin/master
步骤 3:解决合并产生的任何冲突(使用您的编辑器或专门的合并工具),然后对发生冲突的文件执行
git add
。步骤 4:将对您分支的更新推送到 github
git push
如果您的 PR 已经打开,github 将自动更新其状态以显示新的提交,如果您按照上述步骤操作,冲突应该不再存在。
如何重置你的 Forked 主分支
如果您没有小心创建分支,而是提交到了您 fork 的仓库的 master
分支,那么您将无法在不重置的情况下将其与原仓库同步。当您想要创建分支时,在 PR 过程中也会出现问题,因为它将针对一个已经分歧的源进行。
当然,最暴力的方法是前往 github,删除您的 fork(这将删除您在该 fork 上所做的所有工作,包括任何分支,因此如果您决定这样做,请务必小心,因为将无法恢复您的数据)。
一个更安全的方法是将您的 fork 的 master
的 HEAD
重置为原仓库的 HEAD
:
如果您尚未设置上游,请立即执行此操作:git remote add upstream [email protected]: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 环境名称。
如果您没有使用 bash
或 fish
shell,请搜索适用于其他 shell 的类似工具。
Github 快捷方式
按作者显示提交:
?author=github_username
您可以通过在提交视图 URL 末尾添加参数
?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 将发生变化以反映您的选择。您可以使用该链接告诉其他人查看特定代码行或特定代码块。
删除 fork
- 前往 github.com/USERNAME/FORKED-REPO-NAME/
- 点击 Settings (设置)
- 向下滚动并点击 [Delete this repository] (删除此仓库)
将
USERNAME
替换为您的 github 用户名,将FORKED-REPO-NAME
替换为仓库名称
修订
相对引用
^ - one commit at a time (parent of the specified commit)
master^ = the first parent of master
master^^ = the first grandparent of master
~<num> - several commits
操作
add (添加)
git add [folder/file]
remove (移除)
git rm [folder/file]
仅删除远程文件副本。例如,删除已入库的 database.yml,但保留本地副本不变。这对于删除已推送的被忽略文件(而不删除本地副本)非常方便。
git rm --cached database.yml
status (状态)
git status
简要状态
git status -s
push (推送)
git push
试运行 (执行除实际发送数据之外的所有操作)
git push --dry-run
但这不会显示任何有用的信息 - 请参阅下面的命令以获取将发生什么的视觉提示
显示哪些文件已更改,并查看与远程 master 分支 HEAD 的差异
git diff --stat --patch origin master
将将被推送的文件列表
git diff --stat --cached [remote/branch]
显示将要推送的文件的代码差异
git diff [remote repo/branch]
显示将要更改的文件的完整文件路径
git diff --numstat [remote repo/branch]
commit (提交)
git commit -a
-a
至关重要,因为如果没有它,您需要对每个已更改的文件执行 git add
!
还有 -A
,但使用它要小心,因为它会添加任何已跟踪的文件,这通常可能不是您想要的。最好忘记这个选项。
authentication (认证)
缓存认证
git config --global credential.helper cache
调整缓存时间
git config --global credential.helper 'cache --timeout=36000'
update (更新)
git pull
git pull 是以下命令的简写:
git fetch
git merge FETCH_HEAD
在 pull/push 之前显示传入/传出的更改
git log ^master origin/master
git log master ^origin/master
search/replace (搜索/替换)
如何使用 CLI 安全高效地在 git 仓库中搜索/替换文件。此操作不得触碰 .git/ 下的任何内容。
find . -type d -name ".git" -prune -o -type f -exec perl -pi -e 's|OLDSTR|NEWSTR|g' {} \;
但它会 touch(1) 所有文件,这会减慢 git 的速度
所以我们只想对实际包含旧模式的文件进行操作
grep --exclude-dir=.git -lIr "OLDSTR" . | xargs -n1 perl -pi -e 's|OLDSTR|NEWSTR|g'
git GUI (图形界面)
git
git gui
gitk
gitk --all
contributors (贡献者)
显示按提交次数排序的贡献者列表。类似于 GitHub 的贡献者视图。
git shortlog -sn
搜索 git 历史
要查找提交消息中包含给定单词的所有提交,请使用:
git log --grep=word_to_search_for
在所有 git 历史记录中搜索字符串
git log -Sword_to_search_for
这将找到添加或删除了字符串 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 key
git branch –contains SHA
cherry picking (拣选)
从一个分支(例如 PR)选择一个提交版本并将其合并到当前检出
git show <commit> # check that this is the right rev
git cherry-pick <commit> # merge it into the current checkout
git push
合并一系列提交
git cherry-pick <commit1>..<commitN>
拣选提交的一部分(仅拣选部分/块,而不是整个文件)
git cherry-pick -n <commit> # get your patch, but don't commit (-n = --no-commit)
git reset # unstage the changes from the cherry-picked commit
git add -p # make all your choices (add the changes you do want)
git commit # make the commit!
类似于上面的 4 个命令 - 交互式拣选 (-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
中止已开始的 cherry-pick 过程,这将回退到之前的状态
git cherry-pick --abort
checkout (检出)
检出特定提交
git checkout <sha1>/or-short-hash
检出特定分支
git clone https://github.com/vidartf/nbdime -b optimize-diff2
覆盖本地更改
如果您想移除工作副本中的所有本地更改,只需暂存它们
git stash push --keep-index
或者如果重要,您可以给它命名
git stash push "your message here"
在执行 ‘git pull’ 后合并通过 ‘git stash push’ 保存的本地更改
git stash pop
如果合并失败,它不会从 stash 中移除。
合并冲突手动解决后,需要手动调用
git stash drop
如果您不再需要它们,现在可以丢弃该 stash
git stash drop
覆盖所有本地更改且不需要身份验证
git reset --hard
git pull
或者
git checkout -t -f remote/branch
git pull
丢弃特定文件的本地更改
git checkout dirs-or-files
git pull
在重置之前,通过从 master 创建一个分支来保留当前本地提交
git checkout master
git branch new-branch-to-save-current-commits
git fetch --all
git reset --hard origin/master
从上游拉取并盲目接受所有更改
git pull --strategy theirs
列出现有的 stashes
git stash list
查看 stashes
最新的
git stash show -p
特定 stash
git stash show -p stash@{0}
用一个命令显示每个 stash 的内容
git show $(git stash list | cut -d":" -f 1)
与特定 stash 进行 diff
git diff stash@{0}
与特定 stash 的文件名进行 diff
git diff stash@{0} my/file.ipynb
对比 2 个 stashes
git diff stash@{0}..stash@{1}
查看 nbdime - 用于 Jupyter Notebook 的 diff 和合并工具 https://nbdime.readthedocs.io/en/stable/
branches (分支)
git 分支删除(当您不在要删除的分支内时)
git branch -d branch_name
通过 github 删除分支 - 在分支合并到上游 master 后,现在可以在 github.com 上删除我的 fork 中的分支
1. https://github.com/stas00/fastai/branches
或者访问 https://github.com/stas00/fastai/ (并点击 [New pull request] 按钮上方的 [NN branches])
1. hit the trash button next to the branch to remove
列出已合并或尚未合并到当前分支的分支。这是在进行任何合并之前有用的检查
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
查找从它们共同祖先到 test 的差异,您可以使用 … 代替 ..
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
使用带有 meld 的 GUI 的另一种方法 (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
注意,一旦分支合并到 master,‘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/
reverting/resetting/undoing (回滚/重置/撤销)
这里有很多场景: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 一次性撤销所有提交——否则您将需要为范围内的每个提交输入消息,从而在您的历史记录中留下不必要的新的提交。)
这是一个安全且简单的回滚到之前状态的方法。不会破坏任何历史记录,因此可以用于已公开的提交。
如果之前发生了合并,revert 可能会失败并要求通过 -m 标志指定特定的父分支来指定使用哪个主线
详情请参阅:http://schacon.github.io/git/howto/revert-a-faulty-merge.txt 和 https://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 不知何故出错并移到了 master 中的某个位置,当有人不小心将其合并到 master 中时,这里介绍如何将其重置回去。在此示例中,我们将使用分支 imagenette-noise-lb。
查找本应是 HEAD 的最后一个提交,例如:https://github.com/fastai/fastai/commit/3ac14751101ff3997bc6b3e26f612d1b6d0ac9ea
可以使用此命令帮助找到正确的提交
git log origin/imagenette-noise-lb
或者使用 github 的分支浏览给定标签(本例中为 imagenette-noise-lb)。
现在将分支的 HEAD 重置到它
git checkout imagenette-noise-lb git reset --hard g52ff7b4 git push --force origin imagenette-noise-lb
ignore (忽略)
临时忽略某个文件的更改,运行
git update-index --assume-unchanged <file>
再次跟踪更改
git update-index --no-assume-unchanged <file>
trace and debug (跟踪和调试)
检查哪个配置来自哪里
git config --list --show-origin
显示特定路径的 git 属性
git check-attr -a dev_nb/001b_fit.ipynb
更多内容请参阅:https://git-scm.cn/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 for general traces,
GIT_TRACE_PACK_ACCESS for tracing of packfile access,
GIT_TRACE_PACKET for packet-level tracing for network operations,
GIT_TRACE_PERFORMANCE for logging the performance data,
GIT_TRACE_SETUP for information about discovering the repository and environment it’s interacting with,
GIT_MERGE_VERBOSITY for debugging recursive merge strategy (values: 0-5),
GIT_CURL_VERBOSE for logging all curl messages (equivalent to curl -v),
GIT_TRACE_SHALLOW for debugging fetching/cloning of shallow repositories.
possible values can include:
true, 1 or 2 to write to stderr,
an absolute path starting with / to trace output to the specified file.
status and information (状态和信息)
事件的简短日志
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 [email protected] -c user.name='Your Name'
覆盖 git diff
git diff --no-ext-diff
合并驱动程序没有此选项。
修复问题
“致命错误: 未知索引条目格式 61740000”。
当您的索引损坏时,通常可以删除索引文件并重置它。
rm -f .git/index
git reset
或者重新克隆仓库。
合并策略
通过定义合并过滤器 'ours' 来告诉 git 不合并某些文件(即保留本地版本)。
https://stackoverflow.com/a/5895890/9201239
- 添加到 .gitattributes
database.xml merge=ours
- 设置 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
clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
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'
其他技巧
从 git 树下载子目录,例如 https://github.com/buckyroberts/Source-Code-from-Tutorials/tree/master/Python
- 将 tree/master 替换为 trunk
- svn co 新的 url
svn co https://github.com/buckyroberts/Source-Code-from-Tutorials/trunk/Python
有用资源
- https://learngitbranching.js.org/ - 带练习的可视化教学