𝓝𝓮𝓶𝓸&博客

【Git】命令行操作

Git 命令行操作

使用git help <command>即可查询某个命令的使用方法。

image

  • 默认当前远程版本库:origin,Git里面的origin就是一个名字,它是在你clone一个托管在Github上代码库时,git为你默认创建的指向这个远程代码库的标签。
  • 默认开发分支:master
  • 默认当前分支:HEAD
  • 当前分支的父提交(上一版本):HEAD^
  • 版本别名:提交版本后的括号内为该分支的最新版本,可以作为版本别名进行reset
    image

image

image

个人理解:我们使用git操作,其实就是将工作区指针到处移动而已。

  • 工作区:指针
  • 本地库:本地数组
  • 远程库:远程数组

各种HEAD指针

终于重新回到了git开始的地方,探究下本地git代码仓库中.git目录。

本篇对标题中的几个概念进行简单说明。

我们可以使用以下命令,来获取本地仓库的HEAD和远程仓库的HEAD

git branch -a

feature/xxxx
remotes/origin/HEAD -> origin/master

其实.git目录文件中存放了我们的HEAD引用:
本地仓库:

cat .git/HEAD

远程仓库:

cat .git/refs/remotes/origin/HEAD

1 HEAD

HEAD可以指向分支,也可以指向提交。

这里先说指向分支的情况。

下图为HEAD指向本地master分支。
image

查看.git目录下的HEAD
image

image

image

说明HEAD其实就是指向master分支,而master中存放的就是最新的commit(40位的sha-1值)。


这里需要特别注意,git push origin HEADgit pull origin HEAD不是一个HEADpush时用的是本地仓库的HEADpull时用的是远程仓库的HEAD

git pull origin HEAD和git push origin HEAD中的HEAD不是同一个。

  • git pull origin HEAD中,HEAD指的是远程仓库的当前分支,这个命令是用来从远程仓库拉取最新的代码并合并到当前分支。
  • 而在git push origin HEAD中,HEAD指的是你当前的本地分支,这个命令是用来将你当前的本地分支推送到远程仓库。

所以,虽然这两个命令中都出现了HEAD,但它们所指代的分支是不同的。

2 ORIG_HEAD

在上图的.git目录可以看到还有一个ORIG_HEAD文件
image

查看内容,其实存放的也是commit。当进行一些有风险的操作的时候,如reset、merge或者rebase,Git会将HEAD原来所指向commit对象的sha-1值存放于ORIG_HEAD文件中。这里不做深入测试分析。

3 FETCH_HEAD

在上图的.git目录可以看到还有一个FETCH_HEAD文件
image

当进行一些有风险的操作的时候,如reset、merge或者rebase,Git会将HEAD原来所指向commit对象的sha-1值存放于ORIG_HEAD文件中。也就是说ORIG_HEAD可以让我们找到进行最近一次危险操作之前的HEAD位置。

FETCH_HEAD表示某个branch在服务器上的最新状态。
每一个执行过fetch操作的项目都会存在一个FETCH_HEAD列表,其中每一行对应于远程服务器的一个分支。
当前分支指向的FETCH_HEAD, 就是这个文件第一行对应的那个分支。存在两种情况:如果没有显式的指定远程分支, 则远程分支的master将作为默认的FETCH_HEAD;如果指定了远程分支, 就将这个远程分支作为FETCH_HEAD.

SEQUENCER SUBCOMMANDS 序列化子命令

--continue:继续
Continue the operation in progress using the information in .git/sequencer. Can be used to continue after resolving conflicts in a failed cherry-pick or revert.
使用.git / sequencer中的信息继续进行操作。可用于在解决失败的挑选或恢复中的冲突后继续。

--skip:略过
Skip the current commit and continue with the rest of the sequence.
跳过当前提交,继续完成序列的其余部分。

--quit:退出
Forget about the current operation in progress. Can be used to clear the sequencer state after a failed cherry-pick or revert.
忘记当前正在进行的操作。可以用来清除序列状态在一个失败的挑选或恢复后。

--abort:中止
Cancel the operation and return to the pre-sequence state.
取消操作并返回到预序列状态。

1 本地库初始化

git init:初始化本地仓库

效果

注意:.git目录中存放的是本地库相关的子目录和文件,不要删除,也不要胡乱修改。

2 设置配置签名

当你安装Git后首先要做的事情是设置你的用户名称和e-mail地址。这是非常重要的,因为每次Git提交都会使用该信息。它被永远的嵌入到了你的提交中。

但是git不与具体使用的某个远程仓库的用户绑定,如github、gitee。

使用git config --list进行查看配置列表

形式:

用户名:tom
Email地址:goodMorning@163.com

作用:区分不同开发人员的身份

辨析:这里设置的签名和登录远程库(代码托管中心)的账号、密码没有任何关系。

命令

  • 项目级别/仓库级别:仅在当前本地库范围内有效

    • git config user.name tom_pro
    • git config user.email goodMorning_pro@163.com
    • 信息保存位置:./.git/config文件
  • 系统用户级别:登录当前操作系统的用户范围

    • git config --global user.name tom_glb
    • git config --global user.email goodMorning_pro@163.com
    • 信息保存位置:~/.gitconfig文件(~代表根目录)
  • 级别优先级

    • 就近原则:项目级别优先于系统用户级别,二者都有时采用项目级别的签名
    • 如果只有系统用户级别的签名,就以系统用户级别的签名为准
    • 二者都没有不允许

解决git每次提交和拉取代码需要输入用户名和密码问题
输入以下命令行:

git config --global credential.helper store

3 基本操作

3. 1 status 状态查看

git status:查看工作区、暂存区状态

3. 2 add 添加

git add [filename]:将工作区的“新建/修改”添加到暂存区

  • filename:为欲添加的文件名,*代表当前目录的所有文件。

mv 重命名

git mv

git mv命令可以将一个文件重命名,并将此重命名操作记录在Git的版本历史中。使用这个命令,你只需要指定原文件名和新文件名即可。

git mv old_file.txt new_file.txt

使用git mv命令重命名文件后,可以使用git status命令来查看文件的状态。Git将会显示出文件的重命名操作。

https://blog.csdn.net/subtitle_/article/details/128955085

3. 3 commit 提交

git commit -m "commit message" [filename]:将暂存区的内容提交到本地库

个人理解:

  • add:将我们的代码交给git监管
  • commit:将我们的代码提交到本地库
  • push:将我们的代码推送到远程库

3. 4 log 查看历史记录日志

git log:查看历史记录,显示完整的历史记录信息

多屏显示控制方式:

  • 空格:向下翻页
  • b:向上翻页
  • q:退出

git log --grep xxx:搜索日志


上面这种方法显示的历史记录可能不太好看:我们可以采用以下方法进行简化历史记录的显示:

git log --pretty=oneline:我们可以使用此命令让历史记录放在一行上

git log --oneline:缩短了哈希值的显示,只显示后面7位

上面的几种方法只能显示过去与现在


下面这个命令可以显示过去、现在和未来

git reflog:这种方式可以看到我们所有的历史记录,包括回退前、回退后、过去、现在、将来。

HEAD@{移动到当前版本需要多少步}

git reflog --all:可以查看更完整的所有记录


git show [commit_id]:查看某次历史提交信息的完整信息,包括文件修改信息
git show --name-only:查看某次历史提交信息的文件名
有时候咱们想要知道咱们提交的这部分改动都有哪些。此时使用 git diff有些麻烦,所以可执行 git show命令。可直接显示出来咱们都修改了哪部分。

3. 5 reset 前进后退

本质:使用git reflog来显示所有历史记录

可以使用以下方法来移动HEAD指针

基于索引值操作[推荐]

  • git reset --hard [局部索引值]
  • git reset --hard a6ace91

使用^符号:只能后退

  • git reset --hard HEAD^:一个^表示后退一步,n个表示后退n步

使用~符号:只能后退

  • git reset --hard HEAD~n:表示后退n步

回退后提交只能使用git push -f强制提交,因为在他前面已经有其他新节点了,得强行覆盖掉。

3. 6 reset 命令的三个参数对比

image

原本本地库、暂存区、工作区三者版本一致:

--soft参数

  • 仅仅在本地库移动HEAD指针(本地库回退)
  • 暂存区还是原样
  • 工作区还是原样

    这里可以看出,我们的本地库版本退后了一步,而其他的都没改变

