Git

Git 是分布式的代码管理工具。

查看状态

使用 status 命令来查看当前的状态

  
$ git status

暂存区操作

暂存区(Cache)相当于对我们代码的暂时保存,我们每次修改后,都可以添加至暂存区,来保存我们的修改。

添加

可以使用 add 命令来将文件添加到暂存区:

  
# 将文件添加到暂存区
$ git add <文件>

放弃修改

添加到暂存区后,并不代表被正式提交。如果有意外情况,我们想要放弃暂存区的修改,可以使用 restore 命令:

  
# 将标记为 Modify 的文件回退到上一次的 Add 状态
$ git restore <文件>

移出

如果想要将添加为暂存区的文件取消跟踪,可以使用 rm --cached 命令:

  
# 将 Add 状态的文件,取消追踪状态,变为 Update 状态(即新建状态)
$ git rm --cached <文件>

# 将当前目录下的所有文件都取消追踪状态(必须全都是 Add 状态)
$ git rm --cached -r .

# 将当前目录下的所有文件(有 Modified 状态)强制取消追踪(不会丢失修改,但是不会追踪该文件了)
$ git rm --cached -r -f .

总结

  • 新建的文件,未被添加到暂存区,状态为 Update。
  • 使用 add 命令,将 Update 状态的文件,添加进暂存区,状态变为 Add。
  • 修改 Add 状态的文件,会引起 git 的检测,将状态标记为 Modified。
  • 如果觉得改错了,可以使用 restore 命令,放弃所有修改,将文件回退至上一次 Add 状态。
  • 如果想将 Add 状态的文件,变为 Update 状态,即移出暂存区,可以使用 rm --cached 命令,添加 -r 参数可以递归的移出,添加 -f 参数,可以强制移出 Modified 状态的文件,但不会导致 Modify 丢失。

本地库操作

本地库(Repository)是真正的版本管理,每次提交的代码,会作为一个版本记录下来,如果有意外,我们可以在版本间穿梭。

提交

使用 commit 命令来将所有暂存区的文件,提交到本地库中:

  
$ git commit -m '日志信息' [文件名]

提交到本地库后,文件会变为无状态(因为已经变成新版本了)。如果我们又修改了文件内容,那么文件会直接变为 Modified 状态。

查看记录

然后我们可以通过 logreflog 命令来查看提交记录:

  
# 只输出版本 log
$ git reflog

# 输出全部信息
$ git log

每次提交会有一个哈希值,来标记这次提交。方便我们回退版本。

版本穿梭

如果想要回到其他版本的代码,可以使用 reset 命令来进行穿梭。

  
# 穿梭回版本号对应的版本(同时清空本地的暂存区和工作树,但是如果你有自己新建但是没有 add 的文件,并不会给你删掉的),版本号可以通过 reflog 或 log 命令查看
$ git reset --hard <版本>

# 其实 reset 命令有很多参数
$ git reset [--soft | --mixed | --hard] <版本>

# --soft 和 hard 一样,可以回退版本,但是 soft 会保存当前的修改到暂存区,适合哪些不想放弃当前的修改的情况。
$ git reset --soft HEAD^

# --mixed 是默认值,会保留工作树,清空暂存区。简单说就是 soft,但是将你的修改放到工作树,这时你再 add 一次,就和 soft 一样了
$ git reset HEAD^

reset 其实本质是移动指针,可能会丢失你的新版本,但 reflog 依旧会记录原来的版本,所以还是可以穿梭回去的。

重做提交

使用 revert 命令,可以重做一次提交(根据指定版本建立一次新的 commit 来回滚版本):

  
# 将本地版本回退到某个版本
$ git revert <版本>

# 正常会帮你 commit,但如果你不希望 commit,可以加上 -n 参数
$ git revert -n <版本>

reset 命令的区别:

  • reset 是移动指针来回退版本,会导致指针后的版本全部丢失(但是可以从 reflog 找到对应的版本号,再 reset 过去)。
  • revert 是用新的 commit 来回滚版本,不会导致前面的版本丢失。

在开发中,我们使用 reset 回退本地版本后,需要使用 push -f 来强制推送到远程。而 revert 是建立一个新的 commit,可以直接 push 到远程。

分支

为了能够更细粒度的控制版本,一般我们会创建多个分支,通过分支的不同,我们可以让线上版本保持大版本,让开发分支保持频繁迭代。

查看分支

使用 branch 命令可以查看分支:

  
# 查看本地所有分支
$ git branch

# 查看本地所有分支(带版本号和提交信息)
$ git branch -v

# 查看所有分支(包括远程分支)
$ git branch -a

# 查看远程分支
$ git branch -r

创建分支

使用 branch 命令也可以创建分支:

  
$ git branch <分支>

切换分支

使用 checkout 命令来切换分支:

  
# 切换到指定分支
$ git checkout <分支>
# 和上面的命令一样
$ git switch <分支>

# 创建,并切换到该分支(需要注意你是从哪个分支创建的)
$ git checkout -b <分支> <基于哪个分> # 省略第二个分支名,就是从当前分支创建
# 上面这个命令比较常用,可以使用 git fetch 先将远程分支拉取,再通过以上命令新建分支
$ git fetch
$ git checkout -b dev origin/dev # 基于远程库dev分支,来新建一个本地dev分支

删除分支

