文章

Git 基操2

Git 基操2

Git 回滚

  • 未 push 到远端
    • 未 add/已经 add 未 commit
      • git checkout . && git clean -xdf git clean
      • SourceTree reset/discard
    • 已经 commit
      • git reset
      • git revert
  • 已经 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 具体操作:

  1. 查看版本号:

可以使用命令 git log 查看:

  1. 使用 git reset --hard 目标版本号 命令将版本回退:

再用 “git log” 查看版本信息,此时本地的 HEAD 已经指向之前的版本:

  1. 使用 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 的文件

  1. git checkout . 本地所有修改的。没有的提交的,都返回到原来的状态,新增的文件不能删除,只能是修改的文件
  2. git stash 把所有没有提交的修改暂存到 stash 里面。可用 git stash pop 恢复
  3. git reset
    • git reset --hard HASH 返回到某个节点,不保留修改,已有的改动会丢失。
    • git reset --soft HASH 返回到某个节点, 保留修改,已有的改动会保留,在未提交中,git status 或 git diff 可看。

未 add 的文件(unstagged files,还在工作区)

  1. 新文件 SourceTree 中直接 Remove 掉
  2. 旧文件的修改直接在 SourceTree 中 Discard 掉
  3. 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

  1. 如果用 git reset 的话,只是将本地的 branch master 给回滚了,origin/master 并没有修改(这个仅仅是回滚了本地仓库,远端仓库并没有被回滚)
1
 git reset --mixed 8f46933
  1. 用 git revert,找到你想要回滚的 commit id,现在是想将 96c3e0 删除,那么 commit id 就填写这个。
git revert 96c3e0

然后进入 vim 模式,编写 commit message,如果不填写就会默认生成一个 commit,并有默认的 commit message
然后 push 到远端去。

  1. 用 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 图形化

git pull 时勾选最后一项

  • 如果想要把 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

  1. 查看提交历史,git log
commit 3ca6ec340edc66df13423f36f52919dfa3......

commit 1b4056686d1b494a5c86757f9eaed844......

commit 53f244ac8730d33b353bee3b24210b07......

commit 3a4226b4a0b6fa68783b07f1cee7b688.......

历史记录是按照时间排序的,时间近的排在前面。

  1. git rebase
    想要合并 1-3 条,有两个方法
  • 1、从 HEAD 版本开始往过去数 3 个版本
git rebase -i HEAD~3
  • 2、指名要合并的版本之前的版本号 (3a4226b 这个版本是不参与合并的,可以把它当做一个坐标)
git rebase -i 3a4226b
  1. 选取要合并的提交
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

然后就进去到了 vim 编辑界面

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)

40e1a1ecommit 右键 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 rebase 后找回消失的 commit

  1. 执行:git reflog 这里显示了 commit 的 sha, version, message,找到你消失的 commit id,然后可以使用这个 ‘ 消失的 ‘commit 重新建立一个 branch.

reflog 记录你的每一次命令,reflog 命令查看命令历史,以便确定要回到未来的哪个版本,特别是进行了 reset 后

  1. 切换到消失的 commit
git checkout -b branch-bak [commit-sha]
  1. 再进行 commit/merge
  2. Ref

Git 遇到的问题

ssh_exchange_identification: Connection closed by remote host

开了 vpn 或者 ss 等代理开了全局模式

git pull refusing to merge unrelated histories

  1. 错误
1
2
$ git pull
fatal: refusing to merge unrelated histories

  1. 原因

https://github.com/git/git/blob/master/Documentation/RelNotes/2.9.0.txt#L58-L68

  1. 解决
1
git pull --allow-unrelated-histories

No newline at end of file

每个文本文件最后要有一行空格,否则会提示在 SourceTree
No 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. 清除松散对象
1
git gc --prune=now
  1. 刷新本地分支 (步远程仓库分支到本地,删除远程分支不存在但是本地还有的分支)
1
git remote prune origin
  1. 拉取远端仓库代码
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 服务器管理员进行配置修改。

本文由作者按照 CC BY 4.0 进行授权