跳到主要内容

Git

基础命令

# 初始化git仓库
git init

# 查看提交记录
git log

# 查看历史命令
git reflog

# 查看当前状态
git status

git restore

# 工作目录内容添加进索引区域
git add <name>
git add .

# 当修改了工作区的文件但还未暂存更改(git add)时,可以通过该命令放弃更改
git restore <name>
git restore .

# 当修改了工作区的文件并且暂存更改时,可以通过该命令放弃暂存更改,变成尚未git add的状态
git restore --staged <name>
git restore --staged .

git commit

# 根据暂存区的内容生成新的commit
git commit
git commit -m 'feat: moyu'

# 可以用来重写当前的commit内容和消息,可以用来保证提交记录的简洁
git commit --amend
git commit --amend --reset-author

git branch

# 显示所有分支
git branch

# 显示所有远程分支
git branch -r

# 创建分支
git branch <name>

# 删除本地分支
git branch -D <name>

# 修改分支名
git branch -m <nameA> <nameB>

# 切换分支
git checkout <name>

# 切换到上一个分支
git checkout -

# 创建分支并切换过去
git checkout -b <name>


# 查看分支的track信息
git branch -vv

# 设置当前分支的上游分支,等价于--set-upstream
git branch -u origin/feature

git tag

# 查看所有tag
git tag

# 给当前commit打tag
git tag v1.0.1

# 给某个commit打tag
git tag v1.0.1 <commit>

# 删除tag
git tag --delete v1.0.1

git stash

# 暂存工作区的修改
git stash

# 查看已stash的内容
git stash list

# 释放最新stash的内容到工作区
git stash pop 0

# 清空暂存区的内容
git stash clear

分支合并

git存在两种分支合并的策略,即git mergegit rebase。本人强烈建议使用git rebase来进行分支管理,首先这种策略十分简单,而且能保证提交历史非常简洁。

git merge

# 合并目标分支
git merge <name>

使用git merge时,根据分支情况的不同会存在两种合并情况, fast-forward(快进)和non-fast-forward。多人协作的时候多数是non-fast-forward的情况,此时合并分支会创建一个新的提交。

fast-forward

fast-forward

fast-forward合并

non-fast-forward

分支合并

分支合并结果

git rebase

# 合并目标分支
git rebase <name>

git-rebase

git-rebase结果

冲突

git rebase时如果碰到代码冲突,在解决完冲突后通过git add .git rebase --continue可进入下一步;如果又不想要合并了,也可以通过git rebase --abort放弃合并。

远程操作

git remote # 查看远程仓库的信息
git remote add origin <url> # 添加仓库地址映射
git remote remove origin # 删除地址映射

git clone <url> # 拉取远程仓库到本地

大多数情况下,我们的本地仓库只需要和一个远程仓库进行关联,此时通常会使用特殊的标识origin来代表这个远程仓库。

在少数情况下本地仓库需要和多个远程仓库进行关联,一种常见的场景是我们想要给Github的某个开源库贡献代码,这时候我们会先fork并把fork后生成的仓库clone到本地,此时origin代表着自己的远程仓库,通常我们还会手动通过git remote add upstream <url>来关联原本的开源库用于更新最新代码,此时upstream代表原本的开源库。

Remote-Tracking Branch

远程仓库的分支通常被称为远程分支Remote Branch),通常可以在Github或Gitlab上进行查看。

与之相关的一个重要概念叫做远程跟踪分支Remote-Tracking Branch),通常以类似origin/master的形式表示,在执行如git fetchgit pushgit pull这样的远程操作后git会自动移动对应的远程跟踪分支,以确保它总是指向远程仓库对应的分支。

在使用git clone来生成本地仓库时,不仅默认会使用origin来表示远程仓库,还会自动给本地分支建立追踪关系,如master分支会自动追踪origin/master,此时也会把master分支称为Tracking Branch,把origin/master称为上游分支(Upstream branch)。事实上,当不指定分支信息直接执行git pushgit pull时,会根据当前分支的追踪关系来决定目标分支。

git branch -vv

使用git branch -vv可以查看所有的追踪信息

设置上游分支

# 希望feature分支跟踪origin/master分支的四种写法
git branch -u origin/master <branchname>
git branch -u origin/master # 不指定branchname时默认为当前分支