使用 branch [-d | -D] 命令删除分支:

  
# 删除一个没有新提交记录的分支
$ git branch -d <分支>

# 删除一个分支,即删除的分支存在没有合并到当前分支的提交
$ git branch -D <分支>

合并分支

当我们开发好一个分支后,我们希望将这个分支合并到更高层次的分支去(比如 dev 分支合并到 main 分支):

  
# 首先我们需要切换到高层次分支(合并到的分支)
$ git switch <合并到的分>

# 然后我们合并那个低层次分支(一般是我们开发的分支)
$ git merge <开发好的分>

# 强制合并不相关的历史(一般是因为远程库创建了一个版本,本地库创建了一个版本,想要把他俩合并)
$ git merge --allow-unrelated-histories <远程拉取到本地的分>

如果合并时产生冲突,那我们需要解决冲突。冲突产生是因为有两个分支在同一位置产生了不同的修改,这时 Git 无法判断哪个需要保留,需要人工解决。一般是两个个人分支都进行了同一个位置的修改,第一个合并到高层次分支的不会冲突,但是第二个人想要合并的时候就会产生冲突。

一般来说,产生冲突后可以使用编译器在高层次分支中,直接选择保留哪个版本的代码,在解决冲突后,再 addcommit 一次即可。

冲突合并完成后,双方都应该再拉取一次高层次分支的代码,来确保冲突合并的代码更新到双方的分支上。

建立追踪

如果想手动建立追踪关系,可以使用 branch --set-upstream 命令:

  
# 将本地的main分支,和远程的next分支建立追踪关系
$ git branch --set-upstream main origin/next

恢复分支

如果我们希望创建一个指向指定版本的分支,可以使用 branch 命令:

  
$ git branch <分支> <版本>

重命名分支

  
$ git branch -m <分支> <新的分支>

暂存修改

如果我们在开发时,在工作区和暂存区有一些新的修改,但是我们急需切换到其他分支时,还不希望提交本次代码时,可以使用 stash 命令,将我们的工作区和暂存区储存起来。

  
# 存储工作区和暂存区,保存后,我们的当前分支就会clean
$ git stash

这样,由于工作区和暂存区被存储后干净了,我们可以直接切换到其他分支操作。等到想要将之前存储的修改取回时:

  
# 将存储的修改取回
$ git stash pop

远程仓库

在开发时,我们肯定需要一个远程库来在云端存储我们的代码。

创建远程库

在 GitHub 上创建即可。

添加到本地

我们有了远程库后,需要给本地库添加远程库,使用 remote 命令。

  
# 查看本地库记录的所有远程库
$ git remote -v

# 添加远程库
$ git remote add <> <远程库地>

推送

使用 push 命令将本地库推送到远程,这也是 git 中使用最多的命令之一。

  
# 推送本地库
$ git push <远程库别> <本地分支>:<远程分支>
# 例:
$ git push origin main:main # 将本地的main分支推送到远程origin仓库的main分支

# 删除远程分支
$ git push <远程库别> :<远程分支> # 推送一个空的分支到远程,即删除远程分支

# 如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略
$ git push origin

# 如果当前分支只有一个追踪分支,那么主机名也可以省略
$ git push

# 如果当前分支与多个远程库存在追踪关系,则可以使用-u选项指定一个默认主机,这样后面就可以不加任何参数了
$ git push [ -u | --set-upstream ] origin main # 将main分支推送到origin,同时指定origin为默认主机

# 强制推送。如果本地库版本比远程库旧,那么Git会要求现在本地pull合并差异,如果想要强行推送,可以使用-f参数
$ git push [ -f | --force ] origin

# 将本地所有分支,都推送到远程,远程没有的会创建
$ git push [ -a | --all ] origin

拉取

如果远程库的版本比本地更新,那么我们需要拉取远程库到本地:

  
# 拉取远程库
$ git pull <远程主机> <远程分支>:<本地分支>
# 例:
$ git pull origin next:main # 将远程库的next分支拉回,与本地main分支合并

# 一般来说,远程分支名和我们本地分支名是一样的,我们可以省略冒号
$ git pull origin main
# 等同于
$ git fetch origin
$ git merge origin/next

# 再一般来说,我们的分支是和远程分支建立追踪关系的(--set-upstream),这时我们可以省略分支名
$ git pull origin # 拉取远程库的当前分支,并合并

# 更一般来说,我们一个本地库可能只有一个远程库,这时我们还可以省略远程库名
$ git pull

pull 命令其实是 fetchmerge 的结合,而 fetch 命令:

  
# 使用fetch取回远程库更新
$ git fetch <远程主机> <分支> # 取回的更新,在本地主机上要用”远程主机名/分支名”的形式读取
# 例:
$ git fetch origin main # 本地使用origin/main读取

# 再取回远程分支后,我们可以使用 checkout -b 来基于远程分支创建本地分支
$ git checkout -b newBranch origin/main
# 或者使用merge合并到本地分支
$ git merge origin/master

克隆

如果我们希望完整的拉取一个项目到本地(不是更新我们的库),我们可以使用 clone 命令来直接把库拉到本地(而且 git 已经初始化)。

  
$ git clone <远程仓库地>

如果我们希望从 0 拉取一个项目,我们最好使用 clone,而不是直接下载 zip 文件,因为直接下载不会初始化 git。