--mixed参数(默认

  • 在本地库移动HEAD指针(本地库回退)
  • 重置暂存区(重置,即 后退版本)(暂存区回退)
  • 工作区还是原样

--hard参数(危险,会完全回到)

  • 在本地库移动HEAD指针(本地库回退)
  • 重置暂存区(暂存区回退)
  • 重置工作区(工作区回退)

让单个文件回滚到指定版本

1.进入到文件所在文件目录,或者能找到文件的路径

查看文件的修改记录

git log fileName

2.回退到指定版本

git reset 版本号 fileName

3.提交到本地参考:
git commit -m “提交的描述信息”

4.更新到工作目录

git checkout fileName

5.提交到远程仓库

git push origin master

这样指定的文件回退到指定版本了

3. 7 删除文件并找回

前提:删除前,文件存在时的状态提交到了本地库。

操作:git reset --hard [指针位置]

  • 删除操作已经提交到本地库:指针位置指向历史记录
  • 删除操作尚未提交到本地库:指针位置使用HEAD

revert 还原(撤销)某个版本

git revert <commit id> :git 会生成一个新的 commit,将指定的 commit 内容从当前分支上撤除。

在 Git 开发中通常会控制主干分支的质量,但有时还是会把错误的代码合入到远程主干。 虽然可以直接回滚远程分支, 但有时新的代码也已经合入,直接回滚后最近的提交都要重新操作。 那么有没有只移除某些 Commit 的方式呢?可以一次 revert操作来完成。

原理:git revert是用于“反做”某一个版本,以达到撤销该版本的修改的目的。比如,我们commit了三个版本(版本一、版本二、 版本三),突然发现版本二不行(如:有bug),想要撤销版本二,但又不想影响撤销版本三的提交,就可以用 git revert 命令来反做版本二,生成新的版本四,这个版本四里会保留版本三的东西,但撤销了版本二的东西。

考虑这个例子,我们提交了 6 个版本,其中 3-4 包含了错误的代码需要被回滚掉。 同时希望不影响到后续的 5-6。

* 982d4f6 (HEAD -> master) version 6
* 54cc9dc version 5
* 551c408 version 4, harttle screwed it up again
* 7e345c9 version 3, harttle screwed it up
* f7742cd version 2
* 6c4db3f version 1

这种情况在团队协作的开发中会很常见:可能是流程或认为原因不小心合入了错误的代码, 也可能是合入一段时间后才发现存在问题。 总之已经存在后续提交,使得直接回滚不太现实。

下面的部分就开始介绍具体操作了,同时我们假设远程分支是受保护的(不允许 Force Push,大多情况都是这样,导致使用不了reset)。 思路是从产生一个新的 Commit 撤销之前的错误提交。

使用 git revert <commit> 可以撤销指定的提交, 要撤销一串提交可以用 git revert <commit1>..<commit2> 语法。 注意这是一个前开后闭区间,即不包括 commit1,但包括 commit2。(这个跟Java中的截取字符串一样,都是为了方便)

理解:这里是为了基于commit1反做还原后面的commit2,然后依次补齐后面的提交修改,来达到检测还原反做是否与后续提交冲突的效果。

git revert -n f7742cd..551c408
# 这样也可以 git revert --no-commit f7742cd..551c408
git commit -a -m 'This reverts commit 7e345c9 and 551c408'

其中 f7742cd 是 version 2,551c408 是 version 4,这样被移除的是 version 3 和 version 4。 注意 revert 命令会对每个撤销的 commit 进行一次提交--no-commit 后可以最后一起手动提交。

git revert -n COMMITgit revert --no-commit COMMIT:这就相当于在本节点恢复撤销以前的COMMIT,然后不自行提交。也就是说只改变了本地的工作区代码,并不改变节点提交版本号。

-n 是代表不自动提交,只是将回滚的代码添加到,暂存区(效果类似 git add),因为 revert 默认是回滚一个版本,如果自动提交,最后的代码会变成 verson1 – version2 – version3 – version4 – revert-to-version3 – revert-to-version2 会生成2个commit 记录,所以,一般都要使用 -n(也写作 --no-commit) 这个参数。

注意:reset是回到以前版本的节点上,而revert -n还是在本节点,只是恢复撤销了。
-n, --no-commit
Usually the command automatically creates a sequence of commits. This flag applies the changes necessary to cherry-pick each named commit to your working tree and the index, without making any commit. In addition, when this option is used, your index does not have to match the HEAD commit. The cherry-pick is done against the beginning state of your index.

此时 Git 记录是这样的:

* 8fef80a (HEAD -> master) This reverts commit 7e345c9 and 551c408
* 982d4f6 version 6
* 54cc9dc version 5
* 551c408 version 4, harttle screwed it up again
* 7e345c9 version 3, harttle screwed it up
* f7742cd version 2
* 6c4db3f version 1

现在的 HEAD(8fef80a)就是我们想要的版本,把它 Push 到远程即可。

确认 diff

如果你像不确定是否符合预期,毕竟批量干掉了别人一堆 Commit,可以做一下 diff 来确认。 首先产生 version 4(551c408)与 version 6(982d4f6)的 diff,这些是我们想要保留的:

git diff 551c408..982d4f6

然后再产生 version 2(f7742cd)与当前状态(HEAD)的 diff:

git diff f7742cd..HEAD

如果 version 3, version 4 都被 version 6 撤销的话,上述两个 diff 为空。 可以人工确认一下,或者 grep 掉 description 之后做一次 diff。 下面介绍的另一种方法可以容易地确认 diff。

bd1b80cb 和0f2f7e4a 是错误的提交可以通过git revert bd1b80cb 0f2f7e4a 来进行单独提交的回滚

还原合并

报错:commit xxx is a merge but no -m option

通常你不能恢复一个合并,因为你不知道合并的哪一边应该被认为是主线。此选项指定主线的父节点号(从1开始),并允许还原除父节点外所有的更改。

# git revert -m parent-number
# git revert --mainline parent-number
git revert -m 1 xxxx

场景如下图,流程如下:

  • Step 1:小辉在分支dev1上开发,提交了4次(分别为c1、c2、c3、c4),发布时需要把dev1分支合并到master分支上,合并的时候采用的是禁止快进式合并,即执行命令git merge --no-ff dev1生成了提交记录m5,并发布到线上。
  • Step 2: 不幸的是,小辉的dev1分支上存在bug,需要回滚dev1上的所有提交。

切到master分支上,使用git log --graph看出:

可以看到Merge: e0a0378 e6a29dc,其中e0a03781(master主分支),e6a29dc为2(另一个次分支),我们的-m parent-number就是选择其中一个分支作为主线分支进行保留,还原其他分支的更改。

这里可以采用命令 git revert -m 1 77cb206f6b5db591a43eb55899b84f161656694f,即保留master主分支(1代表保留master主分支,revert掉另外一个分支的提交),revert完成后再看log会生成一个revert提交m6,如下图:

简化图如下:

参考:https://blog.csdn.net/zhuqiuhui/article/details/105424776

撤销掉撤销操作

一、问题场景

前提,我所在的公司采用 GitLab 进行代码管理。

这次的异常发生在不久前对系统的一次常规迭代升级过程中,由于系统依赖的第三方包出现了问题,导致服务起不来,为了不影响其他功能的使用,所以将 master 分支进行 revert 到上次的版本。

等到我们解决了依赖包的问题,重新从 dev 提了 merge 到 master 的申请时,却发现代码变动都不显示了(注:如果修改了代码,也可能出现“conflict”的问题,但是本质一样),导致无法合并分支。也就是说:被回滚的代码被认为已经存在于 master 分支上,不算改动。

二、原因分析

revert 操作实际是只是进行了一次逆向 commit,将 merge 的代码进行回滚,但是 commit 的记录还存在。也就是说,dev 上面存在的待提交的代码,其实已经是 master 的过去代码,属于已提交过的状态,所以不会显示 different。
image

其实相当于,分支代码中有“版本三”这个提交id,merge到主分支之后,git发现“版本三”这个提交id已经存在了(git没发现被“版本四”撤销了),所以merge显示不了代码变动信息。

三、解决方案

方案一:撤销掉撤销操作(官方推荐方法)

该核心思想就是:对 revert 的那次提交记录再次 revert 。

首先,切换到 master 分支,并基于 master 分支拉出一个分支 revert_tmp。作为 master 的副本,revert_tmp 的作用就是保存 revert 的提交记录;

git checkout master
git checkout -b revert_tmp

其次,在 master 分支上找到 revert 的那条提交记录的版本号,回滚至之前的版本(版本号可以通过“git log”命令,或者从网页端查看);

git log         # 查询<版本号>,格式,如:f2c3b544166eec612ea6814d6cd19aeef46824f8
git revert <版本号>  

然后,切换到 dev 分支上,将 revert_tmp 这个分支 merge 到 dev 分支上。

git checkout dev 
git merge revert_tmp 
git push -f

最后,在 dev 重新提交对 master 的 merge 申请,会发现 revert 之前的代码都回来了。

方案二:reset 法

与 revert 不同,采用 git reset 将 head 向后移动到上一次 merge 前的 commit 版本,会丢弃所有的 merge commit 记录(revert 不会丢弃,是逆向 commit),所以,再次合并不会出现记录不显示或者冲突的问题。

git reset HEAD^    # 回退所有内容到上一个版本 
git reset HEAD^^   # 回退所有内容到上上个版本 

image

注意:谨慎使用 –-hard 参数,它会删除回退点之前的所有信息。

https://blog.csdn.net/weixin_44259720/article/details/126223793

新建分支重新提交(不推荐)

使用本地分支,新建一个不同名的分支,在commit_2代码量不大的情况下,重新拷贝到新分支上,然后重新提交,merge,这样绕过了revert1的限制,本质上这个方法就是重写。

3. 8 diff 比较文件差异

git diff [文件名]:将工作区中的文件和暂存区进行比较

git diff [本地库中历史版本] [文件名]:将工作区中的文件和本地库历史记录比较

git diff:不带文件名比较多个文件

git diff HEAD^ HEAD:比较上一版本和现在版本的区别,即 上一版本经过什么样的操作到达现在版本,HEAD^ → HEAD

git diff --name-only:只输出有变动的文件名

4 分支branch管理

4. 1 什么是分支?

在版本控制过程中,使用多条线同时推进多个任务。

4. 2 分支的好处?

  • 同时并行推进多个功能开发,提高开发效率
  • 各个分支在开发过程中,如果某一个分支开发失败,不会对其他分支有任何影响。失败的分支删除重新开始即可。

4. 3 分支操作

git branch:查看本地分支。

电脑B本地clone仓库默认只会clone下master分支,而其他电脑A推送的分支是不会默认同步下来的。
clone下来的远程仓库并不会将所有分支都clone下来。

git branch -a:查看所有分支,包括本地分支与远程分支。
image

git branch [分支名] [commitId]:创建分支。当我们运行了git branch xx 之后,它就会默认基于你当前所在的这个分支最后一次提交的文件,进行一个复制,作为这个新创建的 xx 分支的初始代码。

git branch -v:(version)查看本地分支的版本情况

git branch -d [分支名]:(delete)删除分支

注意:在删除分支的时候,一定要先退出要删除的分支,然后才能删除。

git checkout [分支名]:切换分支

git checkout -b [分支名]:创建并切换分支branch

合并分支

  • 第一步:切换到接受修改的分支(被合并,增加新内容)上:git checkout [被合并分支名]
  • 第二步:执行merge命令:git merge [有新内容分支名]
  • 第三步:删除?:git branch -d [分支名]

查看分支:

注意:当前分支前面有个标记“*”。

创建分支:

切换分支:

合并分支:
现在先在dev分支下的readme文件中新增一行并提交本地

切换到master分支下观察readme文件

将dev分支的内容与master分支合并:

删除分支:

注意:在删除分支的时候,一定要先退出要删除的分支,然后才能删除。

合并所有分支之后,需要将master分支提交线上远程仓库中:

修改分支名:
git branch -m oldName newName

切换远程分支

查看远程所有分支

$ git branch -a

git branch不带参数,列出本地已经存在的分支,并且在当前分支的前面用*标记,加上-a参数可以查看所有分支列表,包括本地和远程,远程分支一般会用红色字体标记出来

或者先用git fetch获取远程所有资源再进行分支切换。

上述命令会显示所有分支,例如:

master
origin/master
origin/feature-branch
origin/bugfix-branch

选择要切换到的远程分支,并使用 git checkout 命令进行切换。例如,切换到名为 feature-branch 的远程分支:

git checkout origin/feature-branch

注意,此时您已经处于一个“游离”的 HEAD 状态,不能进行提交操作。如果您想在远程分支上进行修改并提交更改,最好使用以下命令将其重命名为本地分支:

git checkout -b feature-branch origin/feature-branch

上述命令将创建一个本地分支,名称为 feature-branch,与远程分支 origin/feature-branch 建立绑定关系,并自动切换到该分支。现在,您可以在此分支上对代码进行修改并提交更改了。


远程已有分支,本地需要新建对应分支

git checkout --track origin/XXX

合并提交

对于多分支的代码库,将代码从一个分支转移到另一个分支是常见需求。

这时分两种情况。一种情况是,你需要另一个分支的所有代码变动,那么就采用合并(git merge)。另一种情况是,你只需要部分代码变动(某几个提交),这时可以采用 Cherry pick。

git将某分支的某次提交合并到另一分支
代码开发的时候,有时需要把某分支(比如develop分支)的某一次提交合并到另一分支(比如master分支),这就需要用到git cherry-pick命令。

  1. 首先,切换到develop分支,敲 git log 命令,查找需要合并的commit记录,比如commitID:7fcb3defff

  2. 然后,切换到master分支,使用 git cherry-pick -n 7fcb3defff 命令,就把该条commit记录合并到了master分支,这只是在本地合并到了master分支

-n 是代表不自动提交,只是将回滚的代码添加到,暂存区(效果类似 git add),因为 revert 默认是回滚一个版本,如果自动提交,最后的代码会变成 verson1 – version2 – version3 – version4 – revert-to-version3 – revert-to-version2 会生成2个commit 记录,所以,一般都要使用 -n(也写作 --no-commit) 这个参数。

-n, --no-commit
Usually the command automatically creates a sequence of commits. This flag applies the changes necessary to cherry-pick each named commit to your working tree and the index, without making any commit. In addition, when this option is used, your index does not have to match the HEAD commit. The cherry-pick is done against the beginning state of your index.

  1. 最后,git push 提交到master远程(可以用命令也可以用工具的push操作),至此,就把develop分支的这条commit所涉及的更改合并到了master分支。

解决冲突

  • 冲突的表现

  • 冲突的解决

    • 第一步:编辑文件,删除特殊符号
    • 第二步:把文件修改到满意的程度,保存退出
    • 第三步:git add [文件名]

      提示:You must edit all merge conflicts and then mark them as resolved using git add.

    • 第四步:git commit -m "日志信息"

    注意:此时commit一定不能带具体文件名

什么是"关联"分支?

git pushgit pull在默认的情况下将会操作本地仓库与远程仓库的关联分支。
https://www.cnblogs.com/xiaoyuanjiangjin/articles/16345355.html

首先我们先使用git branch -vv 查看一下目前分支的“关联”情况;

$ git branch -vv
* dev    1a1b215 [origin/dev] Merge branch 'master' of https://github.com/jinxintang/gitTest into dev
  master a09fdc4 [origin/master] create pull

可以看到我们的本地的dev关联的是远程(origin)的dev,本地的master关联的是远程(origin)的master;
那么这种关联是如何建立、是否可以修改呢;
配置本地分支与远程分支的三种方法:

1.检出时建立关联关系:git checkout -b dev origin/dev
当我们检查时,git会自动为我们检出的分支和远程分支建立关联关系;

2.提交时配置关联关系:git push -u origin <remote_branch>git push --set-upstream origin <remote_branch>

nemo /e/02.Workspace-test/gitTest (dev)
$ git branch -vv
* dev 3b7001a [origin/dev] cm
  master  a09fdc4 [origin/master] create pull

nemo /e/02.Workspace-test/gitTest (dev)
$ git push -u origin dev
Everything up-to-date
Branch dev set up to track remote branch dev from origin.

nemo /e/02.Workspace-test/gitTest (dev)
$ git branch -vv
* dev 3b7001a [origin/dev] cm
  master  a09fdc4 [origin/master] create pull

通过上面的例子可以看到push前dev关联的是origin/dev,执行push -u 后管理分支改为origin/dev
注:默认配置下,提交时本地分支需和远程分支同名;

3.更改git/config文件:git branch --set-upstream-to=<remote_branch>

nemo /e/02.Workspace-test/gitTest (dev_zcz)
$ git branch --set-upstream-to=origin/zcz
Branch dev_zcz set up to track local branch origin/zcz.

nemo /e/02.Workspace-test/gitTest (dev_zcz)
$ git branch -vv
* dev_zcz    3b7001a [origin/zcz] cm
  master     a09fdc4 [origin/master] create pull
  origin/zcz 3b7001a [dev_zcz] cm

无论使用上述那种方法,本地分支和远程分支的“关联”最终都会写到config文件;

nemo /e/02.Workspace-test/gitTest/.git (GIT_DIR!)
$ cat config
[core]
        repositoryformatversion = 0
        filemode = false
        bare = false
        logallrefupdates = true
        symlinks = false
        ignorecase = true
[remote "origin"]
        url = https://github.com/jinxintang/gitTest.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
[branch "dev_zcz"]
        remote = .
        merge = refs/heads/origin/zcz
[branch "origin/zcz"]
        remote = .
        merge = refs/heads/dev_zcz

注:本项目的配置信息存放目录:项目所在目录.git/config
看完这三种配置关联分支的方法,想必大家已经对“关联分支”有了一定了解;


关联分支:在git中表现为upstream,无论是使用push -u 或是 git branch --set-upstream-to方法,均会将这种对应关系写入.git/config配置文件,如果一个本地分支没有关联分支,则无法执行 git pushgit pull指令;

没有"关联"分支的情况下,使用push会先让你设置一个upstream branch.

nemo /e/02.Workspace-test/gitTest (dev_no_upstream)
$ git branch -vv
* dev_no_upstream 3b7001a cm
  dev_zcz         3b7001a [origin/zcz] cm
  master          a09fdc4 [origin/master] create pull
  origin/zcz      3b7001a [dev_zcz] cm

nemo /e/02.Workspace-test/gitTest (dev_no_upstream)
$ git push
fatal: The current branch dev_no_upstream has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin dev_no_upstream

标签tag管理

通常我们在生产环境发版时,创建一个 tag,这样一个不可修改的版本将被冻结起来,这对于发布或者版本管理非常有益。

git tag --help

1. Git tag的基本概念和用法

1.1. 什么是 Git tag?

在 Git 中,tag是标记存储库历史记录中特定提交的一种方式。tag通常用于标记项目的特定版本,例如版本 1.0 或 2.0。

项目的版本管理中,每当一个release版本发布时,需要做一个记录,以便以后需要的时候能查找特定的版本,这时候就用到tag这个功能。

Git中的tag指向一次commit的id,通常用来给开发分支做一个标记,如标记一个版本号。

1.2. Git 标签有什么作用?

tag可以用于指定在仓库历史记录的某个特定点上创建的版本,这在软件开发中非常有用。如果需要在软件发布时创建tag,开发人员就可以跟踪哪个版本用于真实世界中的生产环境。在软件开发中,tag还可以用于标记里程碑或特定功能的实现。

除此之外,tag还可以用于将存储库中的提交与版本控制系统中的标记相关联。这可以用于确保特定版本的软件与存储库中的特定提交相关联。在软件开发中,这可以用于确保特定版本的软件与单独的存储库分支相关联。

tag和branch有什么区别?

branch是一个分支;tag是分支上的一个里程碑,一个点;
tag就是一个只读的branch;一般为每一个可发布的里程碑版本打一个tag;
简单说比如branch有1.0,1.1等,其中1.0分支里可以有1.0.1,1.0.2这些tag;
tag就像是一个里程碑一个标志一个点; branch是一个新的征程一条线;
tag是静态的,branch要向前走;
稳定版本备份用tag,新功能多人开发用branch(开发完成后merge到master)。

2. 创建 Git tag

可以使用消息创建带注释的 tag,也可以创建不带消息的轻量级 tag。带注释的 tag 包括 tag 的名称、电子邮件和日期等信息,而轻量级 tag 仅是对特定提交的指针。

2.1. 创建轻量级tag

运行 git tag [tagname] 命令,后跟标签的名称。例如,git tag v2.0 将创建一个名为 “v2.0” 的轻量级标签。

image

2.2. 创建带有注释的tag

运行 git tag -a [tagname] 命令,后跟引号中的消息。例如,git tag -a v1.0 -m "Initial release" 将创建一个名为 “v1.0” 并带有 “Initial release” 消息注释的标签。

运行 git show [tagname] 命令,可以查看标签的详细信息,包含注释、作者、邮箱、创建日期等信息
image

2.3. 编辑已有的tag

运行 git tag [tagname] 命令,后跟新的提交哈希或分支名称。例如,git tag v1.0 [new-commit-hash] 将把“v1.0”标签移动到新提交。或者,git tag v1.0 [new-branch-name] 将标签移动到新分支的末尾。

image

3. 列出和检出 Git tag

3.1. 列出所有tag

运行 git tag 命令。例如,git tag 将列出所有本地标签。如果要查看远程存储库中的标签,可以使用 git ls-remote --tags [remote] 命令。例如,git ls-remote --tags origin 将列出名为 origin 的远程存储库中的所有标签。

image

3.3. 列出匹配的tag

运行 git tag -l [pattern] 命令。例如,git tag -l v1.* 将列出所有以 “v1.” 开头的标签。

image

3.4. 检出tag

要检出 Git 标签,可以使用 git checkout [tagname] 命令。例如,要检出名为 “v1.0” 的标签,请运行 git checkout v1.0 命令。

检出标签将使存储库进入 “游离 HEAD” 状态,这意味着所做的任何更改都不会与分支相关联。如果要在标签上进行更改,可以创建一个新的分支来进行更改,以避免对标签进行更改并破坏历史记录。

4. 将 Git tag推送到远程存储库

4.1. 推送单个tag

运行 git push origin [tagname]。例如,要将标签“v1.0”推送到远程仓库,请使用命令 git push origin v1.0
image

运行 git ls-remote 查看远程标签。可以看到v1.0已经成功推送到远程仓库。
image

4.2. 推送所有tag

运行 git push --tags。这将推送所有尚未在远程仓库上的本地标签。
image

此时我们可以在仓库看到本地的tag已经被推送过来了。
image

4.3. 删除tag

请注意,如果您删除了远程标签,那么需要确保本地存储库中也删除了它。您可以使用 git tag -d [tagname] 命令来删除本地标签。例如,要删除名为 v1.0 的标签,可以运行以下命令:git tag -d v1.0
image


要删除远程 Git 标签,需要使用 git push 命令,后跟 --delete 选项和标签名称。例如,要删除名为 v1.0 的标签,可以运行以下命令:git push --delete origin v1.0。这将从远程存储库中删除标签。
image

变体是 git push <remote> :refs/tags/<tagname>

$ git push origin :refs/tags/v1.4-lw
To /git@github.com:schacon/simplegit.git
 - [deleted]         v1.4-lw

上面这种操作的含义是,将冒号前面的空值推送到远程标签名,从而高效地删除它。

https://blog.csdn.net/chengyq116/article/details/126237871

5. Git tag高级用法

5.1. 签名tag

要为 Git 标签签名,可以使用 git tag -s [tagname] 命令,并使用你的 GPG 密钥对标签进行签名。例如,git tag -s v1.0 将使用您的 GPG 密钥对名为 “v1.0” 的标签进行签名。
image

如果报错No secret key,解决方法:在Git Bash终端配置gpg程序位置
image

编辑完成后:wq保存退出即可。
image

要验证签名的 Git 标签,请使用 git tag -v [tagname] 命令。例如,git tag -v v1.0 将验证名为 “v1.0” 的标签的签名,并显示签名者和提交 ID 等信息。如果您想验证未标记的提交,可以使用提交哈希代替标记名称,例如,您可以运行git verify-commit [commit-hash]直接验证提交,而不是运行git tag -v [tagname]
image

签名标签可以帮助确保标签的完整性和来源,因此它们特别适用于需要进行身份验证或需要进行安全审计的软件项目。


注意:如果报错了,请确保已经安装GPG,并且密钥已正确配置。

请按照以下步骤安装并配置 GPG:

  1. 在您的操作系统上打开终端或命令提示符。
  2. 输入以下命令以安装 GPG:
  • 如果你使用的是 macOS,可以使用 Homebrew 包管理器安装 GPG:

    brew install gnupg
    
  • 如果你使用的是 Linux,可以使用你的包管理器安装 GPG。例如,在 Ubuntu 上,可以使用以下命令:

    sudo apt-get install gnupg
    
  • 如果你使用的是 Windows,可以从 Gpg4win 官方网站下载并安装 Gpg4win:https://gpg4win.org/download.html

  1. 安装完成后,你可以验证 GPG 是否正确安装,可以在终端上运行以下命令:

    gpg --version
    

    如果你正确安装了 GPG,你应该会看到版本号和其他信息。
    image

  2. 配置密钥
    1.使用gpg --gen-key命令生成一个新的GPG密钥。这会打开一个提示,您可以在其中输入有关密钥的信息,例如您的姓名和电子邮件地址。
    2.生成密钥后,您可以使用gpg --list-keys命令列出您的密钥。这将显示您密钥的指纹,您稍后需要使用它。
    3.使用gpg --export --armor [你的钥匙指纹]命令导出您的GPG公钥。这将以ASCII装甲格式输出您的公钥,您可以将其复制并粘贴到Git托管服务中。
    image
    ok,配置成功
    image
    4.通过运行以下命令配置Git使用您的GPG密钥:

    git config --global user.signingkey [你的钥匙指纹]
    git config --global commit.gpgsign true
    

    运行git config --list查看user.signingkey是否配置成功。
    image
    这些命令将告诉Git使用您的GPG密钥来签署您的提交。

5.2. tag过滤

要过滤 git 标签,请使用 git tag 命令的 --list 选项,后跟一个模式。例如,要列出以“v1.”开头的所有标记,请运行以下命令:

git tag --list 'v1.*'

这将列出所有以“v1.”开头的标记,例如“v1.0”、“v1.1”等。

你还可以使用 --contains 选项来列出包含指定提交的标记。例如,要列出包含提交 abc123 的所有标记,请运行以下命令:

git tag --contains abc123

这将列出所有包含提交 abc123 的标记。

5.3. 访问tag历史记录

要访问Git标签的历史记录,您可以使用git tag命令,带有--list选项和--sort选项,后跟version:refname以按版本号对标签进行排序。例如,要按时间顺序列出标签,您可以运行以下命令:

git tag --list --sort=-v:refname
  • 按值降序排序的前缀 -
  • version:refname”或“v:refname”(标签名称被视为版本)

这将以相反的时间顺序列出所有标签,并将最新的标签列在第一位。
image

您还可以通过添加--max-count选项,后跟要显示的标签数,来限制列出的标签数。例如,要列出最近的10个标签,可以运行以下命令:

git tag --list --sort=-v:refname --max-count=10

这将以相反的时间顺序列出最近的10个标签。

https://blog.csdn.net/to_the_Future/article/details/131560257

rebase变基

该 git rebase 命令允许您轻松更改一系列提交,修改存储库的历史记录。您可以一起对提交进行重新排序、编辑或压缩。

通常,您会习惯于 git rebase :

  • 编辑以前的提交消息
  • 将多个提交合并为一个
  • 删除或还原不再需要的提交

警告:由于更改提交历史记录可能会使使用存储库的其他人遇到困难,因此在已推送到存储库时重新确定提交基数被认为是不好的做法。若要了解如何安全地基于 GitHub.com 变基,请参阅“关于拉取请求合并”。

针对分支重新变基提交

若要在另一个分支和当前分支状态之间重新构建所有提交,可以在 shell(Windows 的命令提示符或 Mac 和 Linux 的终端)中输入以下命令:

git rebase --interactive OTHER-BRANCH-NAME

根据某个时间点重新确定提交基数

若要对当前分支中的最后几次提交进行变基,可以在 shell 中输入以下命令:

git rebase --interactive HEAD~7

变基时可用的命令

变基时有六个命令可用:

  • pick:pick 简单地表示包含提交。重新排列 pick 命令的顺序会在变基时更改提交的顺序。如果选择不包含提交,则应删除整行。
  • reword:该 reword 命令类似于 ,但在您使用它后,变基过程将暂停 pick ,并让您有机会更改提交消息。提交所做的任何更改均不受影响。
  • edit:如果您选择 edit 提交,您将有机会修改提交,这意味着您可以完全添加或更改提交。您还可以在继续变基之前进行更多提交。这允许您将大型提交拆分为较小的提交,或者删除在提交中所做的错误更改。
  • squash:此命令允许您将两个或多个提交合并为一个提交。提交被压缩到它上面的提交中。Git 让你有机会编写一条新的提交消息来描述这两个更改。fixup这类似于 squash ,但要合并的提交会丢弃其消息。提交只是合并到它上面的提交中,并且使用早期提交的消息来描述这两个更改。
  • exec:这允许您对提交运行任意 shell 命令。

使用 git rebase 示例

无论您使用哪种命令,Git 都会启动默认文本编辑器并打开一个文件,其中详细说明了所选范围内的提交。该文件如下所示:

pick 1fc6c95 Patch A
pick 6b2481b Patch B
pick dd1475d something I want to split
pick c619268 A fix for Patch B
pick fa39187 something to add to patch A
pick 4ca2acc i cant' typ goods
pick 7b36971 something to move before patch B

# Rebase 41a72e6..7b36971 onto 41a72e6
#
# 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
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

从上到下分解这些信息,我们看到:

  • 列出了 7 个提交,这表明我们的起点和当前分支状态之间有 7 个更改。
  • 您选择变基的提交将按照最早的更改(在顶部)到最新更改(在底部)的顺序进行排序。
  • 每行列出一个命令(默认情况下)、 pick 提交 SHA 和提交消息。整个 git rebase 过程围绕着您对这三列的操作。您所做的更改将重新定位到您的存储库中。
  • 提交后,Git 会告诉您我们正在处理的提交范围 ( 41a72e6..7b36971 )。
  • 最后,Git 通过告诉您在变基提交时可用的命令来提供一些帮助。

下面是一个关于在命令行上使用 git rebase 的简短教程。

使用 Git 变基

在此示例中 git rebase ,我们将介绍除 . exec

我们将通过在终端上输入 git rebase --interactive HEAD~7 来开始变基。我们最喜欢的文本编辑器将显示以下行:

pick 1fc6c95 Patch A
pick 6b2481b Patch B
pick dd1475d something I want to split
pick c619268 A fix for Patch B
pick fa39187 something to add to patch A
pick 4ca2acc i cant' typ goods
pick 7b36971 something to move before patch B

在此示例中,我们将:

  • 将第五次提交 ( fa39187 ) 压缩到 "Patch A" 提交 ( 1fc6c95 ),使用 squash .
  • 将最后一个提交 ( 7b36971 ) 移到提交 ( 6b2481b ) 之前, "Patch B" 并将其保留为 pick .
  • 将 "A fix for Patch B" 提交 ( c619268 ) 合并到 提交 ( 6b2481b ) 中, "Patch B" 并使用 fixup 忽略提交消息。
  • 将第三个提交 ( dd1475d ) 拆分为两个较小的提交,使用 edit .
  • 修复拼写错误的 commit ( 4ca2acc ) 的提交消息,使用 reword .

唷!这听起来像是很多工作,但通过一步一步来,我们可以轻松地进行这些更改。

首先,我们需要修改文件中的命令,使其如下所示:

pick 1fc6c95 Patch A
squash fa39187 something to add to patch A
pick 7b36971 something to move before patch B
pick 6b2481b Patch B
fixup c619268 A fix for Patch B
edit dd1475d something I want to split
reword 4ca2acc i cant' typ goods

我们已将每行的命令从 pick 更改为我们感兴趣的命令。

现在,保存并关闭编辑器;这将启动交互式变基。

Git 跳过第一个 rebase 命令 , pick 1fc6c95 因为它不需要执行任何操作。它转到下一个命令 squash fa39187。由于此操作需要您的输入,因此 Git 会再次打开您的文本编辑器。它打开的文件如下所示:

# This is a combination of two commits.
# The first commit's message is:

Patch A

# This is the 2nd commit message:

something to add to patch A

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   a
#

这个文件是 Git 表达的方式,“嘿,这就是我要用它 squash 做的事情。它列出了第一个提交的消息 ( "Patch A" ) 和第二个提交的消息 ( "something to add to patch A" )。如果您对这些提交消息感到满意,可以保存文件并关闭编辑器。否则,您可以选择通过简单地更改文本来更改提交消息。

当编辑器关闭时,变基将继续:

pick 1fc6c95 Patch A
squash fa39187 something to add to patch A
pick 7b36971 something to move before patch B
pick 6b2481b Patch B
fixup c619268 A fix for Patch B
edit dd1475d something I want to split
reword 4ca2acc i cant' typ goods

Git 处理两个 pick 命令( pick 7b36971 和 pick 6b2481b )。它还处理 fixup 命令 ( fixup c619268 ),因为它不需要任何交互。 fixup 将 中的 c619268 更改合并到提交之前的 6b2481b .这两个更改将具有相同的提交消息: "Patch B" 。

Git 进入 edit dd1475d 操作,停止并将以下消息打印到终端:

You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

此时,您可以编辑项目中的任何文件以进行任何其他更改。对于您所做的每项更改,您都需要执行新的提交,您可以通过输入 git commit --amend 命令来执行此操作。完成所有更改后,可以运行 git rebase --continue .

然后,Git 获取命令 reword 4ca2acc 。它会再次打开文本编辑器,并显示以下信息:

i cant' typ goods

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD^1 <file>..." to unstage)
#
# modified:   a
#

和以前一样,Git 会显示提交消息供您编辑。您可以更改文本 ( "i cant' typ goods" ),保存文件,然后关闭编辑器。Git 将完成变基并将您返回到终端。

将变基代码推送到 GitHub

由于您更改了 Git 历史记录,因此通常 git push origin 的做法将不起作用。您需要通过“强制推送”最新更改来修改命令:

# Don't override changes
$ git push origin main --force-with-lease

# Override changes
$ git push origin main --force

强制推送具有严重影响,因为它会更改分支提交的历史顺序。请谨慎使用它,尤其是在多人访问存储库时。

https://i.cnblogs.com/posts/edit;postId=14276616

merge合并

注意:合并操作只能基于当前分支合并其他分支,不能直接其他分支A合并其他分支B,毕竟可能要在本地处理冲突!!!

在很多介绍GItFlow工作流的文章里面,都会推荐在合并分支的时候加上--no-ff参数, 而我们在合并的时候,有时git也会提示 使用了 fast-forward, 这里我将介绍一下merge的三种状态及 git merge 和 git merge --no-ff 的区别

Git merge的时候,有几种合并方式可以选择

--ff
When the merge resolves as a fast-forward, only update the branch pointer, without creating a merge commit. This is the default behavior.
 
--no-ff
Create a merge commit even when the merge resolves as a fast-forward. This is the default behaviour when merging an annotated (and possibly signed) tag.
 
--squash
--no-squash
Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit, move the HEAD, or record $GIT_DIR/MERGE_HEAD (to cause the next git commit command to create a merge commit). This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus).
 
