Git 基操2
Git 回滚
- 未 push 到远端
- 未 add/已经 add 未 commit
git checkout . && git clean -xdf git clean
- SourceTree reset/discard
- 已经 commit
- git reset
- git revert
- 未 add/已经 add 未 commit
- 已经 push 到远端
- git reset/revert + git push -f
git reset 和 git revert
git 的版本管理,及 HEAD 的理解
git 的每次提交,Git 都会自动把它们串成一条时间线,这条时间线就是一个分支。如果没有新建分支,那么只有一条时间线,即只有一个分支,在 Git 里,这个分支叫主分支,即master分支。有一个HEAD指针指向当前分支(只有一个分支的情况下会指向 master,而 master 是指向最新提交)。每个版本都会有自己的版本信息,如特有的版本号、版本名等。
git reset
什么是 git reset?
回到某次提交,提交及之前的 commit 都会被保留,但是此次之后的修改都会被退回到暂存区
git reset 原理:
git reset 的作用是修改 HEAD 的位置,即将 HEAD 指向的位置改变为之前存在的某个版本,如下图所示,假设我们要回退到版本一:
git reset 命令:
git reset [--hard|soft|mixed|merge|keep] [commit|HEAD]
- –mixed 会保留源码,只是将 git commit 和 index 信息回退到了某个版本.(默认 git reset 不带参数就是 –mixed 模式)
- –hard 源码也会回退到某个版本,commit 和 index 都回回退到某个版本.(注意,这种方式是改变本地代码仓库源码)
- –soft 保留源码,只回退到 commit 信息到某个版本。不涉及 index 的回退,如果还需要提交,直接 commit 即可
git reset 用途
- git reset 主要用于回滚 commit 未 push 的代码
- 已经 push 的代码也可以用 git reset,但是可能会出现很多冲突
git reset 具体操作:
- 查看版本号:
可以使用命令 git log
查看:
- 使用
git reset --hard 目标版本号
命令将版本回退:
再用 “git log” 查看版本信息,此时本地的 HEAD 已经指向之前的版本:
- 使用
git push -f
强推提交更改:
此时如果用 “git push” 会报错,因为我们本地库 HEAD 指向的版本比远程库的要旧
git revert
git revert 原理:
git revert 是用于 “ 反做 “ 某一个版本,以达到撤销该版本的修改的目的。比如,我们 commit 了三个版本(版本一、版本二、 版本三),突然发现版本二不行(如:有 bug),想要撤销版本二,但又不想影响撤销版本三的提交,就可以用 git revert 命令来反做版本二,生成新的版本四,这个版本四里会保留版本三的东西,但撤销了版本二的东西。
git revert 理解
- git revert 用于反转提交,执行 revert 命令时,要求工作树必须是干净的
- git revert 用一个新的提交来消除一个历史所做的任何修改
- git revert 生成一个新的提交来撤销某次提交,此次提交之前的 commit 都会被保留;之后你的本地代码会回滚到指定的历史版本,这时再 git push 就可以把远端仓库的代码更新了(不会像 reset 造成冲突)
git revert 案例: 要 rervert 已经 push 到远端的 9a3d099commit,用命令:
git revert 9a3d099
用 SourceTree,选中 9a3d099commit,右键 Reverse commit:
注意:如果 revert 的是一个 merge commit,要注意 parent commit 的选择,仓库http://blog.psjay.com/posts/git-revert-merge-commit/
git 回滚命令 reset、revert 的区别
- git revert 是生成一个新的提交来撤销某次提交,此次提交之前的 commit 都会被保留.
- git reset 是回到某次提交,提交及之前的 commit 都会被保留,但是此次之后的修改都会被退回到暂存区
- 用 git reset 回滚远端代码,很容易出现冲突;git revert 则不会有冲突
- reset 是在正常 commit 历史中,删除了指定的 commit,这是 HEAD 是向后移动了;revert 是在正常的 commit 历史再 commit 一次,只不过是反向提交,它的 HEAD 是一直向前的
未 push 的文件
git checkout .
本地所有修改的。没有的提交的,都返回到原来的状态,新增的文件不能删除,只能是修改的文件- git stash 把所有没有提交的修改暂存到 stash 里面。可用 git stash pop 恢复
- git reset
git reset --hard HASH
返回到某个节点,不保留修改,已有的改动会丢失。git reset --soft HASH
返回到某个节点, 保留修改,已有的改动会保留,在未提交中,git status 或 git diff 可看。
未 add 的文件(unstagged files,还在工作区)
- 新文件 SourceTree 中直接 Remove 掉
- 旧文件的修改直接在 SourceTree 中 Discard 掉
- git checkout
git checkout .
作用是放弃掉还没有加入到暂存区的修改。此命令不会删除新创建的文件,因为新创建的文件还没有被跟踪(tracked),因此需要 git clean。
❯ git checkout .
Updated 1 path from the index
git checkout . && git clean -xdf git clean
的作用是从工作目录中删除还没有被追踪的文件。- -n 不实际删除,只是进行演练,展示将要进行的操作,有哪些文件将要被删除。(可先使用该命令参数,然后再决定是否执行)
- -i 显示将要删除的文件
- -d 递归删除目录及文件(将 .gitignore 文件标记的文件全部删除)
- -f 删除文件 强制运行
- -q 仅显示错误,成功删除的文件不显示
❯ git checkout . && git clean -xdf
Updated 0 paths from the index
Removing .DS_Store
Removing .gradle/
Removing .idea/
Removing AppLibs/core/build/
Removing AppLibs/lib_pagergrid/build/
Removing AppLibs/libcommon/build/
Removing AppLibs/libwidget/build/
Removing AppUIs/AndroidUI/build/
Removing AppUIs/Google/build/
Removing build/
Removing buildSrc/.gradle/
Removing buildSrc/build/
Removing debugTools/.gradle/
Removing debugTools/build/
Removing debugTools/~/
Removing local.properties
- 可用
git clean -nxdf
查看要删除的文件及目录,确认无误后再使用下面的命令进行删除
❯ git clean -nxdf
Would remove .gradle/
Would remove buildSrc/.gradle/
Would remove buildSrc/build/
已经 add 了未 commit(staged files,在工作区)
丢弃暂存区修改 (丢弃已经 add 的文件,把暂存区修改撤销掉恢复到 unstage 状态,重新放回工作区)
SourceTree
- 方式 1:直接右键选中文件 Discard 掉
- 方式 2:Repository→Reset
已经 commit 未 push 的(在暂存区)
git reset 方式
git reset --mixed HEAD
已经 push 到远端仓库
对于已经把代码 push 到远端仓库了,回滚本地代码并回滚远端仓库的代码,回滚到某个版本,本地和远端保存一致。
已经 push 了到 origin 了 96c3e0c
,现在想回滚到 8f46933
:
- 如果用 git reset 的话,只是将本地的 branch master 给回滚了,origin/master 并没有修改(这个仅仅是回滚了本地仓库,远端仓库并没有被回滚)
1
git reset --mixed 8f46933
- 用 git revert,找到你想要回滚的 commit id,现在是想将
96c3e0
删除,那么 commit id 就填写这个。
git revert 96c3e0
然后进入 vim 模式,编写 commit message,如果不填写就会默认生成一个 commit,并有默认的 commit message
然后 push 到远端去。
- 用 SourceTree
选中要 revert 的 commit id,9a3d099
,然后单击右键,Revert commit 即可,并会生成一个新的 commit,并带默认的 commit message。
示例:
## 回退到指定版本,不保留原更改代码
git reset --hard e377f60e28c8b84158
## 回退到指定版本,保留原更改代码,且生成新的提交
# git revert e377f60e28c8b84158
git push -f origin master
案例
merge 分支错了,需要回滚到 merge 前
如果确定放弃这次合并的提交,假如是 merge 了错误的分支到 master,先通过 git reflog 或者 gitg、gitk、qgit 等工具确定你 merge 之前 master 所在的 commit,然后在 master 分支上使用 git reset --hard <commit>
重置头指针。一般来说,在 master 上直接执行 git reset --hard HEAD~
也可以回到合并之前的提交,但 git reset –hard 命令还是使用确定的 commit 为好。注意,git reset –hard 命令有风险,除非十分确定要放弃当前提交,否则最好先 git branch 为当前的提交建立个新的分支引用后再继续,待确定无误后删除即可。
git rest --hard <commint id> // 要回滚的分支的commit id
还是需要 git push -f 强推掉错误的分支的 commit id
git rebase
git pull 用 rebase 替代默认 pull 行为
默认的 git pull 是 git fetch 和 git merge 的组合操作,在 merge 时会生成一个新的 commit,让分支线变得杂乱不堪。
从远端拉取代码使用 git pull --rebase
替代默认的 git pull
命令方式
用 git rebase 替换 git merge
git pull --rebase
# 或者
git fetch origin master
git rebase origin/master
如果遇到冲突了,会中断让你去解决冲突,此时需要解决掉冲突后再 git add
,然后执行 --continue
git rebase --continue
这样 git 会继续应用 (apply) 余下的补丁,这个过程可能还会有冲突,需要继续解决掉冲突后继续。
在任何时候,你都可以用 --abort
来终止 rebase 行动,然后分支会回到 rebase 开始前的状态。
git rebase --abort
SourceTree 图形化
- 如果想要把 rebase 当做 git pull 的預設值,可以在项目根目录的
.git/config
加上:
[branch "master"]
remote = origin
merge = refs/heads/master
rebase = true
(全局添加) 也可以直接加到 ~/.gitconfig
让所有的 tracked branches 都自动套用这个设定:
[branch]
autosetuprebase = always
合并分支用 rebase 替代 merge
分支合并使用 git rebase <branch-name>
替代 git merge <branch-name>
命令方式
如在 master 分支合并 develop 分支,先切换到 master 分支,执行命令:
git rebase develop
SourceTree 图形化
先切换到要被合并的分支 develop,然后右键要合并的分支 master,然后 rebase
git rebase -i
合并多个 commit
- 查看提交历史,git log
commit 3ca6ec340edc66df13423f36f52919dfa3......
commit 1b4056686d1b494a5c86757f9eaed844......
commit 53f244ac8730d33b353bee3b24210b07......
commit 3a4226b4a0b6fa68783b07f1cee7b688.......
历史记录是按照时间排序的,时间近的排在前面。
- git rebase
想要合并 1-3 条,有两个方法
- 1、从 HEAD 版本开始往过去数 3 个版本
git rebase -i HEAD~3
- 2、指名要合并的版本之前的版本号 (3a4226b 这个版本是不参与合并的,可以把它当做一个坐标)
git rebase -i 3a4226b
- 选取要合并的提交
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 1.执行了rebase命令之后,会弹出一个窗口,头几行如下:
pick 3ca6ec3 '注释**********'
pick 1b40566 '注释*********'
pick 53f244a '注释**********'
# 2. 将pick改为squash或者s,之后保存并关闭文本编辑窗口即可。改完之后文本内容如下:
pick 3ca6ec3 '注释**********'
s 1b40566 '注释*********'
s 53f244a '注释**********'
# 3. 然后保存退出,Git会压缩提交历史,如果有冲突,需要修改,修改的时候要注意,保留最新的历史,不然我们的修改就丢弃了。修改以后要记得敲下面的命令:
git add .
git rebase --continue
#放弃这次压缩的话,执行以下命令:
git rebase --abort
# 4. 如果没有冲突,或者冲突已经解决,则会出现如下的编辑窗口:
# This is a combination of 4 commits.
#The first commit’s message is:
注释......
# The 2nd commit’s message is:
注释......
# The 3rd commit’s message is:
注释......
# Please enter the commit message for your changes. Lines starting # with ‘#’ will be ignored, and an empty message aborts the commit.
# 5.输入wq保存并推出, 再次输入git log查看 commit 历史信息,你会发现这两个 commit 已经合并了。
rebase 变更 commit log 记录
现在 log 顺序是:patch_5→patch_4→patch_3→patch_2→patch_1
我想把 patch_2 放到 patch_4 之前,修改后顺序是:patch_5→patch_2→patch_4→patch_3→patch_1
现在执行 git rebase -i commitId
git rebase -i a262604ce4efc36b0bcfe007a77d2de532575f7c
git rebase -i 在 SourceTree 使用
rebase children of xxx interactively
git rebase -i 在 Git Fork 中使用
Interactive Rebase 功能
主要看 Interactively Rebase 'master' to Here
功能,下面的选项都是对这个功能的简化
Interactively Rebase ‘master’ to Here
- Pick 选择用这个 commit,会保留该 commit 和 message
- **Edit **停止 amend
- Reword 编辑当前 commit 的 message
- Squash 将当前 commit 合并到前一个 commit,并保留 message(追加到前一个 commit message 后)
- **Fixup **将当前 commit 合并到前一个 commit,并丢弃当前 commit message,使用前一个 commit message
- Drop 移除当前 commit
多个 commit 合并成一个
目标:将 update readme.txt add x 这几个 commit 合并到一个 commit 中去
原有:
解决 1:Interactive Rebase / Squash into Parent(追加 commit)
在 40e1a1e
commit 右键 Interactive Rebase
,然后 Squash into Parent
- Squash:保留 commit message
- Fixup:丢弃 commit message
将第 1 条第 2 条 commit 改成 squash,用 afa6b5f 的 commit message
如果已经 push 到 remote 后,git push -f
下,效果:
改动 commit 顺序
目标:将 8 移动到 add 4 后面,并把 add 4 这 2 个合并成一个
原有顺序:
操作:
示例:Git Fork 将多条 commit 合并
目标:将红色框合并成一条 commit
步骤:
选中:feat(SAB-73120): 未支付组件初步框架
commit
选择 Edit
,进入交互模式
选择 FixUp,最后一个 Edit,后续还会进行 Continue Rebase 确认
修改后:
由于之前的提交已经 push 到 remote,这里需要强推:git push -f
其他
洁癖者 git rebase 和 git merge –no-ff
使用 git pull --rebase
主要是为是将提交约线图平坦化,而 git merge --no-ff
则是刻意制造分叉。
- 洁癖者用 Git:pull –rebase 和 merge –no-ff
http://hungyuhei.github.io/2012/08/07/better-git-commit-graph-using-pull---rebase-and-merge---no-ff.html - 洁癖患者的 Git GUI 指南
http://blog.justbilt.com/2017/04/12/the-git-for-neat-freak/
git rebase 后找回消失的 commit
- 执行:
git reflog
这里显示了 commit 的 sha, version, message,找到你消失的 commit id,然后可以使用这个 ‘ 消失的 ‘commit 重新建立一个 branch.
reflog 记录你的每一次命令,reflog 命令查看命令历史,以便确定要回到未来的哪个版本,特别是进行了 reset 后
- 切换到消失的 commit
git checkout -b branch-bak [commit-sha]
- 再进行 commit/merge
- Ref
Git 遇到的问题
ssh_exchange_identification: Connection closed by remote host
开了 vpn 或者 ss 等代理开了全局模式
git pull refusing to merge unrelated histories
- 错误
1
2
$ git pull
fatal: refusing to merge unrelated histories
- 原因
https://github.com/git/git/blob/master/Documentation/RelNotes/2.9.0.txt#L58-L68
- 解决
1
git pull --allow-unrelated-histories
No newline at end of file
每个文本文件最后要有一行空格,否则会提示在 SourceTreeNo newline at end of file
error: invalid path(Linux/Mac 下的文件在 Windows 下 clone 失败)
- 问题:
1
2
3
4
在git clone到本地时遇到报错:
error: invalid path 'src/main/java/com/sankuai/meituan/hive/udf/Aux.java'
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
- 分析:
core.protectNTFS 默认为 true。官方解释:If set to true, do not allow checkout of paths that would cause problems with the NTFS filesystem
NTFS 有个路径保护机制,防止文件系统出错
- 解决:
1
2
# 忽略路径中的转义字符,否则在mac和windows混合路径可能会报错
git config --global core.protectNTFS false
在 git 拉取的时候报错 fatal: cannot create directoryxxxx’: Invalid argument
- 问题:
- 原因:
UI 同事用的是 Mac,你用的是 Windows 的话,其中有个文件夹的名称中有一个 / 号,这个文件夹命名规则在苹果上没有问题,但在 windows 上,不能通过 git 创建这个文件夹(git 项目中的某个文件夹的名字中有 windows 不允许的字符比如?,. , \ :
等) - 解决:
让 UI 同事修改文件夹的名称 (不能包含/:*?"<>|
中的任何一种符号)
git pull 时远端存在删除 remote 时,报错 error: cannot lock ref unable to update local ref
原因:
远端删除了分支 origin/xos2/gac/res/sop0_hotfix
,git pull 时报错
错误:
解决
- 清除松散对象
1
git gc --prune=now
- 刷新本地分支 (步远程仓库分支到本地,删除远程分支不存在但是本地还有的分支)
1
git remote prune origin
- 拉取远端仓库代码
1
git pull
error: RPC failed; curl 56 Recv failure: Connection was reset/s
- 原因
在网络情况不稳定下克隆项目时,可能会出现上图中的错误。
问题原因:Http 缓存不够或者网络不稳定等。 - 解决:
1
git config --global http.postBuffer 524288000
git push pre-receive hook declined
分析:就是没有权限;在使用 Git 进行推送(push)操作时,如果出现 “pre-receive hook declined” 的错误提示,这通常意味着 Git 服务器上的 pre-receive 钩子阻止了推送操作的执行。这个错误提示通常意味着 Git 服务器上设置了一些规则或限制,以确保代码库的安全性和稳定性。
出现 “pre-receive hook declined” 错误的原因通常是由于 pre-receive 钩子执行了某些验证操作,但是这些操作未通过检验,导致钩子返回了 false,从而阻止了推送操作的执行。具体钩子脚本中执行的操作和验证规则与 Git 服务器的配置和要求有关,因此需要具体问题具体分析。
要解决这个问题,首先需要查看 Git 服务器的配置,了解 pre-receive 钩子的具体规则和验证操作。然后需要检查自己提交的代码是否符合这些规则,如果不符合,需要对代码进行修改,然后重新推送。如果需要修改 pre-receive 钩子的验证规则,可以联系 Git 服务器管理员进行配置修改。