git 那些事儿 —— 基于 Learn Git Branching
前言
推荐一个 git 图形化教学网站:Learn Git Branching,这个网站有一个沙盒可以直接在上面模拟 git 的各种操作,操作效果使用图形的方式展示,非常直观。本文可以看作是它的文字版,将其中各级关卡所要学习的概念和命令提取出来,方便查阅。文中的一些示例,如果没有显而易见的输出,就需要读者在沙盒中亲自输入来查看效果。
git 日常操作
git 命令虽多,但也遵循 80/20 法则,经常用到的也就下面几个:
git clone project-name git checkout branch-name git branch git pull origin master git add file-name git rm file-name git mv file-name git status git log git commit -m comment-string git push origin master git merge branch-name
会了这些命令,一些日常的操作就基本没问题了,那 git 还有什么可学的呢?有的,提交错了需要回滚怎么操作?多个 commit 如何合并成一条提交?如何定位分支上的一个提交(以便对比、回退、拉取新分支)?在一个分支上处理多个任务如何切换?如何调整一条分支上多个 commit 的顺序?如何将一台机器上的修改打成补丁在另一台机器上提交?本地删除一个分支如何同步到远程?……下面随着教程我们来一探究竟。
本地分支操作
分支是 git 最重要的概念之一,用好分支是用好 git 的基础。git 的分支非常轻量级,不会造成存储或内存上的开销,所以应该 “早建分支、多用分支”!只要记住使用分支其实就相当于在说:“我想基于这个提交以及它所有的父提交进行新的工作” 就好啦。
新建并切换分支
git branch bugFix git checkout bugFix <==> git checkout -b bugFix
以上两个等价,都是在当前提交上创建分支并切换到新分支上准备工作。
合并分支 - merge
git checkout -b bugFix git commit -m 'a' git checkout master git commit -m 'b' git merge bugFix
在 git 中合并两个分支时会产生一个特殊的提交记录,它有两个父节点。翻译成自然语言相当于:“我要把这两个父节点本身及它们所有的祖先都包含进来。”
git checkout bugFix git merge master
因为 master 继承自 bugFix,Git 什么都不用做,只是简单地把 bugFix 移动到 master 所指向的那个提交记录。
合并分支 - rebase
git checkout -b bugFix git commit -m 'a' git checkout master git commit -m 'b' git checkout bugFix git rebase master <===> git rebase master bugFix
rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去,相比 merge 的优势是可以创造更线性的提交历史,代码库的提交历史将会变得异常清晰。rebase 第二个参数是要移动的源分支,如果不提供默认为 HEAD。如果当前分支不在源分支上,使用第二个参数可以节省一次 checkout 动作。
git checkout master git rebase bugFix <===> git rebase bugFix master
同理,由于 bugFix 继承自 master,所以 git 只是简单的把 master 分支的引用向前移动了一下而已
在提交树上移动
HEAD 是一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录,大多数修改提交树的 git 命令都是从改变 HEAD 的指向开始的。
分离的 HEAD
HEAD 通常情况下是指向分支名的 (如 bugFix),分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。
git commit -m 'c' git checkout 'c'
相对引用
git 中 commit 以 hash 值作为名字,基于 sha-1 的哈希值长达 40 位,虽然 git 对哈希的处理很智能 —— 你只需要提供能够唯一标识提交记录的前几个字符即可,但是使用哈希值仍然很不直观。
通用
git 中的相对引用可以从 HEAD 或分支名等易于记忆的地方开始计算,主要有两种形式:
- ^ - 指定位置的上一个提交
- ~N - 指定位置的前 N 个提交,未提供 N 时等价于 ^
示例:
git checkout master^ git checkout bugFix^^ git checkout HEAD^ git checkout HEAD^ git checkout HEAD^ <===> git checkout HEAD~3
选择父提交
操作符 ^ 与 ~ 符一样,后面也可以跟一个数字。但不是用来指定向上返回几代,而是指定合并提交记录的某个父提交。一个合并提交有两个父提交,所以遇到这样的节点时该选择哪条路径就不是很清晰了。git 默认选择合并提交的“第一个”父提交,在操作符 ^ 后跟一个数字可以改变这一默认行为。
git checkout -b bugFix git commit -m 'a' git checkout master git commit -m 'b' git merge bugFix git checkout HEAD^ # master 'b' git checkout HEAD^2 # bugFix 'a'
链式操作
^ 与 ~ 可以结合使用,实现快速移动
git checkout -b bugFix git commit -m 'a' git commit -m 'b' git commit -m 'c' git checkout master git commit -m 'd' git commit -m 'e' git merge bugFix git commit -m 'f' git checkout HEAD~ # merged commit git checkout HEAD^2 # bugFix 'c' git checkout HEAD~2 # bugFix 'a' <===> git checkout HEAD~^2~2 # bugFix 'a'
示例中将三条移动指令合并成了一条,使用链式操作可以大大减少移动的命令次数。
强制移动分支
git branch -f master HEAD~3 git branch -f bugFix bugFix~3
结合上一节的内容,可以将分支强制移动到提交树上的任意位置,一般只在处理特殊情况时用到,平时慎用。
撤销变更 - reset
git reset 通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。
git reset HEAD^
撤销最近一次提交。在 reset 后,最近一次提交所做的变更还在,但是处于未加入暂存区(Unstage)状态。
撤销变更 - revert
虽然在本地分支中使用 git reset 很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的!为了撤销更改并分享给别人,我们需要使用 git revert。
git revert HEAD
同样是撤销最近一次提交,在 revert 后多了一个新提交,这个提交刚好是用来撤销 HEAD 这个提交的。revert 之后就可以把你的更改推送到远程仓库与别人分享了。
整理提交记录
开发人员有时会说“我想要把这个提交放到这里, 那个提交放到刚才那个提交的后面”, 而接下来就讲的就是它的实现方式
cherry-pick
如果你想将一些提交复制到当前所在的位置 (HEAD) 下面的话, cherry-pick 是最直接的方式了。
git checkout -b side git commit -m 'a' git commit -m 'b' git commit -m 'c' git checkout master git cherry-pick 'a' 'c'
相对 rebase 的最大区别是,用户可以指定要复制的 commit 而不是一股脑都合并进来。
交互式 rebase
cherry-pick 简单而有效,但前提是你知道想要 commit 的明确的哈希值,想从一系列的提交记录中找到想要的记录,使用交互式 rebase 就是最好的方法了。
git checkout -b side git commit -m 'a' git commit -m 'b' git commit -m 'c' git rebase -i HEAD~3
交互式 rebase 指的是使用带参数 --interactive 的 rebase 命令,简写为 -i。如果你在命令后增加了这个选项,git 会打开一个 UI 界面并列出将要被复制到目标分支的备选提交记录,它还会显示每个提交记录的哈希值和提交说明,后者有助于你理解这个提交进行了哪些更改。
pick 85c5736 udpate score pick 2b4a381 udpate score pick d40db94 udpate score pick e957b1b udpate score pick 89c5b59 udpate score # Rebase 711f9d2..89c5b59 onto e957b1b (5 commands) # # Commands: # p, pick= use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop = remove commit # l, label
上面是执行 git rebase -i HEAD~5
的编译器界面 (这里使用了 vim 作为默认的 git 编辑器)。
可以看到用户在这个界面通过调整 action 关键字来选择 commit 并调整它们的顺序,甚至可以对 commit 进行合并处理,合并方式也是比较丰富的,可以保留每条 commit 的提交信息,也可以忽略它们。
在 Learn Git Branching 中当 rebase UI 界面打开时,它可以完成三种功能:
- 调整提交记录的顺序 (通过鼠标拖放来完成)
- 删除你不想要的提交 (通过切换 pick/ommit 的状态来完成,点击 ommit 就意味着你不想要这个提交记录)
- 合并提交 (它允许你把多个提交记录合并成一个)
实际上只有前两种是支持的。
锚定 commit
分支很容易被人为移动,并且当有新的提交时,它也会移动,分支很容易被改变。当软件发布新的大版本或者是修正一些重要的 bug 或是增加了某些新特性,就需要永远指向某个提交记录的标识。
git tag
git tag 可以永久地将某个特定的提交命名为里程碑,然后就可以像分支一样引用了。更难得的是,它们并不会随着新的提交而移动。你也不能检出到某个标签上面进行修改提交,它就像是提交树上的一个锚点,标识了某个特定的位置。
git commit -m 'a' git commit -m 'b' git commit -m 'c' git tag v1 'a' git tag v2 HEAD^ git tag v3
当不提供 commit 参数时,默认使用 HEAD 代替。
git describe
用于查找给定 commit 最近的 tag 信息。
git commit -m 'a' git tag v1 git commit -m 'b' git commit -m 'c' git tag v2 git describe # v2 git describe HEAD^ # v1_1_g'b'
命令语法为:git describe <ref>
输出格式为:<tag>_<numCommits>_g<hash>
tag 表示的是离 ref 最近的标签, numCommits 是表示这个 ref 与 tag 相差有多少个提交记录, hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。然后就可以用这个信息去引用相关的 commit 了,简直是 CUI 的福音有木有~
上面演示的是 Learn Git Branching 的效果,在 git 命令中,貌似需要加入 --tags 才会生效:
$ git describe --tags v2 $ git describe HEAD^ --tags v1-1-ge957b1b
而且分隔符也变成了中划线。
远程分支操作
远程仓库
远程仓库实际上只是你的仓库在另个一台计算机上的拷贝。你可以通过因特网与这台计算机通信 —— 也就是增加或是获取提交记录。现在用网站来对远程仓库进行可视化操作变得越发流行了(像 Github 或 Phabricator),但是理解其中的基本概念非常重要。
git clone remote-repository-git
git clone 会在本地创建一个远程仓库的拷贝,同时创建对应的远程分支来记录与远程仓库的同步状态,远程分支见下一节内容。
远程分支
远程分支反映了远程仓库 (在你上次和它通信时) 的状态,远程分支命名规范:<remote repository name>/<branch name>
,repository 一般为 origin,这是因为当你用 git clone 某个仓库时,git 已经帮你把远程仓库的名称设置为 origin 了。远程分支有一个特别的属性,在你检出时自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因,你必须在别的地方完成你的工作,(更新了远程分支之后) 再用远程分享你的工作成果。
git checkout origin/master git commit -m 'a'
此时 git 变成了分离 HEAD 状态,当添加新的提交时 origin/master 也不会更新。这是因为 origin/master 只有在远程仓库中相应的分支更新了以后才会更新。
$ git checkout origin/master Note: switching to 'origin/master'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -cOr undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at 89c5b59 udpate score
此时你可以提交 commit,但是它们在下次与远程分支同步后将自动消失。
从远程仓库获取数据
当从远程仓库获取数据时, 远程分支也会更新以反映最新的远程仓库
git fetch
git fetch 完成了仅有的但是很重要的两步:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针 (如 origin/master)
git fetch 实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。
git fetch 并不会改变你本地仓库的状态。它不会更新你的 master 分支,也不会修改你磁盘上的文件。
将变化更新到当前工作中
git fetch git merge/rebase/cherry-pick origin/master <===> git pull
当远程分支中有新的提交时,你可以像合并本地分支那样来合并远程分支,由于先抓取更新再合并到本地分支这个流程很常用,因此 git 提供了一个专门的命令来完成这两个操作,它就是 git pull。
git fetch git merge <===> git pull git fetch git rebase <===> git pull --rebase git push
pull 默认也可以使用 rebase 进行合并,这是通过提供 --rebase
参数实现的。以下是关于 rebase 的优缺点:
- 优点: Rebase 使你的提交树变得很干净, 所有的提交都在一条线上
- 缺点: Rebase 修改了提交树的历史
比如, 提交 C1 可以被 rebase 到 C3 之后。这看起来 C1 中的工作是在 C3 之后进行的,但实际上是在 C3 之前。一些人喜欢保留提交历史,就可以选择 merge;其他人喜欢干净的提交树,于是选择 rebase。
向远程仓库传输数据
git push 负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。一旦 git push 完成, 你的朋友们就可以从这个远程仓库下载你分享的成果了。一个名为 push.default 的配置会影响不带参数的 git push 的行为,它的默认值取决于 git 的版本,这里假定是 upstream。
git push
git push 成功后远程分支 (origin/master) 也同样被更新了,所有的分支都同步了
偏离的历史
上面的例子都比较简单,与远程仓库同步的真正困难来自于远程提交历史的偏离。在远程仓库被修改且与你的本地工作有冲突的情况下, git push 就不知道该如何操作了。这种情况 (历史偏离) 有许多的不确定性,Git 是不会允许你 push 变更的。实际上它会强制你先合并远程最新的代码,然后才能分享你的工作。
远程跟踪
master 被设定为跟踪 origin/master —— 这意味着为 master 分支指定了推送的目的地以及拉取后合并的目标。当你克隆时,git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支 (比如 origin/master)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 master。克隆完成后,你会得到一个本地分支,但是可以查看远程仓库中所有的分支。这样做对于本地仓库和远程仓库来说,都是最佳选择。在克隆的时候会看到下面的输出:
local branch "master" set to track remote branch "origin/master"
你可以让任意分支跟踪 origin/master,然后该分支会像 master 分支一样得到隐含的 push 目的地以及 merge 的目标。 这意味着你可以在分支 dev 上执行 git push,将工作推送到远程仓库的 master 分支上:
git checkout -b dev origin/master git pull git commit -m 'a' git push
注意,此时 master 分支不会有任何改变。
git branch -u origin/master dev
这种方法当当前分支为 dev 时,最后一个参数可省略。总体上不推荐上面的做法,容易造成混乱。
使用 push 参数可以忽略 HEAD 的设置:
git commit -m 'a' git checkout master~2 # git push <remote> <place> git push origin master
切到本地仓库中的“master”分支,获取所有的提交,再到远程仓库“origin”中找到“master”分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。我们通过“place”参数来告诉 git 提交记录来自于 master,要推送到远程仓库中的 master。它实际就是要同步的两个仓库的位置。需要注意的是,因为我们通过指定参数告诉了 git 所有它需要的信息, 所以它就忽略了我们所检出的分支的属性。即使当前处理偏离历史状态,也可以推送成功。如果源分支与推送目的分支不同名,可以通过指定 push 的第二个参数来实现:
# git push origin <source>:<destination> git push origin master^:foo
将 dev 分支中的修改推送到远程服务的 master 分支,忽略当前 HEAD 设置。当目的分支不存在时,git 会自动创建。
这里可以举一个实际的例子,大一点的公司都有自建的代码库系统,可以设置代码库是否允许开发者直接提交代码,一般设置为否,需要提交到一个特殊的分支,经过同行评审后再合入到正式分支:
git push origin HEAD:refs/for/master
之前看不明白为什么这样写,现在结合上面的语法就明白了,是将当前修改提交到 refs/for/master 这个特殊分支,代码库系统再利用这个分支做评审控制,perfect~
git fetch 也有类似的参数:
# git fetch <remote> <place> git fetch origin foo
只更新远程服务的 foo 分支到本地 origin/foo 上。也可以使用分号形式的第二个参数:
# git fetch <remote> <source>:<destination> git fetch origin master:foo
将远程服务的 master 分支直接更新到本地 foo 分支,注意不是 origin/foo 远程分支!它将直接修改你的本地分支,所以使用时需要小心。这里可以这样做有一个前提,就是当前检出分支不可以是 foo。另外如果 foo 不存在,git 将自动创建这个本地分支。这里有一点是需要注意的 —— source 现在指的是远程仓库中的位置,而 destination 才是要放置提交的本地仓库的位置。
总体上不推荐上面的做法,容易造成混乱,在这里介绍它主要是为了从概念上说明 fetch 和 push 的相似性。
不带 <source> 的 <place> 作用于 push 时会删除远程服务中的分支。
git push origin :foo
不带 <source< 的 <place< 作用于 fetch 时会在本地创建一个新分支
git fetch origin :bar
相当于 git branch 命令。
同理,由于 git pull = git fetch + git merge,pull 也可以携带额外的两个参数:
git pull origin foo <===> git fetch origin foo git merge origin/foo git pull origin bar~1:bugFix <===> git fetch origin bar~1:bugFix git merge bugFix
两个参数原封不动传递给了 fetch,<destination> 还会影响 merge,就是这样简单。
小技巧
这部分内容是平时的一些积累,与教程互为补充,偏重实用一些。
稀疏检出
目录太大一次检出可能由于网络不稳定而经常失败时,可以尝试稀疏检出
git init git config core.sparsecheckout true # git config -l # do NOT checkout third-party echo "/*" >> .git/info/sparse-checkout echo "!third-party" >> .git/info/sparse-checkout git remote add origin git@github.com:goodpaperman/apue.git git pull origin master
上面将 third-party 中的内容屏蔽,之后稍作设置便可以继续检出:
git config core.sparsecheckout false # clear third-party item vi .git/info/sparse-checkout git read-tree -mu HEAD
查看远程服务地址
本地太多库了,搞不清哪个是哪个,可以用这个命令查看:
git remote -v # origin git@github.com:goodpaperman/apue.git (fetch) # origin git@github.com:goodpaperman/apue.git (push)
从而确定与本地 git 库关联的远程服务位置
查看本地修改
git status git status -uno # 不显示非版本控制文件 # On branch master # Your branch is up to date with 'origin/master'. # # Changes not staged for commit: # (use "git add..." to update what will be committed) # (use "git restore ..." to discard changes in working directory) # modified: score.txt # modified: predicate_merged.data git status -suno # 显示简略信息 (可用于后续 shell 处理) # M score.txt # M predicate_merged.data git ls-files # 查看处于版本控制的文件列表 (默认递归) git diff path # 查看文件详情对比,针对目录操作时显示该目录下所有文件的差异
添加/删除本地修改
git add path # 将文件添加到版本控制 git add -f path # 当文件命中 .gitignore 规则时,需要通过 -f 来强制添加 git add -u # 仅添加在版本控制下且有变更的文件 git add -u path # 仅添加指定路径中在版本控制下且有变更的文件 git add -u :/ # 从根目录开始添加 (如果当前路径不在根目录,而有一些变更位于当前目录外时使用) git add -- ':!demo' # 指定 exclude 的路径 (demo) git rm --cached path # 将新添加的文件从版本控制中移除,仅适用于已 add 未 commit 的场景,移除后新文件将在本地保留, # 仍可 add。如果针对已处理版本控制的文件,则只删除版本控制中的文件,不删除本地副本 git checkout path # 抛弃本地修改,恢复到暂存区的状态,仅适用于未 add 的场景 # (add 后又修改的情况下会恢复到最后一次 add 时的状态)
提交更改
git commit # 弹出编辑框输入 comment git commit -m "xxx" # 直接提供 comment 不再弹编辑框 git commit --amend # 将本次提交合并到上一次提交中去 (有些 git 库要求一次 push # 只能包含一个提交或只想变更 commit-message 时很有用)
撤销变更
git reset HEAD # 撤销 add 但未 commit 的修改,修改将在本地保留,仍可 add git reset --soft origin/master # 将所有本地已 commit 但未 push 的提交揉合成一条提交 (需要再加一句 git add -u) git reset --hard origin/master # 丢弃所有本地已 commit 但未 push 的提交 (谨慎使用!) git revert HEAD # 撤销已 push 的提交,生成一个相反的 commit, # 提交后 push 可让远程服务上的分支恢复 push 前的状态 git revert -n HEAD~2..HEAD # 撤销多次提交,-n 参数防止每个提交生成一个对应的 revert commit git revert -m 1 HEAD # 选择回滚合并节点的哪一个父提交 (一个合并节点包含两个分支的内容) git revert --continue # 处理冲突后继续撤销 git revert --abort # 结束撤销任务,恢复到撤销前的状态 git rm path # 演示如何撤销 rm 动作 git reset HEAD path # step1:取消暂存 git chekcout -- path # step2: 丢弃工作区改动 git reset --hard <ref> # 撤销更改到某个提交点 git push --force # 强制推送到服务器可在抹除历史记录 # 使用时要小心!!
查看日志
git log # 显示提交信息详情 git log branch-name # 显示另一个分支提交记录 git log --oneline # 显示简略的提交信息 (一行一个提交) git log --oneline --graph # 显示分支树 (一般与 --oneline 同时使用) git show <ref> # 查看某次提交的详细 diff 输出 git show <ref> --stat # 查看某次提交的文件变更列表
分支
git branch # 查看当前分支 git branch --show-current # 查看当前分支 (仅显示分支名,可用于命令组合) git branch -a # 查看全部分支 (包括远程分支) git branch foo HEAD^ # 从特定提交创建分支 git branch -d foo # 删除本地分支 git push origin -d foo # 删除远程分支
问题追踪
git blame file # 查看文件修改历史 git blame <ref> file # 查看某个提交前 (包含这个提交) 的文件修改历史 git blame <ref>^ file # 查看某个提交前 (不包含这个提交) 的文件修改历史 git blame <ref1>..<ref2> file # 查看从 ref1 到 ref2 (包含这个两个提交) 之间的文件修改历史 git blame <ref>.. file # 查看某个提交之后 (包含这个提交) 的文件修改历史
注:可结合前面提到的链式操作定位任意 commit
合并分支
git merge foo # 以当前分支为基础合并分支 foo git mergetool # 调用合并工具处理冲突文件 (默认为 vimdiff,可配置,处理后的文件会自动 commit) git merge --abort # 结束 merge 过程,恢复到 merge 开始前的状态
临时保存修改
git stash # 将当前未提交修改放入存储区备用 git stash list # 查看存储区列表 git stash pop # 恢复上次 stash 的内容,成功恢复后内容将被删除 git stash save bugFix # 同 git statsh,增加备注信息 git stash apply bugFix # 恢复某次 stash 的内容,成功恢复后内容不会删除 git stash show bugFix # 显示存储详情 git stash branch bugFix # 从存储创建分支 (以便下一步处理) git stash drop bugFix # 显式删除 git stash clear # 删除所有 (谨慎使用!)
补丁的生成与应用
git diff > feature.patch # 将当前未提交修改放入存储区备用 git diff --cached > feature.patch # 将已 add 未 commit 的文件也列入补丁 git diff --binary > feature.patch # 将二进制文件也列入补丁 git apply --stat feature.patch # 查看补丁内容 git apply --check feature.patch # 检查补丁是否能打上 git apply feature.patch # 应用补丁 git format-patch <r1>..<r2> # 生成 r1 至 r2 的 patch git format-patch -1 <r1> # 生成单个提交的 patch git format-patch <r1> # 生成自某个提交以来的 patch (不含 r1) git format-patch --root <r1> # 生成从根自某个提交的 patch git format-patch HEAD^ # 生成最近一次提交的 patch git am xxx.patch # 打补丁 git am --signoff xxx.patch # 将自己添加到 signed off by 信息中, # 补丁作者与打补丁的人可能不是同一个 git am ~/patch-set/*.patch # 目录中的补丁按照先后顺序打上 git am --abort # 废弃打的补丁 git am --resolved # 解决完冲突后,接着打补丁
模块
一个 git 仓库可以引用另外的 git 仓库,这就是模块:
git submodule add git@github.com:ebiggers/libdeflate.git third-party/libdeflate git submodule status git submodule update git submodule foreach git pull
模块可以嵌套。可在拉取主仓库时选择一次性下载:
git clone --recurse-submodules git@github.com:goodpaperman/transmission.git 或 git clone --recursive git@github.com:goodpaperman/transmission.git
也可选择先下载主仓库,再递归更新模块:
git clone git@github.com:goodpaperman/transmission.git git submodule update --init --recurisve
平时更新时也要带上子模块:
git pull --recurse-submodules
本地目录作为 git 仓库推送到远程
git init git add . git commit -m "initiali commit" # git branch -M main git remote add origin git@github.com:goodpaperman/bare.git # 指定远程仓库地址 git push -u origin main # 执行推送
git 设置
多帐号设置
添加文件 ~/.ssh/config:
# gitee Host gitee.com HostName gitee.com PreferredAuthentications publickey IdentityFile ~/.ssh/gitee_id_rsa # github default Host github.com HostName github.com PreferredAuthentications publickey IdentityFile ~/.ssh/github_id_rsa # github haihai107 Host github.com HostName github.com User haihai107@126.com PreferredAuthentications publickey IdentityFile ~/.ssh/hai_github_id_rsa
再分别生成 gitee_id_rsa[.pub] 与 [hai_]github_id_rsa[.pub] 文件即可。
需要注意的是当一个远程服务下有多个用户帐户时,不可指定全局的 user.name 和 user.email 设置:
git config --global user.name yunhai git config --global user.email haihai107@126.com
需要取消这些设置,代之以分别设置当前 git 库 (--local)
git config --global --unset user.name git config --global --unset user.email # cd to git repository directory git config --local user.name yunhai git config --local user.email haihai107@126.com
对比工具
通过 ssh 远程访问时,一般没有图形界面,此时推荐 vimdiff 作为默认的对比工具。
git config --global diff.tool vimdiff # 设置 git 默认编辑器 git config --global difftool.prompt false # 启动外部工具前不再单独提示用户 git config --global alias.d difftool # 使用 git d 调出新对比工具
本机访问时,可以设置更高级的图形界面工具,例如 beyond compare/meld/kdiff3/winmege/p4merge……设置方法大同小异
git config --global diff.tool bc4 # 设置 git 默认编辑器 git config --global difftool.prompt false # 启动外部工具前不再单独提示用户 git config --global difftool.bc4.cmd '"/usr/local/bin/bcomp" "$LOCAL" "$REMOTE"' # 使用 git diff 调用新对比工具时向 beyondcompare 传递参数的顺序
早些时候一般使用 git external,首先准备一个 shell 脚本(extdiff.sh),它固定接收 6 个参数,将其中两个传递给要调用的 diff 命令:
#! /bin/sh
if [ $# -ne 7 ]; then
echo Usage: extdiff.sh first-file second-file
fi
# parameter passed by git diff:
# path old-file old-hex old-mode new-file new-hex new-mode
# beyond compare only what $2 & $5
"/Applications/Beyond Compare.app/Contents/MacOS/bcomp" "$2" "$5"
# ensure exit code is accepted by git
exit 0
将这个脚本放置于对比命令所在目录下,然后配置 git 使用该脚本作为默认的 diff 命令:
git config --global diff.external extdiff # 设置 git 默认编辑器
这种方法的缺点是会覆盖默认的 git diff,有时想用默认 git diff 生成 patch 就不好操作了,所以这里只是作为一种补充。
在 windows 和 linux 之间切换开发时,文件权限可能会发生变更 (0644 <==> 0755), diff 时产生大量的 file mode 变更记录会严重干扰对比,通过配置忽略文件权限位变化、可以将这些修改排除在外:
git config --global core.filemode false git config core.filemode false
合并工具
CUI 环境下可以直接通过 git mergetool --tool=vimdiff 调起 vimdiff 作为合并工具,GUI 环境下需要额外配置。
git config --global merge.tool bc4 # 设置 beyondcompare 为默认合并工具 git config --global mergetool.prompt false # 启动外部工具前不再单独提示用户 git config --global mergetool.bc4.cmd '"/usr/local/bin/bcomp" "$LOCAL" "$REMOTE" "$BASE" "$MERGED"' # 使用 git diff 调用新对比工具时向 beyondcompare 传递参数的顺序 git config --global mergetool.bc4.trustexitcode true # 信任工具的返回值而不是编辑完成后单独询问用户是否已解决冲突 git config --global mergetool.keepBackup false # 当合并完成后删除临时文件 (*.orig)
其它配置
git config --list # 列出设置 git config user.name "yunhai" # 设置默认用户名 git config user.email "haihai107@126.com" # 设置默认用户邮箱 git config --global core.editor "vim" # 设置 git 默认编辑器 git config --unset <section>.<key> # 取消设置
常见问题修复
unable to update local ref
git pull 报错:
error: cannot lock ref 'refs/remotes/origin/branch_name: is at xxxxxxxxxxx but expected xxxxxxxxxxx ! xxxxxxxxxxx branch_name -> origin/branch_name (unable to update local ref)
解决方案:
git gc --prune=now
再 git pull 就好了。
no matching host key type found
git 操作报错:
Unable to negotiate with 10.11.81.103 port 8235: no matching host key type found. Their offer: ssh-rsa fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.
解决方案:
> cat ~/.ssh/config # icode Host * PubkeyAcceptedKeyTypes +ssh-rsa HostKeyAlgorithms +ssh-rsa ...
error: The following untracked working tree files would be overwritten by checkout
因磁盘空间不足导致的 git checkout 切换分支失败后,会在本地遗留很多新分支的文件,清理空间再次切换时会报上面的错误
解决方案:
> git checkout new-branch -f
前提是确认所覆盖的文件都是新分支的。
关于 Learn Git Branching
reset # 重新开始 revert / undo # 撤销上次操作 hide goal # 隐藏目标 show goal # 显示目标 hint # 提示 help # 关卡帮助 levels # 显示全部关卡 show solution # 查看标准答案 git fakeTeamwork [branch] [count] # 模拟其它人在远程仓库上的提交 sandbox # 进入沙盒 显示 demo:https://learngitbranching.js.org/?demo 直接进入沙盒:https://learngitbranching.js.org/?NODEMO
参考
[1]. Git 、CVS、SVN比较
[2]. Git 教程
[3]. 版本控制工具(CVS、SVN、GIT)简介
[4]. 版本控制工具的比较
[5]. git问题记录--如何从从detached HEAD状态解救出来
[6]. Git 的 revert 命令撤销多次提交
[7]. git撤销merge,彻底学会git revert的用法
[8]. Git 如何优雅地回退代码,用 reset 还是 revert?
[9]. 【Git】rebase 用法小结
[10]. git生成patch和打patch
[11]. Git 多用户配置
[12]. git常用配置
[13]. 配置Beyond Compare 4作为git mergetool来解决git merge命令导致的文件冲突
[14]. git设置对比工具
[15]. git diff 工具设置_Git外部的合并与比较工具
[16]. git 使用vimdiff 格式显示比对文件和常用操作整理[整]
[17]. mac上有什么查看git diff的工具?
[18]. 使用 P4Merge 作为 GIT 的可视化合并工具
[19]. git远程库代码版本回滚方法
[20]. Git过滤上传文件的方法
[21]. git 在提交之前撤销add操作
[22]. git status总是提示Changed but not updated
[23]. git - 简明指南
[24]. git的submodule功能详解
[25]. git cherry-pick合并某个commit
[26]. Git 应用补丁报错 “sha1 information is lacking or useless”
[27]. git clone 出错
[28]. git创建远程分支
[29]. git操作之pull拉取远程指定分支以及push推送到远程指定分支
[30]. git checkout 命令详解
[31]. 如何把GIT仓库的子目录独立成新仓库
[32]. git clone一个github上的仓库,太慢,经常连接失败,但是github官网流畅访问,为什么?
[33]. Git下的冲突解决
[34]. GitHub 在使用命令行 git push 时报错:The requested URL returned error: 403
[35]. Github仓库master分支到main分支迁移指南
[36]. git拉取远程分支并切换到该分支
[37]. 解决git push代码到github上一直提示输入用户名及密码的问题
[38]. 断点续传式git clone(伪)
[39]. git怎么查看哪些文件是在版本控制下的呢
[40]. git修改之前commit的提交信息
[41]. 'git status'不显示untracked files
[42]. Git如何查看远程仓库地址
[43]. git的分支与合并的两种方法
[44]. Git:合并分支----git merge命令应用的三种情景
[45]. git blame命令详解
[46]. git 撤销合并 重新合并_Git合并和Git调整的终极指南
[47]. git-stash用法小结
[48]. git如何取消merge
[49]. 使用vimdiff作为git mergetool
[50]. How to View or Change Git Username and Email Address
[51]. git commit之后,想撤销commit
[52]. git 寻找代码改动的“始作俑者”
[53]. git cherry-pick 教程
[54]. git检出某文件的指定版本
[55]. Git如何检出指定目录或文件
[56]. git 切换远程仓库地址
[57]. 如何在Git中查看单个提交的更改文件列表?
[58]. git blame
[59]. Git error on git pull (unable to update local ref)
[60]. git rm操作后文件恢复
[61]. SSH登录时"no matching host key type found. Their offer: ssh-rsa,ssh-dss"的错误解决方法
[61]. Git 彻底删除服务器上的提交记录
[62]. Git创建本地仓库并推送至Github上(详细步骤)
[63]. Git异常 #Unable to negotiate with xx.xx.xx.xx port 29418: no matching key exchange method found.
[64]. Easy way to exclude files during git add
[65]. git 查看非当前分支提交log
[66]. Git Submodule管理项目子模块
[67]. git仓库子模块下载失败怎么办
本文来自博客园,作者:goodcitizen,转载请注明原文链接:https://www.cnblogs.com/goodcitizen/p/things_about_git.html