With --no-squash perform the merge and commit the result. This option can be used to override --squash.

而我们平常什么都不加的时候,则使用默认--ff , 即 fast-forward 方式

看过官方注释后,我们用一张图来简单描画一下相应的行为
image

  • --fast-forward:Git 合并两个分支时,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,叫做“快进”(fast-forward)不过这种情况如果删除分支,则会丢失merge分支信息。

  • --squash:把一些不必要commit进行压缩,比如说,你的feature在开发的时候写的commit很乱,那么我们合并的时候不希望把这些历史commit带过来,于是使用–squash进行合并,此时文件已经同合并后一样了,但不移动HEAD,不提交。需要进行一次额外的commit来“总结”一下,然后完成最终的合并。

  • --no-ff:关闭fast-forward模式,在提交的时候,会创建一个merge的commit信息,然后合并的和master分支

merge的不同行为,向后看,其实最终都会将代码合并到master分支,而区别仅仅只是分支上的简洁清晰的问题,然后,向前看,也就是我们使用reset 的时候,就会发现,不同的行为就带来了不同的影响

下图是使用 merge --no-ff的时候的效果,此时git reset HEAD^ --hard 的时候,整个分支会回退到 dev2-commit-2
image

下图是使用 fast-forward 模式的时候,即 git merge ,这时候 git reset HEAD^ --hard,整个分支会回退到 dev1-commit-3
image

