推荐一个学习git的网站:Learn Git Branching

本文记录学习git时总结的一些常用操作,如有错误欢迎在评论区指正。下文图中的c0,c1,…cn实际使用中为提交记录的hash值。

分支操作

新建分支:branch和checkout

# 新建分支
git branch <bname>

# 切换分支
git checkout <bname>

# 新建并切换分支,实际上是上面两条命令的组合
git checkout -b <bname>

此外,自2.23版本开始,git添加了switch命令,用于切换分支:

# 切换分支
git switch <bname>

# 新建并切换分支
git switch -c <bname>

合并分支:merge和rebase

# 将bname分支的内容合并到当前分支
git merge <bname>

# 将当前分支以onto为起点,形成新的提交树
git rebase <onto>
# 将bname分支以onto为起点,形成新的提交树
git rebase <onto> <bname>

下面简要说明下两者间的区别。假设我们当前的开发情况如下,在c1结点创建了一个dev分支进行开发。在开发过程中,在main分支有人提交了代码。此时的提交记录如下图所示:

image-20211215153431421

那么,当我们开发完成后想将代码提交到main分支时,可以有如下两种方式:

  • merge:

    1. 切换到main分支:git checkout main
    2. 执行合并:git merge dev

    image-20211216082652882

    此时main分支就包含了dev分支上的所有内容。

  • rebase:

    dev分支上可以直接使用git rebase main,将代码提交到main分支上。

    image-20211216083242723

    可以看到,dev本来是以c1提交为基础开发的,而进行rebase后,git会把dev分支的代码,以main分支的最新代码作为起始点,重新生成新的提交。从结果上看,使用rebasemerge有更加简单清晰的提交记录。

撤销变更:reset和revert

在讲撤销变更之前,首先说明一下HEAD和相对引用。

HEAD一般情况下指向当前分支的最新提交(使用git checkout <hash>可以将HEAD指向特定提交)。

^~符号是相对引用,其中^表示上一级提交,有n个^则表示上n个提交;~后面更上数字,~n表示上n个提交。

  • git reset撤销本地的提交,默认仅回退提交记录,不会删除本地的修改;加上--hard参数会将本地修改全部删除,要谨慎使用。

    # 回退到上个版本
    git reset HEAD^
    # 回退到上上上个版本
    git reset HEAD~3
  • git revert 是用一个新的提交来声明之前的提交要被撤销。

    # 回退到上个版本,注意与reset不同,没有^
    git revert HEAD
    # 回退到上上版本
    git revert HEAD^

image-20211216093303924

修改提交树:cherry-pick和rebase -i

  • cherry-pick是将一个或几个记录应用到当前分支

    # 将指定记录转移到当前分支
    git cherry-pick <hash1> <hash2> ...

    # 指定分支的最新提交转移到当前分支
    git cherry-pick <bname>

    image-20211216105402633

  • rebase加上-i参数会打开交互式的提交界面,通过它可以调整提交记录、删除不要的提交、和并提交等。

    # 修改最近的两次提交
    git rebase -i HEAD~2

    image-20211216110255505

    一般是使用的vim编辑器,通过调整各个记录的顺序可以修改提交顺序,按照下方给出的提示,可以删除、编辑之前的提交记录。

常用操作

暂存修改:stash

如果已经有了修改,又需要切换到其他分支,可以使用stash暂存已经做出的修改,之后再进行恢复。

# 暂存修改
git stash
git stash save <comment>

# 查看
git stash list

# 查看修改的文件
# index可选,不加则默认第一个
git stash show [index] # index形式为stash@{num},即list输出的index

# 查看文件的修改内容
git stash show -p [index]

# 应用某个存储,但不会从list中删除
git stash apply [index]

# 应用并从list中删除某个存储
git stash pop [index]

# 丢弃某个存储
git stash drop [index]
# 删除所有的暂存
git stash clear

标签:tag

# 给指定commit打标签,不指定则默认为当前commit
git tag <tag> [hash]
# 生成带注释的标签
git tag -a <tag> [hash] -m <msg>

想要查询距离某个提交最近的tag,可以使用describe命令。默认情况下仅会查询带有注释的标签,如果想有查询所有标签则可以加上--tags参数。

# 查找距离指定提交最近的标签
git describe <commithash> [--tags]

如果指定的commit处正好有标签,则直接输出标签名,否则输出如下格式的字符串:

# tag-最近的标签名;num-距离该标签的提交数;hash-本次查询的commit的hash
<tag>-<num>-<hash>

远程操作

当使用clone命令从远端克隆一个仓库后,本地会有远程分支origin/master(具体名字由远程主机和分支名决定),它反映了远程仓库的状态。

拉取:pull

在讲pull之前,有必要先说明一下fetch命令,它会从远程仓库下载本地确实的记录,更新本地的远程分支(如origin/master),它不会更改本地的分支和文件。

默认情况下,git pull命令是fetch+merge的组合,也就可以将本地与远程的文件同步。如果添加--rebase参数,则使用rebase来代替默认的merge操作。

推送:push

# 基本格式为   remotename source:dest
git push [-u] <remote> <local_branch>:<remote_branch>

-u--set-upstream的缩写,当推送新的分支到远程仓库时,需要该参数建立与远程仓库的关联。

remote_branch不填,则默认与local_branch相同。

local_branch为空时,可以删除远程分支,如git push origin :dev则会删除远程仓库的dev分支,但本地的dev分支不会删除。

同理,使用git fetch origin :dev会在本地新建dev分支。

不建议使用以上两种删除和新建分支的方式,因为这会导致本地与远程的分支联系错乱,需要自己重新指定。

其他操作

合并多个提交为一个提交

当在开发过程中,针对同一问题进行了多次提交,如果不想保留历史提交记录而想将之前的多次提交合并为一次提交,则可以使用rebase -i方便地修改之前的提交记录。

假设之前进行了3次提交,如下所示:

image-20211231222005298

使用git rebase -i HEAD~2合并第2、3两次提交(rebase是将指定提交后面的提交进行修改,因此无法合并第1次的提交)。会进入如下界面:

image-20211231222607355

想将3号提交合并到2上,则将3号提交前面的pick修改为squash,最终结果为:

pick 7973919 2
squash 1b89bd1 3

将修改的文件保存后进入下一步的编辑提交信息的操作。

由于我安装git时指定了编辑器是vscode,所以这些编辑操作会在vscode中进行,此时修改完成后关闭标签页即可进入到下一步。如果是默认的vim编辑器,则需要使用wq进行保存后进入下一步。

编辑提交信息界面如下所示:

image-20211231223337713

可以使用#将之前的提交信息注释掉,然后添加本次的提交信息,保存即可完成此次合并提交。

image-20211231223521044