目录
4、提交—git add / git commit / git push
7、撤销恢复—git checkout / git reset / git revert
(1)、撤销未 add 时的代码用 git checkout
(2)、撤销已经 commit 但未 push 的代码建议用 git reset
(3)、撤销已经 push 的代码建议用 git revert
8、git 合并及删除历史提交——git rebas(必须慎用)
前言
学习 git 前,建议先了解下 vim。
一、git 有三个分区
- 工作区:就是你在电脑里能看到的目录(代码编辑区)。
- 暂存区:一般存放在 ".git目录下" 下的index文件中(过渡层,避免误操作)。
- 版本库:工作区有一个隐藏目录.git (本地仓库,专门控制版本)。
另外还有一个远程仓库:比如GitHub上的库。
他们之间的关系如下:
二、git 基本操作
1、克隆—git clone
从远程仓库克隆代码到版本库(本地仓库)。
- --recursive:是递归的意思,不仅会git clone当前项目中的代码,也会clone项目中子项目的代码。
2、拉取—git fetch / git pull
- git fetch:从远程仓库中拉取最新版本代码到本地,不会自动 merge。
- git pull:从远程仓库中拉取最新版本代码到本地码,并自动 merge。相当于:git fetch 和 git merge 两步操作。
git 拉取远程分支到本地:
git pull origin [远程分支名称]:[本地分支名称]
这等价于:
git checkout -b [本地分支名称]
git branch --set-upstream-to=origin/[远程分支名称] [本地分支名称]
3、查看—git status / git diff
- git status:查看提交的状态,查看是否有修改。
- git diff:尚未缓存的改动——具体修改的内容。
拓展
git status -s 或 git status --short:
状态简览。
git diff: 工作区和暂存区比较。
git diff --cached: 工作目录与上次提交时之间的所有差别,这些内容在不带"-a"参数运行 "git commit"命令时就会被提交。
git diff HEAD: 工作目录与上次提交时之间的所有差别,这条命令所显示的内容都会在执行"git commit -a"命令时被提交。
git diff --word-diff: 使用逐词比较,默认是逐行比较。
git diff <分支名>:查看当前的工作目录与另外一个分支的差别。
git diff <分支1>..<分支2>:比较项目中任意两个分支的差异。如果你想找出 <分支1> 和 <分支2> 的共有父分支和 <分支2> 之间的差异,你用3个‘.'来取代前面的两个'.' 。对比后的结果解读为:“<分支2>比<分支1>增加或删除了xxx。”
git diff HEAD -- ./lib:你当前工作目录下的lib目录与上次提交之间的差别(或者更准确的说是在当前分支)。
4、提交—git add / git commit / git push
- git add:将工作区文件添加到缓存区。
- git commit:将缓存区文件添加到版本区,或者将工作区文件直接添加到版本区。
- git push:将版本库文件推送到远程仓库。
提交指定文件:
- git add <文件1 文件2 ...>
- git commit <文件1 文件2 ...> -m "注释"
- git push 或 git push origin <分支> 或 git push origin HEAD:refs/heads/<分支>
【拓展】
- git commit 的拓展
- git commit --amend:覆盖提交。既可以对上次提交的内容进行修改,也可以修改提交说明。
git commit --amend 修补提交命令,实际上相当于执行了以下两条命令(注:.git/COMMIT_EDITMSG 是保存上次提交的日志):
git reset ---soft HEAD^
git commit -e -F .git/COMMIT_EDITMSG
- git push 的拓展
- 强制推送至远程仓库:git push -f 或 git push --force
- git push origin HEAD:refs/for/<分支>:提交的代码 需要 经过 code review 之后才能 merge 进指定的远程仓库
- git push origin HEAD:refs/heads/<分支>:提交的代码可以直接 merge 进指定的远程仓库
5、日志—git log / git reflog
- git log:查看所有提交过的版本信息。(按 q 或 ctrl + z 均可退出)
- git log --pretty=oneline:查看 完整 hash 版本号 和 commit 信息。
- git log --graph --pretty=oneline --abbrev-commit:查看 简短 hash 版本号 和 commit 信息。
- git reflog:查看所有分支的所有操作记录(包括已经被删除的 commit 记录和 reset 的操作),内容由 hash 版本号、HEAD 记号 和 commit 信息 组成。(按 q 或 ctrl + z 均可退出)
6、删除—git rm
- 简单地从工作区手工删除文件:
git rm 文件名
- 删除之前修改过并且已经放到缓存区的文件:
git rm -f 文件名
- 删除缓存区,保留工作区的文件:
git rm --cached 文件名
7、撤销恢复—git checkout / git reset / git revert
请谨慎使用这些方法,因为它们都会丢弃工作目录中的未提交更改:
- git revert 用于记录一些 新的提交以反转一些早期提交 的影响(通常只是错误的提交)。
- 如果你想 扔掉工作目录中所有未提交的更改,你应该看 git reset xxx,特别是 --hard 选项。
- 如果你想 提取特定文件,就像在另一个提交中那样,你应该看 git checkout xxx,特别是 git checkout <commit> --<filename> 语法。
(1)、撤销未 add 时的代码用 git checkout
git checkout 既可以用来切换分支,又可以用来恢复工作目录。下面只对 git checkout “恢复工作目录” 的功能进行研究。
语法:
-
git checkout . :维持 HEAD 的指向不变,用暂存区的 “所有文件” 直接覆盖 “工作区文件”,相当于 取消自上次 git add 的修改。
-
git checkout branch --<filename>:维持 HEAD 的指向不变,用 “某个分支的文件” 替换暂存区和工作区中相应的文件。
-
git checkout HEAD <filename> 或 git checkout HEAD . :将 HEAD 指向的版本库文件,同时替换暂存区和工作区文件。
案例:
放弃本地修改,用暂存区的 “所有文件” 直接覆盖 “工作区文件”(本地文件):
// 放弃本地修改,用暂存区的 “所有文件” 直接覆盖 “工作区文件”(本地文件)
git checkout .
git pull origin <分支>
【注意】
此命令 不会删除掉刚新建的文件(尚未 git add)。因为刚新建的文件还没有加入到 git 的管理系统中,对于 git 来讲是未知的,此时需要自己手动删除该新建的文件就好了。
【拓展】
(2)、撤销已经 commit 但未 push 的代码建议用 git reset
语法:
- git reset HEAD 文件名:删除指定文件的缓存。
- git reset HEAD .:放弃所有的缓存(commit)。
-
git reset --hard <版本号>:放弃本地修改,强制回退到某个版本。
-
git reset --hard origin/<分支>:放弃本地修改,强制保持与远程分支一致。
-
git reset --hard HEAD^ 或 git reset --hard HEAD~1:撤销上 1 次的commit,不保留本地修改。
-
git reset --hard HEAD~2:撤销之前 2 次的commit,不保留本地修改。
-
-
git reset --soft <版本号>:保留本地修改,强制回退到某个版本。
-
git reset --soft origin/<分支>:保留本地修改,强制保持与远程分支一致。
-
git reset --soft HEAD^ 或 git reset --soft HEAD~1:撤销上 1 次的commit,保留本地修改。
-
git reset --soft HEAD~2:撤销之前 2 次的commit,保留本地修改。
-
选项:
-
--mixed:这是默认操作。重置索引而不是工作树(即,保存更改的文件但未标记为提交)并报告尚未更新的内容。
-
--soft:根本不触摸索引文件或工作树(但将头重置为 <commit> ,就像所有模式一样)。这git status将使所有更改的文件“变更被提交”,就像它会这样。
-
--hard:重置索引和工作树。放弃自 <commit> 以来对工作树中跟踪文件的任何更改。
-
--merge:重置索引并更新工作树中 <commit> 和 HEAD 之间不同的文件,但保留索引和工作树之间不同的文件(即没有添加更改的文件)。如果 <commit> 和索引之间的文件有不同的变化,则重置会中止。换句话说,--merge 可以做一些类似的事情git read-tree -u -m <commit>,但是会传递未合并的索引条目。
-
--keep:重置索引条目并更新工作树中 <commit> 和 HEAD 之间不同的文件。如果 <commit> 和 HEAD 之间的文件有本地更改,则重置会中止。
案例:
放弃本地修改,强制拉取更新;或者放弃从远程分支拉取的文件,强制拉取另一个分支的文件:
// 放弃本地修改,强制拉取更新;或者放弃从远程分支拉取的文件,强制拉取另一个分支的文件
git fetch --all
git reset --hard origin/<分支>
git pull origin <分支>
【注意】
若前提是已经 push 到远程仓库了。在使用 reset 进行版本回退后,直接 push 是失败的,因为本地版本号于远程仓库的版本号不一致(本地版本号落后于远程仓库的版本后),这个时候不要去Pull操作,否则代码又还原了,得重新 reset。那怎么办呢?只需在 push 时加上命令:`--force
`(⚠️ 这很可能会删除别人提交的代码,所以最好确保别人没有提交,或者与别人沟通好此操作的必要性以及修复措施,总之这是个危险的操作,慎用),强推,强制覆盖,即可。
git push --force origin develop
【拓展】
详解git reset --hard 和 git reset --soft区别
(3)、撤销已经 push 的代码建议用 git revert
语法:
git revert -n <版本号>
// 或
git revert -n HEAD~<n-1>
// n:代表该提交记录的次数
// n - 1:恢复 HEAD 中某个版本提交的代码时,要指定 HEAD 到 该版本的前一个版本
-
git revert -n <版本号>:回退到指定的历史版本提交的更改,不自动提交(删掉 该版本号 及其之前 的所有已 push 的更改,等待提交)。这样就把该版本的记录删除了。
-
git revert -n HEAD~0:回退到 HEAD 中最后一次提交的更改,不自动提交(删掉 上次已 push 的更改,等待提交)。
-
git revert -n HEAD~2:回退到 HEAD 中倒数第3次提交的更改,不自动提交(删掉 这三次已 push 的所有更改,等待提交)。
选项:
-
-n(--no-commit ):仅仅恢复工作树和索引,修改后不自动提交,最后一起手动提交。 revert 命令默认会对每一步的 commit 进行一次提交,-n 后可以 最后一起手动提交。
案例:
1⃣️、恢复 HEAD 中最后 1 次提交指定的更改,并使用恢复的更改创建一个新的提交。
git revert HEAD~0
git commit -a -m 'revert 到上一个版本'
2⃣️、我们提交了 6 个版本,其中 HEAD 2-3 包含了错误的代码需要被回滚掉。 同时希望不影响到后续的 HEAD 0-1。并使用恢复的更改创建一个新的提交。
// 查看历史
git reflog
* 982d4f6 (HEAD -> master) HEAD@{0}: commit: one
* 54cc9dc HEAD@{1}: commit: two
* 551c408 HEAD@{2}: commit: three error
* 7e345c9 HEAD@{3}: commit: four error
* f7742cd HEAD@{4}: commit: five
* 6c4db3f HEAD@{5}: commit: six
// revert 代码
git revert -n f7742cd..551c408
// 或
git revert -n master~4..master~2
// 提交
git commit -a -m 'This reverts commit 7e345c9 and 551c408'
【注意】
当你 revert 到一个已经 revert 过的版本之前的 版本号 时(比如:有1、2、3三个历史版本,你先revert到2,然后再revert到1),revert 依然可以找到此版本的更改(也就是1版本的更改),此时,当前版本(也就是1版本)一定会与上一次 revert 后的版本(也就是2版本)产生冲突,需要手动合并代码、解决冲突,然后提交。
revert 后的代码可能会产生冲突,若产生冲突,需要手动合并代码、解决冲突,然后提交。
【拓展】
下图是 git reset 和 git revert 的区别:
8、git 合并及删除历史提交——git rebas(必须慎用)
如果已经 push 到远程仓库了,不建议用 rebas。因为一旦 rebase 了,别人再 pull 代码时就会出现一大堆的冲突,最恶心的是基本没法修复。
(1)、git rebase 语法
git rebase 是对代码进行比较,将分之修改后的代码打到另外一个分支之后。而 git merge 则是将另外一个分支的代码打到当前分支之后。
git rebase -i HEAD~n // n 就是 HEAD@{n} 里的 n,可记为:操作的记号
【注意】上述代码中的 HEAD~n 与通过 git reflog 查到的 HEAD@{n} 有时候并不一致。
(2)、git rebas 的选项
在另一个基础提示之上重新应用提交。
git rebase [-i | --interactive] [<options>] [--exec <cmd>]
[--onto <newbase> | --keep-base] [<upstream> [<branch>]]
git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
--root [<branch>]
git rebase (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)
- --interactive(-i):可用于查看所有的基(查看最近的提交记录)。
列出即将被重新设置的提交。让用户在变基之前编辑该列表。此模式也可用于拆分提交(请参阅下面的拆分提交)。
可以通过设置配置选项 rebase.instructionFormat 更改提交列表格式。自定义指令格式将自动将长提交哈希添加到格式中。
- --exec:
在最终历史记录中创建提交的每一行之后附加“exec <cmd>”。<cmd> 将被解释为一个或多个 shell 命令。任何失败的命令都会中断 rebase,退出代码为 1。
您可以通过使用多个命令的一个实例来执行--exec 多个命令:
git rebase -i --exec "cmd1 && cmd2 && ..."
或通过提供多个--exec:
git rebase -i --exec "cmd1" --exec "cmd2" --exec ...
如果 --autosquash 使用,“exec” 行将不会被附加到中间提交,并且只会出现在每个 squash/fixup 系列的末尾。
这在 --interactive 内部使用了机器,但它可以在没有显式 --interactive。
- --onto:
创建新提交的起点。如果未指定 --onto 选项,则起点为 <upstream>。可以是任何有效的提交,而不仅仅是现有的分支名称。
作为一种特殊情况,如果只有一个合并基,您可以使用“A...B”作为 A 和 B 的合并基的快捷方式。您最多可以省略 A 和 B 中的一个,在这种情况下,它默认为 HEAD。
- --root:对所有可从 <branch> 访问的提交进行变基,而不是使用 <upstream> 来限制它们。这允许您在分支上重新设置根提交。当与 --onto 一起使用时,它将跳过 <newbase>(而不是 <upstream>)中已经包含的更改,而没有 --onto 它将对每个更改进行操作。
- --apply:使用应用策略来变基(内部调用git-am )。一旦合并后端处理了应用程序所做的所有事情,此选项将来可能会成为无操作。
- --continue:解决合并冲突后,重新启动变基过程。
- --skip:通过跳过当前补丁,重新启动变基过程。
- --abort:中止 rebase 操作并将 HEAD 重置为原始分支。如果在启动 rebase 操作时提供了 <branch>,则 HEAD 将重置为 <branch>。否则 HEAD 将被重置为 rebase 操作开始时的位置。
- --quit:中止 rebase 操作,但 HEAD 不会重置回原始分支。结果,索引和工作树也保持不变。如果使用 --autostash 创建了临时存储条目,它将被保存到存储列表中。
- --edit-todo:在交互式变基期间编辑待办事项列表。
- --show-current-patch:在交互式变基中或当变基因冲突而停止时显示当前补丁。这相当于 git show REBASE_HEAD。
- <upstream>:要比较的上游分支。可以是任何有效的提交,而不仅仅是现有的分支名称。默认为当前分支的配置上游。
- <branch>:工作部门;默认为头。
- <newbase>:新基。
(3)、git rebase 进入 vim 后的选项
修改 vim 文档时要按照下面的不同选项,来实现自己想要的效果:
- pick:以原来的提交信息(commit message)保存提交。
- reword:需要重新编辑提交信息。
- edit:会因为 amending 而终止。
- squash:会与上一个提交(commit)合并。
- fixup:会丢掉提交日志。
- exec:git会在shell中运行这个命令。
(4)、删除最后一次提交(不推荐用 rebase)
若是删除最后一次提交记录建议用 reset 恢复,因为用 rebase 会报错的,且不会删掉。
下面来验证一下:
// 用 rebase 删除最后一次提交记录
git rebase -i HEAD~1
执行上面的命令后,会进入 vim 模式:
// 第五次提交(最后一次提交)
pick 27d5738 add 5
删除上面的代码后,退出 vim 并保存,此时会提示报错:
error: nothing to do // 错误:什么也没做
所以,建议直接用 reset 操作。
于是我有了新的猜想:如果非要用 rebase,在保证满足下面的条件下是否可行?
git rebase -i HEAD~n(其中 n > 1)
// 或者直接执行(但是你的提交记录必须大于1条)
git rebase -i
下面咱就来验证一下:
// 用 rebase 删除最后一次提交记录
git rebase -i HEAD~2
然后,会进入 vim 模式:
// 第四次提交(倒数第二次提交)
pick 27d5738 add 4
// 第五次提交(最后一次提交)
pick 27d5738 add 5
然后,我将上述代码的最后一行代码(最后一次提交的记录)删掉,退出 vim 并保存。
不出意外的话,此时已经成功的删除了最后一次提交的记录。
综上,既然 rebase 删除最后一次提交风险比较大,何不用 reset 来恢复到上一次提交的代码呢!
(5)、删除历史提交
- git rebase -i HEAD~2:删除倒数第二次的提交记录——执行该命令后,在 vim 文档中找到倒数第二次的提交记录,将其删除,保存并退出 vim,即可。
- git rebase -i HEAD~4 HEAD~2:删除倒数第四次和倒数第二次的提交记录——执行该命令后,在 vim 文档中找到倒数第二次的提交记录和倒数第四次的提交记录,将它们删除,保存并退出 vim,即可。
最后,push 代码到远程仓库。需要注意的是,如果你修改的代码已经 push 到远程仓库了,那么你需要 git push -f 强行提交到远程仓库。
【注意】一般这样修改提交到远程分支后,别人 pull 代码时,难免会有冲突。
(6)、合并历史提交
假设:你 commit 了两次,想要将这两次合并为一次提交。
// 第一次
git commit -m "git rebase test"
// 第二次
git commit -m "git rebase test2"
第一步:进入 vim 编码模式
需要先执行:
git rebase -i HEAD~2
会进入 vim 编码模式。在 vim 文档的顶部找到默认的 pick 操作的代码,对其进行修改:
pick 7d7477e git rebase test
pick eb3b5fa git rebase test2
第二步:合并提交记录
若需将第二次提交合并到第一次提交上,需将第二个 pick 改为 squash:
pick 7d7477e git rebase test
squash eb3b5fa git rebase test2
第三步:是否修改提交信息
然后,询问你是否要修改之前的提交信息(commit message):
git rebase test
git rebase test2
是否要修改提交信息是可选的:
- 选择不改,直接退出 vim 模式,然后执行:“第四步”。
- 选择改,继续往下看。
若选择修改提交信息,首先你要找到值得被修改的提交信息:因为是要将第二次提交合并到第一次提交上,那么合并后,第一次的提交的记录一定会覆盖掉第二次提交的记录,所以第一次提交的信息是值得被修改的。
我打算将第一次的提交信息由 test 改为 test1,作为最终合并后的提交信息。于是我这样改的:
git rebase test1
git rebase test2
然后,退出 vim 模式,操作完成。
第四步:提交代码到远程仓库
最后,要看你 rebase 的提交记录是否包含已经 push 到远程仓库的代码:
- 不包含:执行 git push 提交代码到远程仓库,即可。
- 包含:需要强推覆盖掉之前的代码(操作有风险,请慎用)。执行 git push -f 提交代码到远程仓库,亦可。
【推荐阅读】
git rebase 用法小结(包括游离分支的修复)https://www.jianshu.com/p/4a8f4af4e803
三、git 分支管理
1、查看分支—git branch
git branch
2、创建分支—git branch 分支名
git branch 分支名
3、切换分支—git checkout 分支名
// 切换分支
git checkout 分支名
// 创建并切换分支
git checkout -b 分支名
【拓展】git checkout:既可以用来切换分支,也可以用来回复工作目录。
4、删除分支—git branch -D 分支名
git branch -D 分支名
5、合并分支—git merge 分支名
git merge 分支名
【拓展】
git merge的 三种操作 merge、squash merge 和 rebase merge
选项(详见官网):
-
--commit 和 --no-commit
-
--commit:执行合并并提交结果。
-
--no-commit:执行合并但不提交结果。
-
-
--squash 和 --no-squash
-
--squash:生成工作树和索引状态,虚拟合并,但实际上不进行提交、移动
HEAD
或 记录$GIT_DIR/MERGE_HEAD
。然后,你可以通过git commit -m 'xxx'
命令创建合并提交——这允许您在当前分支之上将原分支上的多个提交创建成单个提交。 -
--no-squash:执行合并并提交结果。
-
6、比较本地分支和远程分支的差别
git diff 本地分支名 origin/远程分支名
7、保存和恢复工作进度——git stash
git stash 可用于临时保存和恢复工作进度与堆栈(先进后出)中,可跨分支。
git stash 应用场景:
- 当正在dev分支上开发某个项目,这时项目中出现一个bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用git stash命令将修改的内容保存至堆栈区,然后顺利切换到hotfix分支进行bug修复,修复完成后,再次切回到dev分支,从堆栈中恢复刚刚保存的内容。
- 由于疏忽,本应该在dev分支开发的内容,却在master上进行了开发,需要重新切回到dev分支上进行开发,可以用git stash将内容保存至堆栈中,切回到dev分支后,再次恢复内容即可。
【注意】在未add
之前才能执行stash
!!!
git stash 命令详解:
-
git stash:能够将所有未提交的修改(工作区和暂存区)保存至堆栈中,用于后续恢复当前工作目录。
-
git stash save <message>:作用等同于git stash,区别是可以加一些注释。
-
git stash list:查看当前stash中的内容。
-
git stash pop stash@{num}:只能恢复一次。将当前stash中的指定内容恢复,并应用到当前分支对应的工作目录上。stash@{num}是可选项,不带此项则默认恢复最近的一次进度,相当于git stash pop stash@{0}。该命令会将堆栈中最近保存的内容删除,所以只能恢复一次。
-
git stash apply:可恢复多次。将堆栈中的内容应用到当前目录,不同于git stash pop,该命令不会将内容从堆栈中删除,也就说该命令能够将堆栈的内容多次应用到工作目录中,适应于多个分支的情况。
-
git stash drop <名称>:从堆栈中移除某个指定的 stash。
-
git stash clear:清除堆栈中的所有 内容。
-
git stash show:查看堆栈中最新保存的 stash 和当前目录的差异。
-
git stash branch:从最新的stash创建分支。应用场景:当储藏了部分工作,暂时不去理会,继续在当前分支进行开发,后续想将stash中的内容恢复到当前工作目录时,如果是针对同一个文件的修改(即便不是同行数据),那么可能会发生冲突,恢复失败,这里通过创建新的分支来解决。可以用于解决 stash 中的内容和当前目录的内容发生冲突的情景(发生冲突时,需手动解决冲突)。
总之,git stash命令的作用就是将目前还不想提交的但是已经修改的内容进行保存至堆栈中,后续可以在某个分支上恢复出堆栈中的内容。这也就是说,stash中的内容不仅仅可以恢复到原先开发的分支,也可以恢复到其他任意指定的分支上。git stash作用的范围包括工作区和暂存区中的内容,也就是说没有提交的内容都会保存至堆栈中。
8、关联远程分支与本地分支
git branch --set-upstream-to=origin/<远程分支名> <本地分支名>
// 例如: git branch --set-upstream-to=origin/fix_xiaoming fix_xiaoming
关联后,本地再 pull、push 分支时就不必每次都输入 “origin <远程分支名>” 了。
9、Git 提交规范
-
feat
增加新功能fix
修复问题/BUGstyle
代码风格相关无影响运行结果的perf
优化/性能提升refactor
重构revert
撤销修改test
测试相关docs
文档/注释chore
依赖更新/脚手架配置修改等workflow
工作流改进ci
持续集成types
类型定义文件更改wip
开发中
四、将本地项目与远程仓库关联
五、git 的学习资源
猴子老师的学习资源:https://github.com/cucygh/fe-materialhttps://github.com/cucygh/fe-material