通常我们把 master 作为主分支,上面存放的都是比较稳定的代码,提交频率也很低,而 develop 是用来开发特性的,上面会存在许多零碎的提交,快进式合并会把 develop 的提交历史混入到 master 中,搅乱 master 的提交历史。所以如果你根本不在意提交历史,也不爱管 master 干不干净,那么 --no-ff 其实没什么用。不过,如果某一次 master 出现了问题,你需要回退到上个版本的时候,比如上例,你就会发现退一个版本到了 commint-3,而不是想要的 commit-2,因为 feature 的历史合并进了 master 里。这也就是很多人都会推荐 --no-ff 的原因了吧。

https://blog.csdn.net/qq_40259641/article/details/102785419

临时存储

git stash temporarily shelves (or stashes) changes you've made to your working copy so you can work on something else, and then come back and re-apply them later on. Stashing is handy if you need to quickly switch context and work on something else, but you're mid-way through a code change and aren't quite ready to commit.

git stash:能够将所有未提交的修改(工作区和暂存区)保存至堆栈中,用于后续恢复当前工作目录。

特别注意:保存的是修改,而不是一整个项目快照,所以你在A分支的修改,也可以同步到B分支,而不修改B分支的其他模块代码。

前提:必须是处于git下的文件,未add到git的文件无法使用。

  • git stash:保存当前工作进度,将工作区和暂存区恢复到修改之前。

  • git stash save "message":作用同上,message为此次进度保存的说明。

  • git stash list:显示保存的工作进度列表,编号越小代表保存进度的时间越近。

  • git stash pop [stash@{num}]git stash pop [num]:恢复工作进度到工作区,此命令的stash@{num}是可选项,在多个工作进度中可以选择恢复,不带此项则默认恢复最近的一次进度相当于git stash pop stash@{0}

  • git stash apply [stash@{num}]git stash apply [num]:恢复工作进度到工作区且该工作进度可重复恢复,此命令的stash@{num}是可选项,在多个工作进度中可以选择恢复,不带此项则默认恢复最近的一次进度相当于git stash apply stash@{0}

  • git stash drop [stash@{num}]git stash drop [num]:删除一条保存的工作进度,此命令的stash@{num}是可选项,在多个工作进度中可以选择删除,不带此项则默认删除最近的一次进度相当于git stash drop stash@{0}

  • git stash clear:删除所有保存的工作进度。