git branch --set-upstream-to=origin/master <branchname>
git branch --set-upstream-to=origin/master

git fetch

# 获取所有remote的远程提交和分支
git fetch

# 拉取origin remote的远程提交和分支
git fetch origin

git fetch会拉取目标remote的所有提交并更新本地的远程跟踪分支。因此可以直接git rebase最新的远程跟踪分支来获取最新的代码,比如:

git fetch && git rebase origin/master

git pull

本质上,git pull命令会使用给定的参数运行git fetch命令,并默认使用git merge策略来合并目标分支,因此git pull origin master等价于git fetch origin master && git merge origin/master

我们也可以调整git pull时采用的分支合并策略:

git config pull.rebase false # git merge(默认)
git config pull.rebase true # git rebase(个人推荐)
git config pull.ff only # git merge but fast-forward only

git push

git push用于推送指定分支到目标remote

git push origin main # 将本仓库的main分支推送到origin的main分支

git push origin test --delete # 删除远程分支

更进阶的用法,我们可以把本地的A分支推送到远程的B分支

git push origin branchA:branchB

版本回退

通过git reset --hard即可实现版本回退和前进,本质上是切换HEAD指针所指向的提交,HEAD^表示HEAD的上一个提交,HEAD~2表示HEAD的上两个提交。

# 回退到上一个commit
git reset --hard HEAD^
git reset --hard Head^^
git reset --hard Head~3

# 回退到上一个commit,但当前commit修改的内容并不会消失,而是保存在工作目录中
git reset head^

除了git reset,我们还可以使用git revert来撤销某次的提交,git revert会产生一个新的提交。

# 撤销当前commit
git revert HEAD

git rebase -i

git rebase -i origin/main 

通过交互式的git rebase可以实现提交的压缩、删除、顺序切换、编辑某个历史提交等功能。

git cherry-pick

git cherry-pick <commit>

底层原理

参考

commit节点通过 tree节点记录着某个时刻对应的文件信息,这些对应的文件保存在索引区域中(严格来说,这些文件被当作 object节点存于 git仓库中,索引指向着这些文件),当我们切换到某个 commit时,会根据索引把对应的文件同步到工作目录中。

通常当我们修改工作目录中的文件后,通过 git add把更新同步到索引中(即在 git仓库中创建一个新的 object结点,并更新索引的指向),然后使用 git commit来生成一个新的 commit节点(即先根据索引的指向生成一个 tree节点,再生成 commit节点),新的 commit节点记录着新的文件信息。

假设 commitAcommitB都记录着文件 test,那么当我们处于 commitA时并修改工作目录的文件后,或者已经同步到索引区域,只要还没有提交记录的话,这时候如果我们切换到 commitB,那我们的本地修改会同步到 commitB当中。

不过,如果我们在 commitA记录着文件 a,而 commitB中不存在文件 a,然后我们处于 commitA时修改文件 a,这时候直接切换到 commitB就会失败,并且会提醒我们应该先提交我们的修改,或者可以使用 git stash来把我们的修改暂存到 commitA中。

比如我们可以先使用 git stash暂存修改,然后就可以直接切换到 commitB了,未来回到 commitA时又可以使用 git stash pop把暂存的修改内容拿出来。

git object

# 显示所有object
ls .git/objects/
10/ ea/ 1c/ ... /info /pack

# 查看object类型/值,常见类型:blob(git add 后创建), tree和commit(git commit 后创建)
git cat-file -t 58c9
git cat-file -p 58c9 # 58c9为想找的object的值

其他

.gitkeep

git通常无法追踪空文件夹,如果我们追踪空文件夹,可以在该文件夹下新建一个空的 .gitkeep文件。

当我们新建文件 akara.txt,并使用 git add .添加进缓存区。此时我们可以:

  1. git commit -m '': 提交记录。
  2. git reset HEAD <file>: 释放缓存。类似的还有 git rm --cached <file>用来删除缓存区的内容

当我们新建文件 akara.txt,并使用 git add .添加进缓存区,之后修改该文件的内容。此时我们可以:

  1. git add .:缓存修改后的内容。
  2. git checkout -- <file> :丢弃修改的内容。本质是用原本缓存区的内容替代工作目录中的文件内容。