Git常用命令
学习网址
前言
此处主要是记录一些有用但之前自己所用较少的命令,并不包括一些最基础的命令,入门的话可以去看菜鸟教程的Git教程。
记录仓库变动
git rm
要从 Git 中移除一个文件,必须先将其从跟踪文件中移除(更准确地说,是从暂存区域中移除),然后再提交。git rm
命令就能做到这一点,同时还能将文件从工作目录中移除,这样下次提交时就不会看到它是未跟踪文件了。
1 | git rm PROJECTS.md |
Tips:可认为是rm file + git add file的结合体。
1 | git rm --cached README |
git rm --cached
命令的功能是将文件从 Git 的暂存区中移除,这样这些文件就不会被包含在下一次的提交中。然而,这些文件仍然会保留在你的工作目录中,这样你就可以继续对它们进行修改,而不会丢失文件的内容。
1 | git rm log/\*.log |
注意 * 前面的反斜线 (),这是通配符的表达方式。这条命令会删除 log/ 目录下所有扩展名为 .log 的文件。
git mv
主要用于在仓库中重命名一个已跟踪的文件。
1 | git mv file_from file_to |
等价于下面的代码
1 | mv file_from file_to |
查看提交历史
查看每次提交的简短统计信息,可以加上--stat
选项;(常用)
1 | git log --stat |
忽略所有merge的log,使用--no-merges
选项;(常用)
查看特定路径或者文件的提交log;(常用)
1 | git log file_name/location_name |
还可以使用--pretty=format
定制化内容的输出格式;
或者使用--pretty
选项搜寻满足特定条件的提交,可以包括对日期,作者,修改文件的约束;
使用--graph
选项在日志旁以图形化方式显示分支和合并历史;
git reflog
git reflog
是 Git 的一个命令,用于查看本地仓库中 HEAD 和分支的移动历史记录。它记录了本地仓库中的引用(reference)的变动情况,包括分支切换、提交、重置等操作,但不包括远程引用的变动。
该命令主要的使用场景有:当你意外地删除分支、回退到错误的提交、或者执行了其他误操作时(如git reset –hard),可以使用 git reflog 找回之前的引用状态,然后进行恢复操作。通过查看 reflog,你可以找到误操作之前的引用状态,并恢复到正确的状态。
撤销动作
git commit –amend
git commit --amend
允许你修改最新的提交,举例说明:假设你已经提交了一个修改,但后来发现有些内容遗漏了或者需要进行修正。且你不想创建一个新的提交来修正这些问题,因为这会使你的提交历史变得混乱。这时候,你可以使用git commit --amend
命令。
1 | git commit -m 'Initial commit' |
这会将Initial commit
提交的内容与forgotten_file的commit合并,但提交历史上只保留一条记录。综上,git commit --amend
命令允许你修改最新的提交,同时保持提交历史的整洁性。
有了该命令,我们就可以及时将工作区的修改内容进行commit,防止内容的丢失,后面都使用--amend
选项保持提交历史的整洁,最后万事俱备再push上库。
git reset HEAD <file>
取消暂存区文件的提交,或者使用下面的命令:
1 | git restore --staged <file> |
git checkout <file>
撤销工作区对文件的修改;
也可以使用下面的命令:
1 | git restore <file> |
如此看来git restore
命令更加统一好用,同时也会出现在git的提示消息中;
撤销commit(慎用)
1.撤销并保留修改:
如果你想保留修改但是撤销最新的提交,可以使用以下命令:
1 | git reset --soft HEAD~1 |
这个命令会将 HEAD 移动到上一个提交,并将你的修改保留在工作目录和暂存区中,以便你可以继续修改并重新提交。
2.撤销并丢弃修改:
如果你想完全撤销最新的提交,并且不保留任何修改,可以使用以下命令:
1 | git reset --hard HEAD~1 |
这个命令会将 HEAD 移动到上一个提交,并且会丢弃你的修改,恢复到上一个提交的状态。
也可以用具体的提交哈希值来代替 HEAD~1,比如 git reset –soft
需要注意的是,如果你的提交已经被推送到了远程仓库,并且其他人已经基于该提交进行了工作,撤销提交可能会导致一些问题。在这种情况下,最好与团队成员讨论,以确保撤销提交不会对项目产生负面影响。(可以使用后面所说的git revert
命令来解决)
也就是说你可以修改没有push的commit,已经push的commit回退版本时要慎重,最好通过提交新的更改来修复问题,而不是直接撤销提交。这样可以保持提交历史的完整性,同时避免影响其他人的工作。
远端仓库相关
git fetch
git fetch 命令用于从远程仓库下载最新的提交和数据到你的本地仓库,但它不会合并这些改变到你的当前工作分支。其作用包括:
更新远程跟踪分支: 执行 git fetch 后,Git 会下载远程仓库中的最新提交和数据,并将它们保存在本地仓库中。这些数据包括远程分支(如 origin/master)的引用,它们跟踪了远程仓库的状态。
获取最新提交: git fetch 会将远程仓库中的最新提交下载到本地,但不会修改你的工作目录或当前工作分支。这使得你可以查看远程仓库的最新状态,然后决定是否需要合并或拉取这些提交到你的工作分支。
设想如下场景,你的同事新建了一分支,名为”branch_a”,并push到远端仓库。这时候你在本地,想直接拉取该分支,使用git pull origin branch_a
命令时会报错,因为你没有将远端仓库的提交和数据下载到你的本地仓库,你的本地仓库中并没有“branch_a”的信息。要么你现在当前分支运行git pull
命令获取远端仓库的最新提交和数据;要么先运行git fetch
的命令,获取“branch_a”的分支信息,再进行后续的分支切换与拉取。
git remote rename
给远端仓库重命名:
1 | git remote rename pb paul |
取消远端仓库重命名:
1 | git remote remove paul |
标签相关
查看标签
列出仓库中所有的tag,还可以搜索特定pattern的标签:
1 | git tag -l "v1.8.5*" |
创建标签
Git 支持两种类型的标签:轻量级标签和注释标签。轻量级标签可以理解为给commit的hash值重命名,没有任何额外信息,而注释标签可以保存创建标签的作者,注释和日期等信息;
下面是创建注释标签(Annotated Tags)的示例,在创建tag时需要增加-a
的选项:
1 | git tag -a v1.4 -m "my version 1.4" |
1 | git show v1.4 |
轻量级标签只需要在git tag
后添加标签名即可:
1 | git tag v1.4-lw |
1 | git show v1.4-lw |
如果我们要给之前的某个commit打标签的话,只需要在git tag
后加入commit的hash值即可,如:
1 | git tag -a v1.2 -m "my version 1.2" 9fceb02 |
推送标签
默认情况下,git push
不会将tag信息push上库,除非显式地指定,与分支上库相同,将tag同步到远端仓库的命令为:git push origin <tag_name>
1 | git push origin v1.5 |
如果有许多tag要推送上库的话,可以使用--tags
选项
1 | git push origin --tags |
删除标签
删除标签有如下命令,本地删除tag:
1 | git tag -d v1.4-lw |
删除远端仓库的tag:git push origin --delete <tagname>
切换标签
如果你想切换标签,可以使用git checkout <tag_name>
即可。需要注意的是,在 “分离 HEAD “状态下,如果你做了修改,然后又创建了一个提交,tag将保持不变,但你的新提交将不属于任何分支,而且除了通过准确的提交哈希值访问外,将无法访问。因此,如果你需要进行修改,比如修复旧版本上的一个 bug,一般会先基于tag创建一个分支。
1 | git checkout -b version2 v2.0.0 |
上述命令是基于tag v2.0.0创建分支version2并切换到version2分支,这样的话我们可以基于v2.0.0继续开发。
分支相关
将本地分支与远端分支相关联,下面的示例代码是将当前的工作分支与远端的serverfix相关联;
1 | git checkout --track origin/serverfix |
新建分支并与远端分支相关联:
1 | git checkout -b <branch> <remote>/<branch> |
Tips:有上述命令的快捷方式,较为常用。如果你要检出的分支名称(a)不存在,(b)只与一个远程上的名称完全匹配,同时满足a和b条件的话,Git会自动帮你创建一个跟踪分支。
查看已设置的跟踪分支,并列出本地分支的各种信息,包括跟踪的远端分支名,是ahead or behind;
1 | git branch -vv |
删除远端分支:
1 | git push origin --delete serverfix |
git rebase
git rebase
功能与git merge
类似,区别在于log比较干净。适用于在推送之前进行rebase,确保log干净后再push上库,而不要对已推送的库上的内容进行rebase。
1 | git rebase <base_branch> <topic_branch> |
一. 进行分支合并git rebase
主要用于将一个分支的提交移动到另一个分支上,常用于将一个分支的提交合并到另一个分支上。相较于git merge
的优点在于commit log里没有”merge xxx into xxx”的日志,看着比较舒服。下面示例将演示如何使用 git rebase
将一个分支的提交合并到另一个分支上。
假设我们有两个分支:feature
和 master
。我们想要将 feature
分支上的提交合并到 master
分支上。
- 首先,我们需要切换到
feature
分支:
1 | git checkout feature |
- 然后,我们运行
git rebase
命令来将master
分支上的提交移动到feature
分支上,可以执行以下命令:
1 | git rebase -i master |
在执行上述命令后,Git 会将
master
分支上的提交逐个应用到feature
分支上。如果在此过程中出现冲突,需要解决冲突并继续 rebase 过程。可以使用git status
命令查看冲突的文件,并手动解决冲突。解决完冲突后,使用以下命令继续 rebase 过程:
1
2git add <conflicted_file>
git rebase --continue重复步骤 3 和步骤 4,合并后进行merge完成feature的合并。
1
2git checkout master
git merge feature
通过以上步骤,我们使用 git rebase
将 feature
分支上的提交合并到了 master
分支上。这种方式可以使得提交历史保持线性,并且可以减少不必要的合并提交。使用rebase命令时一定切记,我们是否会修改其他同事也能看到的已经存在的commit内容,如果是,则不要使用rebase,尽量使用merge。
如果远端仓库的分支名为master,在我们想push修改时,其他同事也在master上有修改,我们可以使用git pull --rebase
,commit log是线性的,在rebase后再进行git push
操作。需要注意的是使用git pull --rebase
时,仓库内不能有modified的文件,我们可以在pull之前使用git stash
命令。
参考资料:
git pull –rebase的正确使用
merging vs. rebasing
二. 进行多次commit的合并
设想我们在新分支上进行了对同一文件进行了多次commit,有较多的历史信息,为了log的整洁性,我们希望把这多次commit进行整合,合并为一次commit,并修改提交信息。比如我们希望将最近的三次commit修改为一次commit,依次进行下面操作。
要使用 Git rebase 将最近的三个 commit 合并为一个 commit 并修改 commit 信息,你可以按照以下步骤进行操作:
- 执行
git rebase -i HEAD~3
命令来启动交互式 rebase。这将打开一个文本编辑器,列出了最近的三个 commit。 - 在编辑器中,你会看到一个包含了最近三个 commit 的列表,每个 commit 都有一个前缀为 “pick” 的行。将除了第一个 commit 之外的所有 “pick” 行的前缀改为 “squash” 或 “s”(表示合并),这样 Git 将会将它们合并到第一个 commit 中。
- 保存并关闭编辑器。Git 将会继续 rebase 操作,并在需要的时候打开另一个编辑器,以便你编辑合并后的 commit 信息。
- 在新的编辑器中,修改合并后的 commit 信息,以反映你所做的更改。保存并关闭编辑器。
- 完成 rebase 操作后,你可能需要解决任何可能出现的合并冲突。Git 会提示你在 rebase 过程中遇到的任何冲突,并提供解决冲突的指导。
- 最后,使用
git log
确认你的 commit 已经合并并修改成功。
以下是一个简单的示例:
1 | git rebase -i HEAD~3 |
编辑器中的内容:(其中git会提供较多的选项,我们按需选择就行)
1 | pick 1234567 Commit message 1 |
编辑器中的内容:
1 | # This is a combination of 3 commits. |
保存并关闭编辑器,然后解决可能出现的冲突,最后确认合并结果。
工作流程
空白行track
在git commit之前,运行下面命令检查是否track了空白行:
1 | git diff --check |
将开发分支合并到主分支上时,可以使用git merge --squash
命令,它是 Git 中用于合并分支并压缩提交历史的命令。它的作用是将一个分支上的所有提交压缩成一个提交,并将这个提交合并到当前分支上,适用于需要保持提交历史清晰、整洁的情况。
三点语法
查看分支上(contrib)相对于主分支(master)的所有改动情况,可以使用git diff master...contrib
来查看,该命令只显示当前主题分支与主分支的共同节点之后引入的工作。
离线归档
准备release版本时,可以使用git archive
命令创建一个zip的归档文件,供那些不使用git的人查看或进行代码备份。
使用git log --no-merges master --not v1.0
查看master分支自tag v1.0后的所有改动,不包括merge的变动,可以整理查看所有的改动情况。
两点语法
设想如下场景,你的主分支名字叫master,为新开发特性,新建分支featureA,随后master和featureA分支各自并行进行。最后featureA开发完毕,准备合并进入master时,你想看一下哪些commit是仅在featureA上而不在master上(因为我们是基于master新建的分支featureA,所以它也继承了之前master分支上的log),你可以使用下面命令:git log master..featureA
。
同理,想将本地push到远程,并查看有什么新的commit时,可以使用如下命令:git log origin/master..HEAD
。其中下面两种写法与两点的语法同理:
1 | git log ^master featureA |
通过上面的语法,我们可以更进一步:
1 | git log refA refB ^refC |
如果我们在一个文件中一次修改多个bug,但想分段进行commit,也就是分不同的修改部分进行commit,这时我们可以使用git add -p
选项进行修改内容的选择跟踪。
git stash
使用场景:当你正在进行一些修改,但需要切换到其他分支或者处理其他任务时,可以使用 git stash 临时保存当前工作目录的修改。这样可以避免将未完成的工作提交到版本库,保持工作目录的干净和整洁。使用git stash
或者git stash push
命令。使用git stash list
查看stash列表,使用git stash apply
将存储的状态取出来(取出来但还不会在list中删除,如果想取出随后就删除,请使用git stash pop
命令),默认取出的stash是最新压栈的。
当你只想保存工作目录中的部分修改,而不是全部修改时,可以先将需要提交的修改添加到暂存区中,然后运行git stash --keep-index
命令保存工作目录的修改。这样可以确保保存的修改不包含已经暂存的部分。git stash -u
是 Git 中 git stash 命令的一个选项,它用于将当前工作目录的修改临时保存到存储中,并且包括未跟踪的文件。
最后,如果我们增加“–patch”选项,git会交互式地与你确认哪些修改需要进行stash,而哪些不需要。
git clean
如果我们想直接删除工作目录中untrack的文件,而不是把它压栈,可以使用git clean
命令,为了安全起见,最好加上“-i”选项进行交互式删除。加上”-d”选项会自动删除untrack的空文件夹。
git搜寻
使用下面命令查看特定字符串的提交或修改记录,其中ZLIB_BUF_MAX
为我们想搜寻的字符,--oneline
是输出的选项,以简易形式输出。
1 | git log -S ZLIB_BUF_MAX --oneline |
使用git blame file
查看file中每一行的最近改动,可以查看是谁引入了相关问题(所以是blame选项,找背锅的,哈哈哈)。git blame -L 11,22 file
仅限查看file中11到22行的最近改动。
查看特定文件中某一函数的改动情况,可以使用如下的命令:
1 | git log -L :git_deflate_bound:zlib.c |
如果我们想查看 zlib.c 文件中函数 git_deflate_bound 的每一次修改,可以运行上述命令,这将尝试找出该函数的边界,然后查看历史记录,以一系列补丁的形式向我们展示函数的每一次修改,直至函数首次创建。或者就在-L后面给出行数范围也可以。可以使用git log --help
查看-L的使用方法。
合并冲突
在我们合并分支或者git stash pop
时,很有可能出现冲突的情况,下面主要给出merge失败的解决方案,即如何解冲突。
首先,如果我们目前没有时间去清除conflict,我们可以先使用git merge --abort
命令,这样会回退到git merge
或者git stash pop
的状态之前,也就相当于撤销一次merge的操作。(如果没敲合并的命令不就没有合并冲突了,hhhhh)
如果我们确实想解冲突,但只是想拿本地分支或者待合入分支的文件版本,我们可以简单操作如下:git checkout --ours file
,这是取我们当前分支的文件状态作为merge后的结果,git checkout --theirs file
这是取待合入分支的文件作为merge的结果。
最后是常用的情况,我们确实想看文件中共同的改动在何处,并想仔细解冲突,可以先使用diff3
来查看版本修改情况,这里“3”的含义是我们在解冲突时所需要的三份文件版本:1. 该文件在两分支上的共同祖先版本(BASE);2. 本地分支的版本(LOCAL);3. 待合入分支的版本(REMOTE);
我们使用git checkout --conflict=diff3 file
,此后打开file,会发现文件中的冲突之处有如下的特征:
1 | <<<<<<< ours |
其中三部分内容以不同的分隔符进行分隔,其中ours是本地文件版本,base是共同祖先版本,theirs是待合入分支的文件版本,我们对其进行选择,随后将该段提示删除保存,即可完成解冲突操作。(解完冲突后进行git add
+git commit
操作);还可以使用git diff
查看尚未解完的冲突,适用于冲突较多的情况。
也许发生冲突的修改已经距离当前很远了,我们可以使用git log --oneline --left-right --merge
来查看与冲突相关的提交,通过当时的commit message信息来决定如何解冲突。
撤销commit
如果我们错误提交了一次commit,心里一定非常慌张。别急,git给你准备了补救手段。如果该commit还没有push上库,那么比较简单,直接git reset --hard HEAD^
即可,回退到commit之前的版本。但如果上库了就没那么简单了,因为可能有其他同事基于你错误的版本已经在向前移动HEAD指针了,如果你强行reset可能会造成更严重的错误。其实想一下,每次查看git commit的log时,都会说此次commit修改了哪些哪些文件,那如果有一个命令让你逆向还原这些文件,不就一样实现了撤销commit的操作吗?这就是git revert
的功能。该命令用起来很简单,git revert commit-id
即可完成撤销,我们再进行git add
和git commit
即可。
需要注意的是,如果撤销的commit是merge分支的操作,那么后续如果再继续merge该分支时,需要一些额外操作,详细操作流程请查看Pro_Git的7.8节。
如果要处理大量重复的冲突,可以查阅git rerere
工具。
Debug何时引入错误
git bisect
是 Git 提供的一个用于二分查找的工具,用于定位代码中引入 bug 的具体提交。它的主要作用是帮助开发者在一个较大的提交历史中快速定位引入 bug 的具体提交,从而更容易地进行问题追踪和修复。(bisect:binary search commit)
使用场景及示例说明如下:
场景: 假设你的项目中出现了一个 bug,而你无法确定 bug 是在哪个具体的提交引入的,但你知道在项目的某个历史版本中是没有这个 bug 的。
步骤:
- 开始 bisect: 首先,你需要告诉 Git bug 的状态,即哪个提交是有 bug 的,哪个是没有 bug 的。你可以使用
git bisect start
命令开始 bisect 过程。我们可以使用1
git bisect start
git bisect start HEAD commit-id
,其中commit-id为已知的正确的版本,HEAD为已发生错误的版本,或者使用两个commit-id表明范围也行。 - 标记 bad 和 good: 接着,你需要标记一个已知是有 bug 的提交为 “bad”,另一个已知是没有 bug 的提交为 “good”。这可以通过
git bisect bad
和git bisect good
命令来完成。
1 | git bisect bad # 当前版本有 bug |
开始二分查找: Git 现在将使用二分查找算法在两个标记的提交之间进行搜索,每次检出一个中间的提交,然后你需要测试该版本是否有 bug。如果有 bug,使用
git bisect bad
命令标记为 bad,否则标记为 good。重复步骤: 重复步骤 3,直到 Git 找到第一个引入 bug 的提交。此时,Git 将输出这个提交的信息,你就可以定位到引入 bug 的具体提交。
结束 bisect: 当找到引入 bug 的提交后,使用
git bisect reset
命令结束 bisect 过程。
1 | git bisect reset |
通过以上步骤,你可以利用 git bisect
工具快速定位代码中引入 bug 的具体提交,从而帮助进行问题追踪和修复。这在大型项目中特别有用,因为通常情况下可能存在大量的提交历史。
submodules
添加submodules
在main project里添加submodule:git submodule add https://xxx
;
之后会在main project中发现.gitmodules文件,里面记录了submodule的url,在main project中的路径以及其关联的分支名;这也是其它成员能知道submodule来源的配置文件;
clone含submodules的project
1 | 1. 第一种方法 |
更新含有submodules的project
1 | 法一: |
push submodule
- 在main project里
git add submodule
,此时会将当前的提交点挂载在main project上; - git push
merge submodule
如果submodule发生冲突,解决流程:
git pull
获取最新submodule状态,并发现发生冲突;git diff
查看local与remote的commit id差异;- 基于远端commit id新建分支,
git branch try_merge remote-commit-id
; git merge try_merge
;- 然后就是正常的解冲突;
git add, git commit, git push
;
submodule tips
可以通过foreach
的子模块命令对所有的子模块进行操作,这在项目中拥有较多submodules时比较有用。
1 | git submodule foreach 'git stash' |
使用alias将长命令缩短
1 | git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'" |
如果在main project中,有的分支上有submodule,而有的分支上没有submodule,那么在切换main project的分支时,最好使用如下方式:git checkout --recursive-submodules branch_name
。
bundle
git bundle
是 Git 提供的一个功能,它可以将 Git 仓库的部分或全部历史打包成一个单独的文件。这个文件可以在不连接到网络的情况下传递给其他人,他们可以通过将其导入到自己的本地仓库来恢复提交历史。git bundle
的主要作用是在没有网络连接或网络速度较慢的情况下,仍然能够方便地共享和传输 Git 仓库的历史记录。
使用场景包括但不限于:
离线协作: 当你需要与其他人协作,但又无法连接到网络时,可以使用
git bundle
将你的本地仓库的提交历史打包成一个文件,并通过其他方式传递给他们,如 USB 磁盘或其他传输媒介。备份和归档: 将 Git 仓库的历史记录打包成一个 bundle 文件可以作为备份和归档的方式。你可以将 bundle 文件存档到云存储或外部硬盘中,以防止数据丢失。
快速克隆: 当你需要在其他计算机上快速克隆 Git 仓库时,可以使用 bundle 文件代替从远程仓库克隆,特别是在网络速度较慢的情况下。
下面是一个示例,演示了如何使用 git bundle
创建和使用 bundle 文件:
1 | # 创建 bundle 文件 |
在上面的示例中,首先我们使用 git bundle create
命令创建了一个名为 repo.bundle
的 bundle 文件,其中包含了 master
分支的提交历史。然后,我们可以将这个 bundle 文件传递给其他人。其他人可以通过 git clone 命令从 bundle 文件中恢复提交历史到本地仓库,或者通过 git pull 命令更新现有仓库的提交历史。可以进行checkout到特定commit id的操作;
Git-Tools-Bundling