git stash show
查看堆栈中最新保存的stash和当前目录的差异。

$ git stash show
 src/main/java/com/wy/StringTest.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

git stash show stash@{1}查看指定的stash和当前目录差异。
通过 git stash show -p 查看详细的不同:

$ git stash show -p
diff --git a/src/main/java/com/wy/CacheTest.java b/src/main/java/com/wy/CacheTest.java
index 6e90837..de0e47b 100644
--- a/src/main/java/com/wy/CacheTest.java
+++ b/src/main/java/com/wy/CacheTest.java
@@ -7,6 +7,6 @@ package com.wy;
  */
 public class CacheTest {
     public static void main(String[] args) {
-        System.out.println("git stash test");
+        System.out.println("git stash test1");
     }
      }
     diff --git a/src/main/java/com/wy/StringTest.java b/src/main/java/com/wy/StringTest.java
     index a7e146c..711d63f 100644
     --- a/src/main/java/com/wy/StringTest.java
     +++ b/src/main/java/com/wy/StringTest.java
     @@ -12,7 +12,7 @@ public class StringTest {

     @Test
     public void test1() {
-        System.out.println("=================");
+        System.out.println("git stash test1");
         System.out.println(Strings.isNullOrEmpty(""));//true
         System.out.println(Strings.isNullOrEmpty(" "));//false
         System.out.println(Strings.nullToEmpty(null));//""

同样,通过git stash show stash@{1} -p查看指定的stash的差异内容。

git stash branch
从最新的stash创建分支。
应用场景:当储藏了部分工作,暂时不去理会,继续在当前分支进行开发,后续想将stash中的内容恢复到当前工作目录时,如果是针对同一个文件的修改(即便不是同行数据),那么可能会发生冲突,恢复失败,这里通过创建新的分支来解决。可以用于解决stash中的内容和当前目录的内容发生冲突的情景。
发生冲突时,需手动解决冲突。

git fetch

要讲清楚git fetch,git pull,必须要附加讲清楚git remote,git merge 、远程repo, branch 、 commit-id 以及 FETCH_HEAD。

  1. 【git remote】首先, git是一个分布式的结构,这意味着本地和远程是一个相对的名称。
    本地的repo仓库要与远程的repo配合完成版本对应必须要有 git remote子命令,通过git remote add来添加当前本地长度的远程repo, 有了这个动作本地的repo就知道了当遇到git push 的时候应该往哪里提交代码。

  2. 【git branch】其次,git天生就是为了多版本分支管理而创造的,因此分支一说,不得不提, 分支就相当于是为了单独记录软件的某一个发布版本而存在的,既然git是分布式的,便有了本地分支和远程分支一说,git branch 可以查看本地分支, git branch -r 可以用来查看远程分支。 本地分支和远程分支在git push 的时候可以随意指定,交错对应,只要不出现版本从图即可。

  3. 【git merge】再者,git的分布式结构也非常适合多人合作开发不同的功能模块,此时如果每个人都在其各自的分支上开发一个相对独立的模块的话,在每次release制作时都需先将各成员的模块做一个合并操作,用于合并各成员的工作成果,完成集成。 此时需要的就是git merge.

  4. 【git push 和 commit-id】在每次本地工作完成后,都会做一个git commit 操作来保存当前工作到本地的repo, 此时会产生一个commit-id,这是一个能唯一标识一个版本的序列号。 在使用git push后,这个序列号还会同步到远程repo。

在理解了以上git要素之后,分析git fetch 和 git pull 就不再困难了。

git fetch, 理解fetch的含义, 是远程协作的关键.


而理解 fetch 的关键, 是理解 FETCH_HEAD.

这里需要解释下什么是FETCH_HEAD?

FETCH_HEAD指的是:某个branch在服务器上的最新状态,每一个执行过fetch操作的项目都会存在一个FETCH_HEAD列表,这个列表保存在 .git/FETCH_HEAD 文件中, 其中每一行对应于远程服务器的一个分支。当前分支指向的FETCH_HEAD,就是这个文件第一行对应的那个分支

一般来说, 存在两种情况:

  • 如果没有显式的指定远程分支, 则远程分支的master将作为默认的FETCH_HEAD.
  • 如果指定了远程分支, 就将这个远程分支作为FETCH_HEAD.

常见的git fetch 使用方式包含以下四种:

git fetch

这一步其实是执行了两个关键操作:

  • 创建并更新所有远程分支的本地的远程分支。(注意这里是“本地的远程分支”
  • 设定当前分支的FETCH_HEAD远程服务器的master分支 (上面说的第一种情况)

需要注意的是: 和push不同,fetch会自动获取远程新加入的分支。

所以说,如果仅用git fetch之后,切换已有的本地分支,已有的本地分支不会有什么改变,因为“本地的远程分支”还没有merge进入“本地分支”。

git fetch origin

同上, 只不过手动指定了remote.

git fetch origin branch1

设定当前分支的 FETCH_HEAD远程服务器的branch1分支.

注意:在这种情况下, 不会在本地创建本地远程分支,这是因为:

这个操作是git pull origin branch1的第一步, 而对应的pull操作,并不会在本地创建新的branch.

一个附加效果是:

这个命令可以用来测试远程主机的远程分支branch1是否存在, 如果存在, 返回0, 如果不存在, 返回128, 抛出一个异常.

git fetch origin branch1:branch2

同样的也有,git pull origin branch1:branch2
但是我一般使用git pull --no-ff origin branch1:branch2,不快进,而是合并为一个新的提交节点。

只要明白了上面的含义,这个就很简单了,

  • 首先执行上面的fetch操作
  • 使用远程branch1分支在本地创建branch2(但不会切换到该分支),
    如果本地不存在branch2分支, 则会自动创建一个新的branch2分支,
    如果本地存在branch2分支, 并且是fast forward,则自动合并两个分支,否则,会阻止以上操作。

git fetch origin :branch2等价于: git fetch origin master:branch2

检出

我们一般使用检出(checkout)都是检出其他的分支,也就是分支切换,其实检出还可以检出暂存区、本地库(如果暂存区没有)或远程库(如果本地库没有)文件到我们的工作区,来达到一个拉取、恢复工作区代码的作用。

检出任意提交记录

检出到分支的上一个提交记录

git checkout HEAD^

https://blog.csdn.net/GAO1125571212/article/details/128976198

重置修改

当我们修改了工作区的一些代码后,发现有些文件的修改我们并不需要,需要还原,我们就可以使用checkout来重置、放弃修改。

git checkout HEAD -- filename:这里的--是为了表明后面的字段名为当前分支的文件名路径名,而不是分支名

其实原理也就是上面我们说的,将当前分支的文件,检出本地库,到工作区中,将工作区恢复到没修改过的状态。

拉取单个文件

在master分支下拉取某个文件

git fetch
git checkout master -- path/to/file

回滚单个文件

git reset HEAD^ filename
git checkout -- filename

注意:这里我们的reset并不能--hard只能使用默认的--mixed那么工作区的内容就不会改变,所以我们需要使用checkout来将我们的本地库数据拉取到工作区。

那么git reset为什么不能通过路径进行硬/软重置呢?

Because there's no point (other commands provide that functionality already), and it reduces the potential for doing the wrong thing by accident.
因为没有意义(其他命令已经提供了这种功能) ,并且它减少了偶然做错事情的可能性。

A "hard reset" for a path is just done with git checkout HEAD -- <path> (checking out the existing version of the file).
对路径的“硬重置”只需使用 git checkout HEAD -- < path > (检查文件的现有版本)即可完成。

A soft reset for a path doesn't make sense.
对路径进行软重置是没有意义的。

A mixed reset for a path is what git reset -- <path> does.
一个路径的混合重置就是 git reset -- < path > 所做的。

参考文章


也就是说,我们可以使用git checkout HEAD -- < path >来硬重置路径。
如果省略版本号,那么就默认为HEAD,这就是为什么我们能够重置我们的修改,放弃我们的修改。

二进制文件冲突解决

如果二进制文件发生冲突,不方便查看git插入的冲突标记, 解决比较棘手,通常最简单的解决方法是提前沟通好,相同修改的地方二选一.
git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>…​

git checkout --ours[--theirs] [--] PATH:这里的--是为了表明后面的字段名为文件名路径名,而不是分支名,可有可无。

--ours 表示检出当前分支,即保存当前分支的改动,丢弃另外分支的改动.
--theirs 表示检出另外分支, 即保存另外分支的改动,丢弃当前分支的改动.

举个栗子:
有分支A和B, 当前我们在分支A上, 需要把分支B合并到分支A, HashMap.c文件发生冲突了.
git checkout --ours -- HashMap.c 表示冲突的地方采用A分支上的修改,丢弃B分支上的修改.
git checkout --theirs -- HashMap.c 表示冲突的地方采用B分支上的修改,丢弃A分支上的修改.

解决完冲突后,就可以像往常一样 git add git commit了。

git worktree(idea打开git多个分支的项目)

在开发工作中,大家估计都遇到过这样的需求:正在开发的东西不想动,但是需要一个完整工作目录来做测试或者搞一些其他事情。一般来说可以拉个分支,用git stash保存目前的工作区状态,等搞完事情再切回来。但是如果需要两个或者更多个工作区同时搞事情上面的方案就不行。这时候当然可以git clone几个完整仓库副本来搞,但是也很麻烦费事,而且仓库的同步也是个问题。有没有更好的方法呢?在此之前我是不知道的,直到发现了git的一个新的屠龙技——git worktrees工作树功能。

https://zhuanlan.zhihu.com/p/92906230?utm_id=0

概要

git-worktree
git worktree 非常适合大型项目又需要维护多个分支,想要避免来回切换的情况

重新给分支/提交一个新的工作区域,且原工作区无法在switch到那个分支,只有释放了才行
新增一个工作树之后,

  • 原仓库目录的.git文件夹中产生一个worktree的文件夹,里面放置着相关的数据,
  • 新工作树的目录中没有.git文件夹,而是有一个.git的文件

优点

  • git worktree 可以快速进行并行开发,同一个项目多个分支同时并行演进
  • git worktree 的提交可以在同一个项目中共享
  • git worktree 和单独 clone 项目相比,节省了硬盘空间,又因为git worktree使用hard link实现,要远远快于 clone

常用命令

git clone : 拉多个远程仓库到本地,缺点同步起来比较麻烦,占磁盘内存
git worktree: git worktree 会将新分支取出来新建一个文件夹,他们的 local repo 是同一个

git worktree add 新目录名称 需要检出的分支名称 : 检出分支到新目录

注意:原目录和新目录不能打开同一分支,会报错fatal: 'feature/xxx' is already checked out at 'D:/BC/Projects'

git worktree list : 查看所有分支
git worktree remove 目录名 : 删除分支

https://www.cnblogs.com/lxd670/p/17550490.html

远程库

为了能在任意 Git 项目上协作,你需要知道如何管理自己的远程仓库。 远程仓库是指托管在因特网或其他网络中的你的项目的版本库。 你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以读写。 与他人协作涉及管理远程仓库以及根据需要推送或拉取数据。 管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义它们是否被跟踪等等。

在服务器上搭建Git仓库

git remote

  • 显示所有远程仓库:git remote -v
  • 新增远程仓库:git remote add xxx
  • 删除远程仓库:git remote rm xxx

远程仓库有很多种使用方法:http、ssh、git

$ cd grit
$ git remote -v
bakkdoor  https://github.com/bakkdoor/grit (fetch)
bakkdoor  https://github.com/bakkdoor/grit (push)
cho45     https://github.com/cho45/grit (fetch)
cho45     https://github.com/cho45/grit (push)
defunkt   https://github.com/defunkt/grit (fetch)
defunkt   https://github.com/defunkt/grit (push)
koke      git://github.com/koke/grit.git (fetch)
koke      git://github.com/koke/grit.git (push)
origin    git@github.com:mojombo/grit.git (fetch)
origin    git@github.com:mojombo/grit.git (push)
nemo      ssh://git@github.com:5280/nemo/grit.git (fetch)
nemo      ssh://git@github.com:5280/nemo/grit.git (push)

可以看到我们如果可以使用ssh连上某个服务器,那么我们也可以使用git通过ssh去连上此服务器,获取路径/nemo/grit.git下的git项目。

克隆

git clone [远程地址]:克隆下来的远程仓库会默认创建一个名为origin的remote。

拉取

  • pull=fetch+merge
  • git fetch [远程库地址别名] [远程分支名]:把远程库的代码拉取到本地库。
  • git merge [远程分支名]git merge [远程库地址别名/远程分支名]:把本地库的代码与工作区合并
  • git pull [远程库地址别名] [远程分支名]

推送

git push [别名] [分支名]

命令行常用操作步骤

情况一:创建自己的项目

1. 创建远程库


2. 创建远程库地址别名

注意:这里的remote地址以及别名,都只对当前git仓库生效,对于其他git仓库不生效,即 作用域仅在本git仓库中。

git remote -v 查看当前所有远程地址别名
git remote add [别名] [远程地址]

3. 推送

git push [别名] [分支名]

推送需要输入账号和密码:

情况二:克隆别人的项目

1. 克隆远程仓库(从无到有)

git clone [远程地址]

效果

  • 完整的把远程库下载到本地
  • 创建 origin 远程地址别名
  • 初始化本地库

情况三:更新已有的项目(更新位于不同分支)

1. 拉取(从旧到新)

  • git pull = git fetch + git merge
  • git fetch [远程库地址别名] [远程分支名]
  • git merge [远程分支名]git merge [远程库地址别名/远程分支名]
  • git pull [远程库地址别名] [远程分支名]

要特别注意的是,自己写完了代码,没有提交推送之前不要pull,得先提交推送然后发生冲突了再pull,否则git一看没有提交修改,说明文件没有修改,那就直接pull过来了,可能覆盖了你的本地文件。

如果遇到了上述问题:请看情况九


那为什么要先commit,然后pull,然后再push,我pull了,岂不是把自己改的代码都给覆盖掉了嘛,因为远程没有我改的代码,我pull,岂不是覆盖了我本地的改动好的地方了?那我还怎么push?

答:这个先 commit 再 pull 最后再 push 的情况就是为了应对多人合并开发的情况,

commit 是为了告诉 git 我这次提交改了哪些东西,不然你只是改了但是 git 不知道你改了,也就无从判断比较;

pull是为了本地 commit 和远程commit 的对比记录,git 是按照文件的行数操作进行对比的,如果同时操作了某文件的同一行那么就会产生冲突,git 也会把这个冲突给标记出来,这个时候就需要先把和你冲突的那个人拉过来问问保留谁的代码,然后在 git add && git commit && git pull 这三连,再次 pull 一次是为了防止再你们协商的时候另一个人给又提交了一版东西,如果真发生了那流程重复一遍,通常没有冲突的时候就直接给你合并了,不会把你的代码给覆盖掉

出现代码覆盖或者丢失的情况:比如A B两人的代码pull 时候的版本都是1,A在本地提交了2,3并且推送到远程了,B 进行修改的时候没有commit 操作,他先自己写了东西,然后 git pull 这个时候 B 本地版本已经到3了,B 在本地版本3的时候改了 A 写过的代码,再进行了git commit && git push 那么在远程版本中就是4,而且 A 的代码被覆盖了,所以说所有人都要先 commit 再 pull,不然真的会覆盖代码的

情况四:更新已有的项目(更新位于同一分支)(当前分支被其他人更新)

  • 在CR过程中,目标分支可能被其他人更新,导致CR版本不是最新,称为落后目标分支,这时需要对CR进行变基(rebase),相当于基于目标分支最新版重新patch了一个commit
  • 当我们需要将feature分支的代码合入dev分支时,我们需要更新feature分支的代码为dev分支的最新代码,所以需要git pull --rebase origin dev

1. 变基

rebase:变基,改变我们的基础节点。
使用方法:git rebase [分支名] [提交id],我们就可以将此分支的提交id变基过来,即 将本地工作区指针移动到该提交。

  • git pull --rebase = git fetch + git rebase
  • git fetch [远程库地址别名] [远程分支名]
  • git rebase [远程分支名]git rebase [远程库地址别名/远程分支名]:把本地库的代码变基到工作区
  • git pull --rebase

理解:也就是说,我们先在基点origin(C)的基础上先更新其他人的修改(D),即 改变我们的基础节点(变基),我们在变基后的基点D的基础上进行增加我们的修改E,组合成为了新的节点(R)。

2. 遇到冲突被中断,处理冲突,继续变基

步骤:
Step1. 在本地仓库中, 更新并合并代码。
也就是说我们需要在pull过来的最新版的基础上继续补充我们的变动,即 变基为最新版本。

# 这里分开写是想进行分步讲解
git fetch origin	# 将远程主机的最新内容拉到本地
git rebase origin/customer/ceb	# 将本地资源与远程资源合并变基,后发生冲突,会标明所有冲突

git pull --rebase  # 注意这里是拉取资源和变基两步,拉取资源无冲突,变基有冲突,所以我们后面解决了冲突之后要继续变基

Step2. 依据提示分别打开冲突的文件, 逐一修改冲突代码

Step3. 所有冲突都修改完毕后, 提交修改的代码。
也就是说我们需要在我们解决冲突进行修改的基础上继续补充我们的变动,即 变基为修改版本。

git add -u	# 加入修改后的代码
git rebase --continue	# 因为上一步rebase命令产生了冲突,终止了,所以这里解决冲突后继续执行rebase,查看是否还有冲突未解决,如果还有冲突未解决就得继续修改

git add -u <--> git add –update
提交所有被删除和修改的文件到数据暂存区
git add .
提交所有修改的和新建的数据暂存区
git add -A <--> git add –all
提交所有被删除、被替换、被修改和新增的文件到数据暂存区

Step4. 更新patch

git push origin HEAD:refs/for/customer/ceb	# 在完全解决冲突之后才能推入远程

提示:

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".

命令:

vim text.txt # 修改冲突的文件
git add text.txt
# 因为冲突已经修改完了,产生了一个新的本地节点,所以需要继续变基到新的冲突修改完的节点,相当于变基到新的节点把commit提交了
# 冲突修改前 → 提交
# 变基为 冲突修改前 → 冲突修改后 → 提交
git rebase --continue
git push origin HEAD:refs/for/master

理解:也就是说,我们先在基点origin(C)的基础上先更新其他人的修改(D),即 改变我们的基础节点(变基),我们在变基后的基点D的基础上进行增加我们的修改E,组合成为了新的节点(R)。

git merge和git rebase的区别

使用下面的关系区别这两个操作:
git pull = git fetch + git merge
git pull --rebase = git fetch + git rebase

现在来看看git merge和git rebase的区别。

假设有3次提交A,B,C。
image

我们在分支origin的基础上进行了修改,代码编写,即 在远程分支origin的基础上创建一个名为"mywork"的分支并提交了,同时有其他人在"origin"上做了一些修改并提交了。
(我们的本地代码编写分支为mywork(E),其他人的修改为D)
image

提交树节点介绍:

  • C:基础节点,我们的代码编辑和别人的代码编辑修改,都在此节点开始完成,以此节点为基点;
  • D:其他人对C版本的代码修改;
  • E:我们当前对C版本的代码修改;
  • M:其他人的修改与我们的修改进行合并(merge);
  • R:先更新其他人的修改,再在其他人的基础上更新我们的修改(变基,即 改变了我们的基础节点)。

其实这个时候E不应该提交,因为提交后会发生冲突。如何解决这些冲突呢?有以下两种方法:

1、git merge
用git pull命令把"origin"分支上的修改pull下来与本地提交合并(merge)成版本M,但这样会形成图中的菱形,让人很困惑。
image

理解:也就是说,我们自己的修改代码编写,即 本地提交分支(E)与其他人的修改(D)进行了分支合并(merge),合并为了一个新的节点(M)

2、git rebase
创建一个新的提交R,R的文件内容和上面M的一样,但我们将E提交废除,当它不存在(图中用虚线表示)。由于这种删除,小李不应该push其他的repository.rebase的好处是避免了菱形的产生,保持提交曲线为直线,让大家易于理解。
image

理解:也就是说,我们先在基点origin(C)的基础上先更新其他人的修改(D),即 改变我们的基础节点(变基),我们在变基后的基点D的基础上进行增加我们的修改E,组合成为了新的节点(R)。

在rebase的过程中,有时也会有conflict,这时Git会停止rebase并让用户去解决冲突,解决完冲突后,用git add命令去更新这些内容,然后不用执行git-commit,直接执行git rebase --continue,这样git会继续apply余下的补丁。
在任何时候,都可以用git rebase --abort参数来终止rebase的行动,并且mywork分支会回到rebase开始前的状态。

情况五:发起CR(Code Review)

CR(Code Review)又称为代码评审,是指在软件开发过程中,对源代码的系统性检查。通常的目的是查找系统缺陷,保证软件总体质量和提高开发者自身水平。

某些公司可能会拥有自己的CR,也就是说,我们的代码提交时并不是直接提交到github,而是先提交到公司的CR,进行代码评审,然后评审通过了之后我们才合入github。

发起CR:每个公司发起CR的命令可能不一样,大家按照公司要求书写即可。
git push origin HEAD:refs/for/master

  • git push 肯定是推送
  • origin : 是远程的库的名字
  • HEAD: 是一个特别的指针,它是一个指向你正在工作的本地分支的指针,可以把它当做本地分支的别名,git这样就可以知道你工作在哪个分支
  • refs/for :意义在于我们提交代码到服务器之后是需要经过code review 之后才能进行merge的
  • refs/heads 不需要

情况六:代码已经提交了,但是发现有点问题,所以想增加patch

增加patch(打补丁):我们的代码此时还没有并入代码库,还存留在CR中,所以我们可以对此次提交增加patch。
git commit --amend:修正最后一次提交,传递此选项将修改最后一次提交。阶段性更改将被添加到先前的提交中,而不是创建一个新的提交。该命令将打开系统配置的文本编辑器,并提示更改先前指定的提交消息。

This option adds another level of functionality to the commit command. Passing this option will modify the last commit. Instead of creating a new commit, staged changes will be added to the previous commit. This command will open up the system's configured text editor and prompt to change the previously specified commit message.

git add cr.txt
git commit --amend
# git commit --amend --no-edit # 会保留上次的提交信息
git push origin master:refs/for/master

为什么我们可以就某次提交来打补丁呢?

从上图可以看出,我们的提交有commit idchange id两种,而commit id会随着提交次数而变化,一次操作就会生成一个commit id,而change id会随着变化而变化,打补丁的时候,我们的change id不改变,只会改变commit id,所以会保证变化的一致性,保证我们可以打补丁。

情况七:想临时存储代码(分支错误,切换分支,代码还不想提交)

应用场景:
1 当正在dev分支上开发某个项目,这时项目中出现一个bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用git stash命令将修改的内容保存至堆栈区,然后顺利切换到hotfix分支进行bug修复,修复完成后,再次切回到dev分支,从堆栈中恢复刚刚保存的内容。
2 由于疏忽,本应该在dev分支开发的内容,却在master上进行了开发,需要重新切回到dev分支上进行开发,可以用git stash将内容保存至堆栈中,切回到dev分支后,再次恢复内容即可。
总的来说,git stash命令的作用就是将目前还不想提交的但是已经修改的内容进行保存至堆栈中,后续可以在某个分支上恢复出堆栈中的内容。这也就是说,stash中的内容不仅仅可以恢复到原先开发的分支,也可以恢复到其他任意指定的分支上。git stash作用的范围包括工作区和暂存区中的内容,也就是说没有提交的内容都会保存至堆栈中。

git stash	# 能够将所有未提交的修改(工作区和暂存区)保存至堆栈中,用于后续恢复当前工作目录。
git checkout xxx	# 切换分支
git stash pop	# 将当前stash中最近保存的内容弹出,并应用到当前分支对应的工作目录上。

情况八:冲突的产生与解决

案例:模拟产生冲突。

  1. 同事在下班之后修改了线上仓库的代码

注意:此时我本地仓库的内容与线上不一致的。

  1. 第二天上班的时候,我没有做 git pull 操作,而是直接修改了本地的对应文件的内容

  2. 需要在下班的时候将代码修改提交到线上仓库(git push

    提示我们要在再次push之前先git pull操作。

【解决冲突】

  1. git pull

    此时 git 已经将线上(push)本地仓库(commit)的冲突合并到了对应的文件中

  2. 打开冲突文件,解决冲突
    解决方法:需要和同事(谁先提交的)进行商量,看代码如何保留,将改好的文件再次提交即可。

  3. 重新提交

线上效果:

新手上路小技巧:上班第一件事先 git pull,可以在一定程度上避免冲突的产生。

情况九:git更新以后本地改动代码不见了被覆盖了

git pull会把本地未提交修改覆盖,这是当然的,毕竟add只是存放在暂存区暂存着了,并没有持久保存,想要持久保存就得commit上传到本地库中。

个人理解:

  • add:将我们的代码交给git监管
  • commit:将我们的代码提交到本地库
  • push:将我们的代码推送到远程库

一般我们git更新不会覆盖掉我们本地的代码,但是也不是绝对的,在一定条件下,会被覆盖,在这种我本地文件没有上传git,没法使用git版本控制,又被git上的版本干掉了我本地代码的情况下,我相信小伙伴都会想说已经mmp的,这里我们说下这个方法,主要是使用idea,webstrom的 localHistory 来找回我们的代码。

  1. 选中整个项目右击,你就能看到本地的历史记录

  2. 点击本地历史记录,你能看到所有本地的记录

为了避免此情况:可以写完代码先 commit 再 pull 最后再 push,养成习惯。

历史记录的修改

有的时候我们会突然发现某个地方需要修改,最常见的某个不应该被提交的文件被提交了进来。我们希望它不只是在后续的版本当中不再出现,而是希望整个从 git 仓库当中移除掉。这个时候我们就需要修改 git 之前的历史记录。这个时候应该怎么办呢?

不要着急,git 当中有很多的手段可以修改之前的历史提交记录。

修改最后一次提交

这一点我们在之前的文章当中曾经提到过,如果我们只是想要修改最后一次的提交记录,这是比较简单的。我们只需要直接修改我们想要修改的部分,在提交的时候加上一个参数 --amend 即可。

git commit --amend

amend 的意思是补丁,它可以把我们这一次的修改合并到上一条历史记录当中,而不会产生一个新的 commit 记录。运行之后,它会打开一个 vim 编辑器,我们还可以修改上一次 commit 时输入的提示信息。
image

我们使用 git log 检查的话,会发现历史记录的修改时间还是上一次的时间。看起来就好像什么也没有发生过一样,悄无声息地就改掉了。
image


该命令也可以修改其他提交信息,如 作者提交时间

git commit --amend --author "nemo<nemo@xxx.com>" --date "Thu Jun 30 16:59:36 2023 +0800"

如果需要修改提交时间为当前时间:

git commit --amend --date=now

https://qa.1r1g.com/sf/ask/637721731/

修改多个信息

--amend 虽然好用,但是它只能修改最后一次的提交信息,如果我们想要修改的提交记录在那之前,我们应该怎么办呢?

git 当中并没有提供直接的工具来实现这一点,不过我们可以使用 rebase 来达成。我们可以加上-i进行交互式地变基,我们可以在任何想要的修改完成之后停止,也可以添加文件或者是做其他想要做的事情。但是我们变基的目标不是某一个分支而是当前分支的某一个历史节点,所以我们需要提供一个具体的 commitid 或者是指针位置

git rebase -i 的功能非常强大,我们几乎可以使用它来完成所有一切我们想要完成的事情。

比如我们想要修改倒数第二次提交,我们可以执行 git rebase -i HEAD~3。也就是以倒数第三个节点作为基准节点执行变基,这时候git会进入一个vim窗口,在这个窗口当中我们可以看到最近的三次提交记录。
image

首先我们可以看到上面的三行就是我们可以修改的三个 commit,分别展示的是要执行的操作以及 commitid 以及 commit message。这里的操作默认的是 pick,也就是使用该 commit。关于我们可以执行的操作 git 在下方也给了充分的提示,其中比较常用的有 pick、edit 以及 squash。

这一次我们想要做的是修改提交记录,所以我们应该执行 edit,我们把想要修改的 commit 前的 pick 改成 edit。比如这样:
image

退出之后,git 会自动带我们回到我们选择edit的分支提交之后的版本。我们进行我们想要的修改,这里我在第15篇文章当中加上了一行:尝试 rebase。之后再使用 git add 以及 git commit --amend 进行修改提交结果。
image

再之后我们执行 git rebase --continue,把剩下要应用的变更应用完成。
image

一切都结束之后,我们可以使用一下 git show 命令查看一下我们修改的 bee9ce3 这个 commit 的记录。可以看到已经多了这一行,说明我们的修改成功了。
image

顺序变更、合并、拆分

1、顺序变更

我们不仅可以修改某一次 commit 当中的内容,还可以修改这些 commit 的相对顺序,以及可以让它们合并以及拆分。

修改顺序其实很简单,我们只需要人为地修改 rebase -i 之后弹出的 vim 文件即可。比如说原本的记录是:

pick A commitMessageA
pick B commitMessageB
pick C commitMessageC

如果我们想要更换顺序,我们只需要修改这个文件即可。比如变成:

pick B commitMessageB
pick A commitMessageA
pick C commitMessageC

那么当我们在退出 vim 的时候,git 会首先应用 B commit 的变更,再应用 A 最后应用 C。

2、合并

除此之外,我们还可以合并多个 commit 记录成一个。操作的方法也很简单,就是我们只需要把 pick 修改成 squash。git 会自动把所有 squash 的 commit 记录合并在一起。

pick A commitMessageA
squash B commitMessageB
squash C commitMessageC

3、拆分

有的时候一个 commit 非常巨大,我们可能也会想要将它拆分,其实操作也很简单。比如我们想要把 commit B 拆分成两条,首先,我们在 rebase 的时候将 commit B 前面的 pick 修改成 edit。

pick A commitMessageA
edit B commitMessageB
pick C commitMessageC

当我们退出的时候,我们会进入到 B commit 刚刚提交完的状态。由于我们要做的是拆分 B 这个提交,所以我们需要执行 git reset HEAD^,把上一次提交重置。然后再分别 add 我们想要拆分开来提交的文件。

整个操作如下:

git reset HEAD^
git add test/*
git ci -m 'add test'
git add code/*
git ci -m 'update code'
git rebase --continue

这样我们就把 commit B 拆分成了两个 commit 插入到了历史记录当中了。

最后的最后,大家需要注意,虽然这些手段在修改记录的时候非常好用。但是如果这些 commit 已经被提交到了远程,我们是不可以直接 git push 同步的。因为 git 会校验我们提交的 hash 值,发现对不上之后会禁止我们的提交。所以如果想要提交到远程的话,只能使用 git push -f 强制覆盖。但是这是一个非常非常危险的操作,如果你 git push -f 了,没有人会知道你到底修改了什么,只建议在自己独有的分支上如此操作,一定一定要谨慎使用。

git可视化操作——git GUI

我们介绍完了git的命令行操作,可能会有些朋友觉得这种操作实在是太麻烦了,所以接下来我们介绍一种git的可视化操作。
详情可以查看:git GUI可视化操作

posted @ 2021-01-14 17:02  Nemo&  阅读(1066)  评论(3编辑  收藏  举报