Git
本文最后更新于:2023年1月20日 下午
Git
Git 用于管理代码提交,是开发必不可少的环节,可以有效保证功能正向迭代,解决多人开发配合问题。
配置用户信息
Git是分布式版本控制系统,所以每台机器都必须提供 用户名称 和 Email 来分辨编辑者。如下这两条配置很重要,每次Git提交时都会引用这两条信息,说明是谁提交了更新,会随更新内容一起被永久纳入历史记录。
为当前仓库配置用户信息
git config --global user.name "Your Name"
git config --global user.email "email@example.com"
--global
表示你这台机器上所有的Git仓库都会使用这个配置
显示当前的Git配置
git config --list
配置信息存储于项目文件夹的
.gitconfig
中,如果是全局配置的话,那么就存储于~/.gitconfig
中。
创建版本库
我们使用 Git 进行版本管理,我们称被管理的目录为工作区。要对工作区的文件进行管理,必须在工作区中创建版本库。创建版本仓库,方法有二:
1.本地初始化
在当前目录初始化一个Git版本库,执行了这个命令的目录下就会生成.git
目录,这个.git
就是版本库
# 在当前目录下初始化版本库
git init
# 新建一个目录,将其初始化为Git版本库
git init <目录名称>
2.克隆远程仓库
如果不是从本地文件中新建git仓库,而是从远程拉取仓库,那么直接对远程仓库进行克隆即可。
git clone <远程仓库url>
# 克隆指定tag的仓库
git clone --branch <tag> <url>
Git的工作结构
Git中的文件分为两种状态:
- Untracked:不属于git管理的
- Tracked:被git管理的
我们可以使用git add <file>
让文件进入跟踪状态。
Git跟踪并管理的是修改,而非文件。这些修改有以下几种状态
- 已修改:某个文件修改了,但还没有告诉Git;
- 已暂存:文件的修改被放在一个提交清单中,但还没有保存到版本库
- 已提交:修改已经被保存在版本库中了。
因此 Git 在管理项目时,修改流转的三个工作区域是:
- 工作区(WorkSpace),就是被管理的项目目录。
- 暂存区(index or
stage),即
.git/index
,用于临时保存改动。 - 版本库(Repository),即
.git
目录,它是 Git 用来保存元数据和对象数据库的地方。
提交文件的修改
Git基本工作流
基本的 Git 工作流程如下:
- 初始化仓库(第一次使用时需要),在本地的工作目录修改某些文件;
- 然后对修改后的文件add到暂存区;
- 最后commit更新,将保存在暂存区域中的文件快照永久转存到 Git 的工作目录中。
- 如果有需要,可以push到远程仓库(如GitHub)
工作区 | 暂存区(.git/index) | 版本库(.git) | 远程仓库 |
---|---|---|---|
被 Git 管理的项目 | 临时存放被修改的文件 | 用于存放提交记录 | 远程代码仓库 |
git init | git add | git commit | git push |
git add:提交到暂存区
要使用 Git 添加文件,可以使用 git add
命令。该命令会将文件添加到 Git 的缓存区,以便将来提交。
# 作用:Add file contents to the index
# 将某些文件提交到暂存区
git add <file1> <file2>
# 将某些目录提交到暂存区
git add <folder1> <folder2>
<file>
: 添加指定的文件。<folder>
: 添加指定目录中所有新文件和已修改的文件。.
: 添加当前目录中所有新文件和已修改的文件。-A
或--all
: 添加所有当前目录中的文件,包括新文件、已修改的文件和已删除的文件。-u
或--update
: 添加所有已跟踪文件中已修改和已删除的文件,但不包括新文件。-i
或--interactive
: 交互模式,可以选择性的添加文件。-p
或--patch
: 交互模式,可以选择性的添加文件的部分修改。
为什么Git比其他版本控制系统设计得优秀?因为Git跟踪并管理的是修改,而非文件。
我们每次git add
添加的是一次修改,而不是一个文件,当我们执行如下流程:
- 第一次修改
- git add
- 第二次修改
- git commit
结果:这里提交的只是第一次修改的文件
git status:查看文件状态
想知道管理的文件处于哪个状态,就使用 status 查看
git status
若是 Untracked files 则表示当前文件没有添加到 Git 中(没有被 Git
管理),下一步使用git add
添加文件进暂存区。
git diff:查看差异
# 显示工作树与版本库的差异
git diff [file] [commit]
# 查看缓存区与版本库的差异
git diff --stage [file] [commit]
# 不指定commit的话默认为HEAD
上述例子中发现,有两行明明一样,为什么还是出现在
git diff
的结果中呢?这种大概率是空格造成的,可以尝试用git diff -w
查看去除空格差异之后的输出结果。
git commit:提交到版本库
将暂存区的修改提交至版本库,提交就是生成一次记录。
git commit -m "message"
执行提交时,系统会要求输入提交信息。请务必输入提交信息,因为在空白的状态下执行提交会失败的。
查看其他人提交的修改内容或自己的历史记录的时候,提交信息是需要用到的重要资料。所以请用心填写修改内容的提交信息,以方便别人理解。以下是Git的标准注解,请以这种格式填写提交信息。
- 第一行:提交修改内容的摘要
- 第二行:空行
- 第三行:修改的理由
查看提交信息
执行提交后,数据库中会生成上次提交的状态与当前状态的差异记录。
- 提交是以时间顺序排列状态被保存到数据库中的。
- 凭借该提交和最新的文件状态,就可以知道过去的修改记录以及内容。
- 系统会根据修改的内容计算出没有重复的40位英文及数字来给提交命名。指定这个命名,就可以在数据库中找到对应的提交。
git log
: 显示项目的提交历史记录,包括提交者、提交日期、提交注释和更改的文件列表。git show <commit>
: 显示特定提交的详细信息。git diff <commit1> <commit2>
: 显示两次提交之间的差异。git blame <file>
: 显示每一行代码的最后修改者和修改时间。git reflog
: 显示每次对项目的引用(例如分支和标签)的更改。git log -p <file>
: 显示某个文件在提交记录中的具体修改git log --stat
: 显示提交信息和每个文件的修改行数git log --pretty=oneline
: 显示简短的提交信息
git log
我们可以使用 git log
查看提交的历史纪录。如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline
参数
git log
commit 68c893c5d9f4825edc01be9b97e75f8daeb98e52 (HEAD -> master)
Author: agustletmen <agustletmeknow@qq.com>
Date: Tue Dec 13 10:41:35 2022 +0800
123
git log --pretty=oneline
68c893c5d9f4825edc01be9b97e75f8daeb98e52 (HEAD -> master) 123
git shortlog
git shortlog
命令会列出指定版本范围内的所有提交,并按照提交者分组显示。对于每个提交者,它会显示提交者的名字和提交次数,以及每个提交的简短信息。
git reflog
Git提供了一个命令git reflog
用来记录你的每一次命令,这与git commit
不同的是,commit只能记录提交,而reflog还记录了reset等操作。(当我们回退版本后,后悔了,想回到回退之前的版本,就得靠
git reflog 来查看版本id)
git reflog
be2d2d3 (HEAD -> master) HEAD@{0}: reset: moving to be2d2d3
4900f3b HEAD@{1}: reset: moving to 4900f3b
4900f3b HEAD@{2}: reset: moving to 4900f3
89759f7 HEAD@{3}: reset: moving to 89759f7
be2d2d3 (HEAD -> master) HEAD@{4}: reset: moving to head^
89759f7 HEAD@{5}: reset: moving to head
89759f7 HEAD@{6}: commit: delete
be2d2d3 (HEAD -> master) HEAD@{7}: commit: hello
4900f3b HEAD@{8}: commit (initial): hello
git show
git show
命令会显示指定提交的详细信息,包括提交者、提交时间、提交信息以及提交中包含的文件的变更信息。
例如,你可以使用 git show HEAD
命令来显示当前分支的最新提交信息,或者使用
git show <commit>
命令来显示指定提交的信息。
操作文件
git rm:删除文件
在Git中,删除也是一个修改操作。一般情况下,你通常直接在文件管理器中把没用的文件删了,此时Git知道你删除了文件,但工作区和版本库不一致了,所有我们需要用命令git rm <file>
从版本库中删除该文件。
# 将文件从暂存区和工作区中删除,一个git rm相当于————执行了rm 后又进行了git add
git rm <file>
# 若要反悔自己的`git add`操作,可以将文件从暂存区中删除
git rm --cached <file>
git rm
删除的文件必须是
没有经过修改的,也就是说必须要和当前版本库的内容一致的,如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项
-f。
git mv:移动/重命名文件
用于移动或重命名文件。git mv
命令将文件从
old-file
移动到
new-file
,并记录这个操作。它相当于在工作区中执行
mv old-file new-file
命令,然后使用
git add new-file
将新文件添加到暂存区,最后使用
git rm old-file
删除旧文件。
git mv <old-file> <new-file>
回退操作
git restore
restore 恢复
-W, --worktree restore the working tree (default)
-S, --staged restore the index
git restore <file> # 恢复工作区的内容
git restore -S <file> # 恢复暂存区的内容
git reset:移动HEAD
git reset 回退版本(版本穿梭) (itqaq.com)
git reset 命令用于回退版本,可以指定退回某一次提交的版本。
git reset [ --soft | --mixed | --hard ] [HEAD|commitID] <文件>
在Git中,用HEAD
表示当前版本,上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。
git reset
有三个参数:soft、mixed、hard;如果不加参数,那么默认使用
--mixed
参数。
reset参数 | 工作区 | 暂存区 | 版本库 |
---|---|---|---|
--soft | 保留 | 保留 | 重置 |
--mixed | 保留 | 重置 | 重置 |
--hard | 重置 | 重置 | 重置 |
--soft
指针移动的时候,暂存区,工作区都不动。
--mixed
指针移动的同时,重置暂存区,但是工作区不动。
--hard
指针移动的同时,重置暂存区和工作区。
远程仓库管理
远程仓库的管理主要使用git remote
,具体的使用请看下文
1.添加远程仓库
在不执行克隆操作时,如果想将一个远程仓库添加到本地的仓库中,可以执行
git remote add <仓库别名> <仓库地址>
1.关联一个远程库时必须给远程库指定一个别名,这个别名是在本地仓库中对远程仓库起的别名,只能在我们自己的本地仓库使用,在真正的远程仓库那边,远程仓库的名字是一个绝对唯一的URL。执行推送或者拉取的时候,如果省略了远程数据库的名称,则默认使用名为"origin"的远程数据库,因此一般都会把远程数据库命名为
origin
。2.仓库地址一般来讲支持 http/https/ssh/git 协议,其他协议地址请勿添加。
2.删除远程仓库
git remote remove <仓库别名>
git remote rm <仓库别名>
3.查看远程仓库具体信息
# Gives some information about the remote <name>.
git remote show <仓库别名>
4.查看当前仓库对应的远程仓库地址
git remote -v
git remote --verbose
这条命令能显示你当前仓库中已经添加了的仓库名和对应的仓库地址,通常来讲,会有两条一模一样的记录,分别是fetch和push,其中fetch是用来从远程同步 push是用来推送到远程
5.修改仓库对应的远程仓库地址
git remote set-url <仓库别名> <仓库地址>
6.修改仓库别名
一般来讲,默认情况下,在执行clone或者其他操作时,仓库名都是 origin 如果说我们想给他改改名字,比如我不喜欢origin这个名字,想改为 oschina 那么就要在仓库目录下执行命令:
git remote rename origin oschina
这样
你的远程仓库名字就改成了oschina,同样,以后推送时执行的命令就不再是
git push origin master
而是
git push oschina master
拉取也是一样的。
推送远程仓库
git push
关联远程仓库后,使用命令推送分支的所有内容到指定分支
git push <远程仓库名> <本地分支名>:<远程分支名> # 这里的远程仓库名是在本地仓库中对远程仓库起的别名
推送分支时可以进行绑定,绑定后的分支可以直接使用git push
,后面就不用添加参数了。
git push -u origin master
# 等价于 git push --set-upstream origin master
# 将本地分支与远程同名分支相关联
git push --set-upstream origin <本地分支名>
-u | --set-upstream
,使用这个参数后,Git不但会把本地的master分支内容推送到远程新的master分支,还会把本地的master分支和远程的master分支关联起来,此后,每次本地提交后,就可以使用命令git push
或git push origin master
推送最新修改。
git fetch
git pull
是直接把远程仓库的更新拉到工作区
git fetch
是把远程仓库的更新拉到本地版本库
git fetch <远程主机名>
git fetch <远程主机名> <远程分支名> # 拉取特定分支
拉取完后进行合并
git merge <本地分支> <远程主机名>/<远程分支名>
# 或
git merge FETCH_HEAD #执行后 .git 会多出来一个 FETCH_HEAD 文件,这个文件记录了远程 HEAD 的信息
git pull
(69 条消息) git pull 和 git fetch的区别? - 知乎 (zhihu.com)
git pull == git fetch + git merge
# 将远程指定分支拉取到当前分支上
git pull <远程主机名> <远程分支名>
# 将远程指定分支拉取到指定分支上
git pull <远程主机名> <远程分支名>:<本地分支名>
# 拉取全部分支
git pull <远程主机名>
Github
fork就是新建一个自己的分支,可以在上面改代码,然后提交。但是提交的内容在你自己的fork上面,要想合并到trunk需要提pull request。
github Pull Request合入全流程介绍 - 走看看 (zoukankan.com)
(1条消息) 如何pull request合作开发_FØund404的博客-CSDN博客_怎么同意pull request
分支操作
分支是生成副本,避免影响开发主线。常用的分支:
- master: 主分支,主要用来版本发布。
- develop:日常开发分支,该分支正常保存了开发的最新代码。
- feature:具体的功能开发分支,只与 develop 分支交互。
- release:分支可以认为是 master 分支的未测试版。
分支的原理
在版本回退里,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支,我们用HEAD
指向当前提交的节点。
HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
每次提交,master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长。
当我们创建新的分支,例如dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
Git创建一个分支很快,因为除了增加一个
dev
指针,改改HEAD
的指向,工作区的文件都没有任何变化。
从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变。以上就是分支的工作原理,下面让我们看看具体实操。
git branch
git branch # 显示所有本地分支
git branch -r # 列出所有远程分支
git branch -a # 列出所有本地分支和远程分支
# 改名字、移动
git branch -M <新名字> # 给当前分支改名字
git branch <分支名称> # 新建一个分支,但依然停留在当前分支
git branch --track <本地分支> <远程分支> # 新建一个分支,与指定的远程分支建立追踪关系
git branch -d --delete <分支名称> # 删除分支,分支合并后才允许被删除,我们也可以使用 `-D` 强制删除
# 删除远程
git push origin --delete <分支名>
git switch
git switch <分支名> #切换分支
合并分支
# 合并指定分支到当前分支
git merge <来源分支>
# 衍合指定分支到当前分支
git rebase <来源分支>
# 选择一个commit,合并进当前分支
git cherry-pick [commit]
分支映射
# 查看本地分支与远程分支的映射关系
git branch -vv
# 建立追踪关系,在现有分支与指定的远程分支之间
git branch --set-upstream <本地分支名称> <远程分支名称>
# 在推送时建立追踪关系
git push -u <远程仓库别名> <本地分支>:<远程分支>
# 撤销本地分支与远程分支的映射关系
git branch --unset-upstream
解决分支冲突
如图,master
分支和feature1
分支各自都分别有新的提交,变成了这样:
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,我们可以使用git merge 分支名
尝试,但这种合并就可能会有冲突:
git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容:
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
我们修改为自己需要的内容后保存,再add
&
commit
。现在,master
分支和feature1
分支变成了下图所示:
此时用带参数的git log --graph
也可以看到分支的合并情况:
git log --graph --pretty=oneline --abbrev-commit
* cf810e4 (HEAD -> master) conflict fixed
|\
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/
标签操作
“请把上周一的那个版本打包发布,commit号是6a5819e...”
“一串乱七八糟的数字不好找!”
如果换一个办法:
“请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。发布一个版本时,我们通常先在版本库中打一个tag。
commit id == ip地址
tag == 域名
创建标签
先切换到需要标签的分支上,再使用git tag <name>
就可以为当前分支创建一个新的分支了。
默认标签是打在最新提交的commit上的。如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?
方法是找到历史提交的commit id,然后打上就可以了。
git tag <tagName> <commitId>
还可以创建带有说明的标签,用-a
指定标签名,-m
指定说明文字
git tag -a v0.1 -m "version 0.1 released" 1094adb
可以用命令git tag
查看所有标签,需要注意的是,标签不是按时间顺序列出,而是按字母排序的。
和git show <commitId>
一样,可以用git show <tagname>
查看标签信息。
- 标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
- 标签是指向commit的死指针,分支是指向commit的活指针
把标签推送到远程
因为创建的标签都只存储在本地,不会自动推送到远程。
如果要推送某个标签到远程,可以使用命令git push <remote> <tagname>
或者一次性推送全部尚未推送到远程的本地标签git push <remote> --tags
删除标签
如果标签打错了,也可以删除
git tag -d <tagName>
如果标签已经推送到远程,要删除远程标签就麻烦一点。
先从本地删除git tag -d <tagName>
然后,从远程删除git push <remote> :refs/tags/<tagName>