Git原理

本文最后更新于:2022年7月21日 下午

Git原理

[TOC]

仓库 (repository)

仓库是git中最大的单位,就是一份代码/项目所在的地方,在实际中,就是一个目录(category)。

仓库可以通过git init命令来创建,创建之后,会出现一个.git的隐藏目录,里面保存着Git所需要的文件。

仓库里的文件一开始是空的,即使实际中的目录有文件,我们需要手动将文件添加进仓库(的暂存区),使用git add <file>,可以一次添加多个文件,也可以使用通配符。

1
2
3
4
git add 1.txt
git add 1.txt 2.txt
git add *.txt
git add ./data/*

添加文件之后,就可以使用git commit命令进行提交,commit就是把暂存区(Stage)的改动一次性提交给仓库(实际上是分支branch),在提交的时候,可以附带一些信息(这点是强烈推荐)

1
git commit -m "message"

commit的消息也可以选择性地遵守一定的规则

feat: 新功能
change:需求变更
fix:缺陷修复
test:修改测试代码
docs:文档变更
style:代码格式调整
refactor:代码重构

查看本地文件改动

1
2
3
git diff  # 查看所有改动
git diff 文件名 # 查看单个文件的改动
git status # 查看整体情况

版本回退

Git最重要的特点就是能够版本回退,回退的单位是commit。要回退到某个commit,需要先知道对应的commit id,这个id是用SHA1算出来的随机字符串。

回退的时候使用的是git reset命令

1
2
git reset --hard HEAD^  # 回退到上一个版本,使用hard模式 HEAD表示当前 HEAD^表示上一个
git reset --hard 3s56fv # 跳转到某个commit id,id可以不用打全

要找到想要的commit id,可以用git log来看过去,假如已经在“过去”,可以用git reflog来看未来。要从乱七八糟的log中找到你要的commit,那就得看你的commit message有没有好好写了。

文件撤销修改(discard changes/rollback)

假如你不是想回退整个版本,而是改动一个文件后不想要改动了,那就可以使用git checkout命令。

1
2
3
git checkout -- <file>
# 1. 假如file还没有放进暂存区,即没git add,那么会撤销到和仓库里一样的状态
# 2. 假如file已经git add了,那么会撤销到add时的状态

假如想把一个已经放进暂存区的文件撤回到和仓库一样的状态,可以使用之前提到的git reset命令

1
git reset HEAD <file>  # 把文件的暂存区修改回退到工作区

假如要从仓库中删除一个文件,那么应该使用git rm命令,假如是误删除了一个文件,也可以使用和上面一样的git checkout命令来恢复。

添加远程库、推送

查看、删除、添加远程库

1
2
3
git remote -v  # 查看现有的远程库信息
git remote rm 远程库名字 # 删除一个远程库在本地的信息
git remote add origin git@github.com:用户名/库名.git # 添加一个叫做origin的远程库

之后把本地库内容推送到远程库上可以使用git push命令

1
git push origin master  # origin是远程库名 master是分支名

假如是想从远程库中下载一份到本地,使用git clone命令,链接在Github的repo主页都会写。

1
git clone <链接>

分支管理

每次commit,都会有一条时间线,这个时间线就是分支(branch)。默认的分支叫做master或者main,HEAD指向当前分支,下面是分支创建切换的基本操作:

1
2
3
4
5
6
git checkout -b newbranch  # 创建并让HEAD指向这个新分支
git branch newbranch # 创建新分支
git checkout newbranch # HEAD指向某个分支
git branch # 查看分支
git branch -d newbranch # 删除分支
# git还提供了新的切换分支命令 git switch

有多个分支时,可以合并分支:

1
git merge 分支名  # 将某个分支合并到当前HEAD指向的那个分支

对于分支管理,建议master分支用来发布版本,然后新建一个dev分支,所有人的改动都在dev分支上进行,有大版本更新时再合并到master。

来源:廖雪峰

拉取

git的拉取可以分为fetchpull两种,fetch将远程主机的最新内容拉取到本地,需要检查再判断是否合并到本机,而pull则是拉取下来直接合并,但是可能产生冲突。

1
2
3
git fetch origin <branch>  # 
git merge FETCH_HEAD # 把刚才fetch的合并
git pull origin <remote_branch>:<local_branch> # 直接pull

如图经常有这种冲突的情况,本地dev分支进行了改动,远程master分支也进行了改动。

git.drawio

此时可以采取解决方案1:把merge到master分支

git-Page-2.drawio

但是这样不太好看,于是可以采取解决方法2:rebase再push

git-Page-2.drawio (1)

假如有冲突的话,可以在pycharm里面的Update Project打开代码解决冲突的三视窗口,在这里可解决代码冲突。这个不是git的命令,是pycharm的。

藏匿stash

假如你在某个分支上工作到一半时,想切换到master分支去临时修复一个bug,可以通过git stash命令保存当前尚未commit的任务。

假如你没有stash就切换分支,会发现当前状态还是在dev改动后的状态

1
2
3
4
5
6
git stash  # 暂时藏匿工作
git stash list # 查看stash
git stash apply # 恢复stash
git stash drop # 删除stash
git stash pop # 恢复stash并删除
git stash apply stash@{0} # 恢复某个stash

pick一个commit

假如想复制别的分支的某个commit到当前分支,可以使用git cherry-pick命令,相当于在这个分支做了一次commit。

1
git cherry-pick <commit id>  # 将某个commit复制到当前分支

标签 Tag

使用git tag命令可以为当前commit打上一个标签,差不多就是给commit起个别名。

1
2
3
4
git tag <info>  # 给当前commit打标签
git tag <info> <commit id> # 给指定commit打标签
git tag -a <tagname> -m "message" # 给标签分成名字和内容
git tag # 查看标签

但是push的时候假如要加标签,需要一个额外的参数--tags

1
2
git push origin master --tags
git push origin <tagname> # 或者push特定的tag名

子模块 Submodule

Git - git-submodule Documentation (git-scm.com)
Git中submodule的使用 - 知乎 (zhihu.com)

子模块就是一个嵌入在一个仓库里的另一个仓库,子模块拥有自己的History,可以单独管理。

创建子模块使用如下命令,创建后会出现一个.gitmodules文件和子仓库的目录,此时一般单独commit一次表示加入了子模块。

1
git submodule add <url>  # 在本库中创建子模块

创建子模块之后,别人clone并不会拉取到子模块代码,可以使用如下命令

1
2
3
4
5
# 1. clone到底
git clone url --recurse-submodules
# 2. 或者,已经clone后更新子模块
git submodule init
git submodule update

假如在本库中修改了子模块:

需要进入子模块文件夹,在子模块单独使用各类git命令。之后再本库中使用git add把子模块的变化提交上去。(可以先git status查看要提交什么,并不是提交整个库)

假如子模块远端有版本变化:

需要进入子模块文件夹git pull,或者使用git submodule foreach 'git pull origin master'一键更新所有子模块。

假如要删除子模块:

1
2
3
4
git submodule deinit <模块目录>  # 卸载子模块
git submodule deinit <模块目录> --force # 卸载子模块,丢弃本地修改
git rm <模块目录> # 删除文件
git commit -m "" # 提交删除的操作