简介
github和git是每个程序员都必须掌握的技能,在这里对两个工具作入门介绍。
github
github是代码托管平台,付费用户可以建立私人仓库,免费用户只能使用公共仓库,也就是代码要公开,它可以帮助开发者存储和管理其项目源代码。
-
网址:
https://github.com/
-
中文社区:
https://www.githubs.cn/
,从这里进入github网站会快点
github和git的关系:github使用git作为版本管理工具。
简单使用
本来想截几张图的,但是github访问太慢了。
新建仓库
- 第一步:在浏览器中输入 github.com,进入github。因为是英文界面,所以需要有一定的英文基础。注册一个账号,需要提供自己的邮箱。
- 第二步:点击sign in,登录到自己的页面
- 第三步:点击左上角的New,新建一个仓库,建好后的仓库会在下方显示,点击就可进入。点击搜索框,可以搜索一些有用的项目
删除仓库
- 第一步:进入仓库内部,点击setting,翻到最下面,有删除按钮
创建分支
第一种方式:点击搜索框 - view all branch - create new branch,创建新的分支
第二种方式:也可以在本地创建分支,然后把本地分支提交到github上,提交命令:git push <远程库名称> <本地库分支>,如果远程库没有相同的分支,默认会创建一个
在github上邀请开发者
在仓库界面:settings - collaborators - add people,邀请开发者后,其它开发者也可以为当前项目提交代码
搜项目的技巧
这里介绍一些实际的使用案例:
- 搜索名称中包含sprint boot的项目:
in:name sprint boot
- 搜索收藏数量大于3000的项目:
stars:>3000
- 搜索分支数量大于100的项目 :
forks:>100
- 搜索readme中包含spring boot字眼的项目:
in:readme spring boot
- 搜索描述中包含spring boot字眼的项目:
in:description spring boot
- 搜索使用java语言实现的项目:
language:java
- 搜索最近的更新日期在2019年3月3日之后的项目:
pushed:>2019-09-03
fork和pull request
fork:复制目标仓库到自己的github账户下
pull request:合并其它人的提交,用户跨团队协作
gitee 码云
网址:www.gitee.com
一个面向国内用户的代码托管和协作平台,由于github在国内无法变得无法访问,可以使用gitee来代替,它是面向国内用户的,访问速度更快。
使用上,可以把它作为github的替代版,在这里,我使用它来作为远程仓库,学习本地仓库和远程仓库的交互。
git
简介
git是一个免费的、开源的分布式版本控制系统,诞生于2005年,起初是为了更好地管理Linux内核的开发而创立的。
版本控制
版本控制:对文件的修改做记录,相当于文件备份,用户可以在需要时将文件切换到以前的版本。版本控制是由个人开发过渡到团队协作过程中诞生出的产物,创建版本控制工具的目的就是为了方便多个开发者对同一个项目进行开发。
版本控制工具的需要提供的功能:
- 历史记录:将本地文件恢复到某一个历史状态
- 分支管理:允许团队在工作过程中多条生产线同时推进任务
- 权限控制:例如把开发分支的代码合并到主分支就需要权限控制,避免主分支混乱
- 协同修改:多人并行修改同一个文件,手动合并冲突或依据修改顺序自动合并冲突
分布式版本控制:每个人都工作在通过克隆建立的本地版本库中,每个人都用于一个完整的版本库,所有操作都直接在本地完成而不需要网络连接
版本控制工具的历史
最早是使用diff和patch两个命令,后来是CVS和SVN
diff、patch
diff:以行为单位比较多个文件之间的差异,也可以比较文件夹
- 格式:diff [option …] files
patch:使用patch命令,根据diff命令生成的差异文件,来恢复原始文件。这个命令使用比较复杂,而且如今用处不大,所以只是了解一下即可
CVS
Concurrent Versions System,版本控制工具,诞生于1985年,是早期的一款版本控制工具
SVN
subversion,诞生于2000年方一款版本控制工具
基本概念
代码库
代码库就是存储代码的地方,git的作用,就是管理代码库。
使用git,创建一个代码库,将自己的代码存放到代码库中,就可以对代码进行版本管理了。
代码库的组成
- 工作区:working tree,用户在这个区域中编辑文件
- 版本库:工作区下的 “.git” 目录
- 暂存区:index/stage,位于版本库下的index文件中,编辑后的文件手动添加到暂存区,暂存区也叫做索引
- 仓库:把暂存区域的文件手动提交到仓库,完成编辑。仓库是存储文件和修改记录的地方。git的数据库分为远程仓库和本地仓库。
- 本地仓库:为了方便用户个人使用,在自己的机器上配置的仓库。
- 远程仓库:配有专门的服务器,为了多人共享而建立的。
- master:git自动为用户创建的第一个分支。
暂存区的作用:暂存区是工作库和本地仓库之间的中间层,它可以追踪文件的修改,同时让用户选择性地提交工作区中的变更,例如,用户可以将工作区中的变更依据功能的不同分批提交到本地库。
对代码库进行版本管理的大致流程:
- 初始化代码库
- 工作区:在工作区编辑文件
- 暂存区:把文件保存到暂存区
- 本地库:把暂存区的文件提交到本地库
- 远程库:把本地库的文件提交到远程库
安装和配置
下载地址:http://git-scm.com/downloads
。针对不同的平台,选择自己需要安装包
安装:下载完成后傻瓜式安装即可
配置git:在命令行执行如下命令
git config --global user.name "用户名"
:告诉git自己的用户名,–global选项,表示这台机器上的所有git仓库都会使用这个配置。如果去掉 --global 参数只对当前仓库有效。git config --global user.email "邮箱名"
:告诉git自己的邮箱git config --list
:显示当前git的配置信息
git的配置文件:
- 用户级别:用户目录下的 .gitconfig文件
- 全局级别:安装目录下的 etc/gitconfig文件
- 仓库级别:.git目录下的config文件,在使用git创建一个本地库后,会默认在本地库根目录下创建一个.git目录
入门案例
在这个入门案例中演示如何使用git来做版本管理,主要通过命令行来操作git
-
新建文件夹 learn_git
-
初始化文件夹为代码库:打开终端控制工具,进入文件夹,执行命令
git init
,此时,文件夹中会创建一个名为 ‘.git’ 的子文件夹,不可以修改这个文件夹中的内容,git正是通过它来实现对代码的版本管理。 -
在工作区中编辑文件:工作区就是 learn_git文件夹下除了 .git 目录以外的地方,任意创建一个文件并编辑一些内容
-
将工作区中的文件添加到暂存区:
git add aa.txt
-
把暂存区的文件提交到本地库:
git commit -m '第一次提交'
-
查看提交日志:
git log
这里只演示最基本的使用,更加详细的操作随后学习
基本操作
要在代码库中使用git命令才有意义
查看帮助信息
git help:在命令行查看简单的帮助信息
git 子命令 --help:查看子命令的帮助信息
创建代码库
创建代码库:
- 初始化代码库:
git init命令
。在一个目录下执行git init命令,这个目录就会被设置为git的仓库,随后所有git命令的执行都要在代码库下执行才有意义。 - .git目录:执行命令后,目录下会出现一个".git"目录,它是git的版本库,所有git需要的数据和资源都存放在这个目录中。代码库本质上是有’.git’子目录的文件夹。
.git目录中的内容:
wuyaojun@wuyaojundeAir git-demo % ll .git
total 24
-rw-r--r-- 1 wuyaojun staff 21 6 29 15:41 HEAD
-rw-r--r-- 1 wuyaojun staff 137 6 29 15:41 config
-rw-r--r-- 1 wuyaojun staff 73 6 29 15:41 description
drwxr-xr-x 15 wuyaojun staff 480 6 29 15:41 hooks
drwxr-xr-x 3 wuyaojun staff 96 6 29 15:41 info
drwxr-xr-x 4 wuyaojun staff 128 6 29 15:41 objects
drwxr-xr-x 4 wuyaojun staff 128 6 29 15:41 refs
这里暂时不了解目录中的内容,只要知道,.git中就是暂存区、本地库和其它git配置即可,和.git平行的其它位置就是工作区。
代码库的基本操作
1、在工作区中编辑文件
工作区,就是和.git子目录平行的其它位置,在工作区中创建几个文件并且编辑一些样例数据。
2、把工作区中的文件添加到暂存区
使用add命令,把工作区中的文件添加到暂存区,格式:git add <file1 .... | dir1 dir2 | .>
,点代表所有文件
案例:git add .
,将所有文件添加到暂存区
3、把暂存区中的内容提交到本地库
使用commit命令来提交代码,格式:git commit [-a | -m <message>]
-m <message>
:message是一些备注信息-a
:修改文件后不需要执行git add命令,直接来提交。例如:git commit -am ‘mes’,直接将工作区的文件提交到仓库,但是不建议使用,因为git add命令是为了追踪文件,直接跳过会导致文件缺乏追踪--amend
:修改上一次提交的message,在命令行执行命令git commit --amend
,会进入一个文本编辑页面,在这个页面中,可以修改上一次提交的message,这个命令会修改上一次提交的commitId,谨慎使用
案例:git commit -m '初始化项目,编写demo文件'
提交完成后,如果再次编辑相同的文件,还需要再次执行git add命令,因为git不会自动跟踪工作区的变化
4、查看提交日志
git log
git log [--pretty=oneline | --oneline] [--stat] [file]
:展示当前分支的提交历史,仅展示可达的提交,不会展示被垃圾回收的提交,默认按提交时间倒序排序
--oneline
:每个提交记录只显示一行--pretty=oneline
:每个提交记录只显示一行- –stat :展示统计信息
- –graph:图形化地展示提交历史
案例:执行 git log 命令,下面是执行结果
commit 003cb1e42ebc2ff0be11c89c773df4660a2cda77 (HEAD -> main)
Author: wyj <xxxxxxxx@163.com>
Date: Sun Jun 29 15:56:33 2025 +0800
初始化项目,编写demo文件
案例2:git log --oneline,每个提交记录只展示一行
003cb1e (HEAD -> main) 初始化项目,编写demo文件
git reflog
git reflog
:展示所有的提交,包括已经被丢弃的提交,是git的安全网,数据默认保留90天
案例:git reflog
003cb1e (HEAD -> main) HEAD@{0}: commit (amend): 初始化项目,编写demo文件
7c4b41a HEAD@{1}: commit (amend): update
d9dd892 HEAD@{2}: commit (initial): 初始化项目,编写demo文件
这里共有三次提交记录,实际上最新的两次是使用 git commit --amend 来修改上一次提交的message,由此也可以看出,这种修改,会生成一个新的提交,同时丢弃掉之前的提交。
5、查看工作区和暂存区的当前状态
依据工作流程,代码中的文件状态枚举:
- 未跟踪:untracked,新创建的文件,git尚未根据,也就是文件没有被添加到暂存区
- 已修改:modified,已被git根据的文件发生了更改,更改尚未被暂存
- 已暂存:staged,使用git add将文件添加到暂存区
- 未修改/未提交:unmodified、committed,文件与最新提交一致,这种状态不会在git status中被输出
- 已删除:deleted,文件被从工作区中删除
- 合并分支时产生冲突:both modified,此时需要手动解决冲突,这个随后介绍
- 已忽略:ignored,在.gitignore中指定的文件或目录
查看状态的命令:git status [-s | -b]
- -s:输出简要信息
- -b:展示分支信息
案例:执行 git status 命令
输出内容讲解:
- 第一行:当前在哪个分支,这里是“main”,这是本地库的默认分支,通过git init方法创建的本地库,默认分支就是main
- 第二部分:“Change to be committed”,在暂存区中,尚未被提交的更改
- 第三部分:“Changes not staged for commit”,在工作区中,尚未被添加到暂存区中的更改
输出内容的格式不是固定不变的,如果状态不同,输出内容的格式也不同。
5、比较文件的前后差异
git diff
命令:git diff [--cached | HEAD] [file]
:默认比较工作区和暂存区之间的文件的差别
- –cached:比较暂存区和本地库之间的文件的差别
- HEAD:比较工作区和本地库之间的文件的差别
git diff不仅可以比较工作区、暂存区、本地库之间的差异,还可以比较文件之间、分支之间、两次提交之间的差异
案例:执行git diff命令
输出内容讲解:展示工作区和暂存区之间文件内容的变化,输出的内容以文件为单位,如果有多个文件,会分页输出
- 第一行:
diff --git a/HelloGit.java b/HelloGit.java
,表示当前比较的是哪个文件 - 第二行:
index 4bd8510..ad775af 100644
,索引,这里是文件的唯一标识,第一个索引是修改前的标识,第二个索引是修改后的表示,第三个是文件的权限描述符。如果修改前和修改后的文件标识没有变化,证明文件没变化,文件标识其实就是文件的哈希值。 - 第三行:展示差异范围,例如,
@@ -1,3 +1,3 @@
,表示旧文件起第一行到第三行、新文件起第一行到第三行 - 随后的内容展示文件的变化,字体的颜色:白色表示无变化,红色表示已删除,绿色表示已新增。如果终端不支持显示颜色的话,可以看每一行的开头,“-”表示删除,“+”表示新增
- 一个文件的变化展示完成之后会展示另一个文件。
案例2:git diff --cached 或 git diff --staged,比较暂存区和本地库之间的差距
案例3:git diff --stat,只展示统计信息,包含有变化的文件列表和新增删除的行数
git blame
命令:git blame <file>
:逐行查看文件的修改历史
案例:执行 git blame HelloGit.java
命令
^003cb1e (wyj 2025-06-29 15:56:33 +0800 1) public class HelloGit {
^003cb1e (wyj 2025-06-29 15:56:33 +0800 2) public static void main(String[] args) {
^003cb1e (wyj 2025-06-29 15:56:33 +0800 3) System.out.println("Hello Git");
0e916460 (wyj 2025-06-29 17:35:15 +0800 4)
0e916460 (wyj 2025-06-29 17:35:15 +0800 5) boolean b = false;
0e916460 (wyj 2025-06-29 17:35:15 +0800 6) System.out.println("b == " + b);
^003cb1e (wyj 2025-06-29 15:56:33 +0800 7) }
^003cb1e (wyj 2025-06-29 15:56:33 +0800 8) }
展示每一行是由哪次提交、哪个人、在什么时间做出的修改
6、查看提交详情
查看提交的全部信息:git show 提交id
,这会显示该次提交的全部信息,包括提交人、提交时间、commit message、文件改动
查看提交的统计信息:git show 提交id --stat
代码库的其它操作
上一节的内容展示了正常情况下,对于代码库的基本操作,这里继续介绍异常情况下的其它操作。
删除文件
只需要从工作区中删除文件,然后把这次变更提交到本地库即可,此外,git还提供了 git rm
命令
git rm命令的使用步骤:
- 使用rm命令删除工作区的文件,
- 使用
git rm <file>
命令删除本地库的文件, - 执行
git commit -m "msg"
命令,就可以把删除动作提交到本地库,从本地库中删除文件
重命名文件
类似的,重命名文件,然后把这次修改提交到本地库,此外,git还提供了 git mv
命令,使用格式 git mv 旧文件名 新文件名
忽略工作区中不需要进行版本管理的文件
在工作中,并不是所有的文件都需要保存到版本库中,例如java项目中编译后的字节码文件,通过.gitignore文件来指定不需要进行版本管理的文件。
在git工作区的根目录下,创建一个特殊的 .gitignore 文件,在这个文件中,每一行指定一个忽略规则,前面的规则会覆盖后面的规则
忽略规则的语法:
#
:注释使用 # 开头- / :以斜杠/开头表示目录
*
:以星号*
通配多个字符。pattern中有/
的时候*
不匹配/
,pattern中没有/
的时候*
可以匹配/
- ** :匹配完整路径,它不管什么斜杠不斜杠的,所有字符全部匹配
- ? :匹配单个字符
- [] :匹配方括号中的字符
- ! :取反,不忽略匹配到的文件
最好在初始化本地库之后就创建 .gitignore 文件并制定忽略规则,在2.x版的git中,如果文件没有被add,并且它符合随后创建的忽略规则,那么它会自动被忽略。
案例:
# 通用开发文件
*.log # 忽略所有目录下的log文件
/*.txt # 仅忽略根目录下的txt文件
# 依赖目录
target/
# 系统文件
.DS_Store
# 例外:不忽略 README.md
!README.md
分支操作
分支:可以理解为代码的不同版本,不同的人操作不同的分支,最后再合并分支。分支可以用于并行推进任务,而且一个任务的失败不会影响到另一个任务,可以增加效率。之前所有的操作都是在同一个分支下进行的,但是在实际开发中,开发者通常是在自己的分支下开发代码,开发完成后,把自己的代码提交到主分支中。
master分支:git自带的分支,默认的主分支
新建分支
git branch <分支名>
:如果没有任何参数,表示查看当前所有分支,哪个分支开头带 *,当前就在哪个分支
- <分支名>:创建分支
- -v :表示显示详细信息,包括分支名和分支的最新一次提交
- -r:查看远程分支
案例1:新建分支,执行命令 git branch 'feature/20250629-learn-branch'
,分支名通常会有命名规范,需要注意
案例2:查看分支,执行命令 git branch
git branch命令没有加任何参数,就是查看分支,并且星号在哪个分支,哪个就是当前分支
切换分支
git checkout <分支名>
切换到指定的分支
- -b:以当前分支为基础新建分支
合并其它分支的更改 【解冲突】
命令:git merge <分支名>
,在当前分支中合并指定分支的内容。如果修改有冲突,会合并失败。
什么情况下两个分支会产生冲突:如果一个文件被两个分支同时修改,并且一方的修改会影响到另一方,此时,就会产生冲突,需要用户手动决定如何解决冲突。
检测两个分支之间的差异:合并两个分支之前,推荐使用 git diff 分支1 分支2
命令,来查看两个分支之间不一致的地方,这个命令会展示出两个分支之间的差异,第二个分支相对于第一个分支做了哪些修改
判断两个分支之间有没有冲突:git merge --no-commit --no-ff 待合并分支名
,这个命令会对两个分支进行合并,但不会产生提交,使用它,可以判断两个分支之间有没有冲突
- –no-commit:不进行提交
- –no-off:禁用快进合并,确保总是进行三方合并
git合并分支时遇到的两种情况:
- 快进合并:fast-forward,例如,从master分支中开辟出某个分支,分支的代码进行了提交,但是master分支没有动,此时,master分支合并开辟出的分支,就是快进合并的情况
- 三方合并:three-way merge,例如,从master分支中开辟出某个分支,分支的代码进行了提交,master分支也提交了自己的修改,此时就有可能产生冲突。当两个分支产生冲突时,git会找到它们共同的祖先,以共同祖先为基准来比较两个分支之间的不同。
merge的三个参数:
- –ff:默认参数,使用fast forward的方式进行合并,如果待合并分支是当前分支的下游,直接进行合并,不会生成新的提交记录
- –no-ff:禁用快进合并
- –squash:不常用,把多个提交记录合并为一个,不推荐
案例:通过一个案例来演示冲突的产生和解决步骤
1、分支合并:git merge main,合并main分支到当前分支,此时有冲突。冲突的原因是在main分支和当前分支分别做了提交,并且修改的数据有冲突
日志中展示了冲突发生的位置。
2、查看冲突情况:git stats,它可以展示出哪些文件中有冲突
结果中表示 README.md 文件中有冲突。
3、打开有冲突的文件
文件内容解析:
<<<<<<< HEAD
到=======
之间是当前分支的代码。=======
到>>>>>>> main
之间是要合并的分支代码。
4、解冲突,编辑文件,删除特殊符号,把文件修改到满意的程度,然后执行 git add . 命令,把修改添加到暂存去,
5、解完冲突后,继续合并,git merge --continue
6、合并完成。
合并完成后,再次执行 git merge main
命令 ,提示 ‘Already up to date.’
如果合并过程中想要取消合并,执行 git merge --abort
命令
分支合并的注意事项
1、合并时指定选择哪方的修改,有时候会比较有用,但是大部分情况下不推荐,因为用户最好知道在解冲突过程中引入了哪些修改。
- git merge -X ours :在冲突时优先使用当前分支(我们的)的修改
- git merge -X theirs :在冲突时优先使用要合并分支(他们的)的修改
本地库和远程库的交互
之前所有的操作都是在本地库。但是在实际开发中,开发者从远程库获取代码,开发完成后,把代码上传到远程库,然后其他人再从远程库中拉取代码,获取最新的修改,这是实际开发中的流程,这里演示本地库和远程库的交互。
在这里,本地库是使用git维护的本地仓库,远程库是在gittee上创建的仓库。
创建远程库 & 关联本地库
1、在gitee上创建一个远程库:
名字取一个和本地库一样的名字。
2、为本地库关联远程库:git remote add <name> <url>
,
-
name:远程仓库的别名,通常叫 origin,后续可以用origin代替完整的仓库url
-
url:远程库的url,例如:
https://github.com/wuyaojun108/storage-code.git
,在github页面上可以找到。
远程库的url,点击页面上的 “克隆/下载” 就可以看到。
如果远程仓库是空的,本地的内容可以直接提交到远程仓库,如果远程仓库有内容,应该先把远程仓库的内容clone下来.
3、查看本地库管理的远程库地址:git remote -v
4、配置公钥,避免提交时还需要输入密码
在.ssh目录下找到自己的公钥,然后粘贴到指定位置。
克隆远程库到本地
命令:git clone <uri>
:它会完整的把远程库下载到本地、创建远程库别名、初始化本地库,在执行命令之前,不用特意创建一个文件夹,这个命令会自动创建文件夹。这是实际开发中常用的。
本地库的代码推送到远程库
推送命令:git push [<远程库别名>] [<本地库分支>]
,默认的远程库别名是当前关联的远程库,默认的分支是当前分支。
建立本地分支和远程分支的关联关系
如果本地分支没有关联的远程分支,推送是会报错,
依据报错中的提示信息,执行如下格式的命令,git branch --set-upstream-to=远程库名/远程分支名 本地分支名,建立本地分支和远程分支的关联关系,建立之后,无论是推送代码还是拉取代码,都无需再指定分支,默认是当前分支和它关了的远程分支
查看本地分支关联的远程分支
命令:git branch -vv
案例:git branch -vv
dev分支,星号表示它是当前分支,然后是当前分支的最新提交,它对应的远程分支,ahead 4表示当前分支有4个提交没有推送到远程,behind 1表示远程有1个提交没有合并到本地,最后是当前分支最新提交的commit message。
从远程库拉取代码
从远程库拉取代码,涉及到两个命令,git fetch、git pull
1、git fetch:仅下载远程变更,不自动合并。它会从远程仓库获取最新的提交历史、分支和标签,但不会自动合并到本地分支。
案例:git fetch origin,默认拉取远程仓库的所有更新,origin是远程仓库的别名
查看获取的变更:
- git log origin/main,查看远程main分支的提交
2、git pull:下载远程变更并自动合并,相当于git fetch 加 git merge 的组合操作。
案例:git pull [<远程仓库名>] [<远程分支名>]:[<本地分支名>]
:从远程库拉取代码,如果本地分支和远程分支是对应的,分支名可以不写。
标签操作
标签:git版本库的一个标记,指向某次提交的指针,是一个点,是不可移动的。标签主要用于发布版本的管理,一个版本发布之后,可以为git打上v1.0.1、v1.0.2 … 这样的标签
使用案例:tag和branch的相互配合使用,有时候起到非常方便的效果,例如:已经发布了 v1.0 v2.0 v3.0 三个版本,这个时候,突然想在不改现有代码的前提下,在 v2.0 的基础上加个新功能,作为 v4.0 发布,就可以检出 v2.0 的代码作为一个 branch ,然后作为开发分支。
操作标签的命令:
- 创建标签:
git tag <tagName> [-m <message>]
,例如 git tag v1.0,给当前分支打上v1.0的标签 - 列出所有标签:
git tag
:按字母排序,枚举当前分支上的所有标签。分支不同,tag不同 - 查看标签信息:
git show <tagName>
:显示标签的详情,包括tag所属分支、提交信息、tag创建者和创建时间等。如果标签打在了当前查看的分支,就会查到tag详情;否则,提示异常信息 - 推送标签:
git push <远程库名> <tagName>
- 删除标签:
git tag -d <tagName>
- 切换标签:
git checkout <tagName>
案例:
1、创建标签:git tag -a v1.0 -m ‘版本1’ ,创建标签并且指定备注信息
2、查看创建详情:git tag ,查看所有标签;git show v1.0,查看刚才创建的标签
3、推送所有本地标签到远程:git push origin --tags,origin是远程仓库的别名;
4、删除远程标签:git push origin --delete v1.0
5、切换标签:git checkout v1.0
标签主要是在上线的时候使用,当开发完成后,确认不再进行新的提交,上线前,为最后一个提交打上标签,表示当前提交代表一个稳定的功能,不会再变。
基本操作2
上一节介绍的操作,都是正向开发流程下,常见的操作,接下来介绍一些开发过程中的异常操作,例如版本回退、修改隐藏等
版本回退
版本回退主要涉及到 git reset 、git revert 这两个命令,git reset 是直接回退版本,如果是版本已经被推送到了远程仓库,那么本地库的回退就需要强制推送到远程仓库,这会修改远程仓库的提交历史,需要谨慎使用。git revert是在原来提交的基础上进行相反的修改,然后重新提交,相对更加适合团队合作时的开发。
git reset
命令:git reset <--soft | --mixed | --hard> commitId
:使用reset命令来执行版本的前进和后退。reset的含义是重新设置,设置HEAD指针到指定的提交,reset命令有三个不同的选项:
- –mixed:默认参数,移动head指针,不会改变工作区的内容,但会改变暂存区的内容。移动完成后,修改的内容会变成未暂存状态,需要执行git add命令。
- –soft:仅仅移动当前head指针,不会改变工作区和暂存区的内容。修改的内容会变成未提交状态,需要执行git commit命令
- –hard:当前head指针、工作区、暂存区的内容全部改变,已提交的内容会丢失。
指定回退到哪个版本的三种方式:
- 基于索引值来前进或后退版本:
- 第一步:执行命令:git reflog,查看每个版本的局部索引值
- 第二步:执行命令:git reset <局部索引值>,把当前指针移动到索引值代表的版本,可以前进,也可以后退。
- 使用异或符执行版本后退:git reset --hard HEAD^^^,只可以后退,有几个异或符,表示后退几步
- 使用 ~(波浪线)的形式执行版本后退:git reset --hard HEAD~3,只可以后退,数字是几,表示后退几步
使用git reset命令回退版本后,如果被回退的版本已经推送到远程分支,那么此时无法再正常向远程分支推送内容,所以就需要强制推送,这会修改远程分支的提交历史,需要谨慎使用,使用前最好和同事沟通。
使用案例:
1、查看当前简要的提交历史: git log --oneline,决定要回退到什么位置
2、强制回退:git reset --hard 46935d8,回退到 46935d8 这个提交处,丢失它之后的所有更改
3、强制推送远程仓库:git push -f
这个案例演示了版本回退的一种方式,如果不小心把错误的代码推送到了远程分支,并且代码太多,需要通过回退分支来修改错误,可以使用这种方式,但这是最不推荐的方式,推荐使用接下来的git revert命令
git revert
命令:git revert 提交id
,撤销指定提交的更改,生成一个包含相反内容的提交,需要注意的是,revert命令针对的是单次提交,如果该次提交之后的提交修改了相同区域的内容,revert时就会产生冲突,需要手动解冲突。
案例:
1、先查看某次提交做的修改 git show df14f10
这次提交将 age 字段改为 age_main,而且我这里没有展示出来,随后的提交中又再次修改了这次提交,所以revert这次提交时会产生冲突,需要手动解冲突。
2、回退指定提交 git revert df14f10
从日志中看,产生了冲突,
3、查看冲突详情 git diff
HEAD 到 ====
是当前文件内容, ====
到 >>>>
与指定提交(df14f10)相反的内容
4、查看工作区中的状态
工作区中有未添加到暂存区中的更改。
5、解冲突,然后执行git add .
这是解完冲突之后的文件内容,这里可以随便写一点内容。
6、提交修改 git revert --continue
在这里可以编辑commit message
如果想要终止revert,执行命令 git revert --abort
7、查看日志,可以看到,git revert命令生成了新的提交,然后将新提交推送到远程仓库。
完成。
隐藏本地修改
在实际开发中,如果用户本地做了一些修改,但是还没有形成一个可以完整提交的内容,但是此时需要切换分支、或者拉取远程仓库的代码,此时,用户可以隐藏本地修改,执行完其它操作后,再恢复修改。
命令:git stash,将本地没有提交的内容进行缓存并从当前分支移除,缓存的数据结构为栈,先进后出。
git stash命令的使用:
- 创建隐藏:git stash [ save ‘xxx’ ]:加上自己的注解进行缓存
- 查看隐藏列表:git stash list
- 弹出最新的隐藏并从栈中删除它:git stash pop
- 弹出最新的隐藏但是不会从栈中删除它:git stash apply
丢弃未提交的修改
方式一:git checkout HEAD -- .
,如果修改没有提交,并且不再需要它了,可以使用这个命令丢弃,注意,没有被git追踪的文件是无法被回退的,也就是没有添加到暂存区的文件。checkout命令用于切换分支或恢复文件到某个状态,HEAD指向当前分支的最新提交,--
的作用是分隔符,避免歧义,点代表所有文件
方式二:将文件从暂存区移出:git restore --staged <file>
方式三:丢弃工作区的修改:git restore <file>
,注意,如果没有没有被git追踪,这个命令和方式一都无法回退这个文件
基本操3
之前的操作已经可以进行日常开发了,这里介绍一些更加复杂的操作,例如,整理提交历史、回退merge操作等。
变基操作
举一个实际的案例,用户从main分支切出一个新的feature分支,在feature分支进行开发,做了几次提交,但是,此时main分支有了新的提交,用户想要把main分支的提交合并到feature分支,此时,通常的做法是使用merge命令,合并main分支的提交,这在之前介绍过。变基操作和merge命令可以实现相同的效果,并且它可以创建一个更加简洁的提交历史,它的原理是,把当前feature分支上最新的提交摘出来,应用到main分支上,然后在feature分支上创建一系列新的提交,这些提交就好像是从main分支最新的提交上进行的一样。注意,这个过程中,main分支是不变的,变的是当前的feature分支。
看一下官方的概念,变基一种将一系列提交从一个分支上摘下来,然后再应用到另一个分支上的机制。这个操作常用于清理提交历史,使得项目的历史变得更加线性和易于理解。
变基会改变提交的哈希值,因为它实际上是创建了一个新的提交序列。这意味着变基是一种“破坏性”操作,应该谨慎使用。
注意事项:
- 使用变基操作时需要考虑到团队协作的影响。在团队中使用变基之前,最好与团队成员沟通,确保大家对变基的操作和影响有共同的理解。
- 不要在已经推送到远程仓库的分支上进行变基:如果已经将分支推送到了远程仓库,然后对分支进行了变基并再次推送,这可能会给其他协作者带来麻烦,因为他们需要处理非快进更新。
案例:feature分支从主分支切出来后,有两个提交,主分支随后也有两个新的提交,这些提交之间有冲突,这个案例演示了这种情况下的编辑操作
1、main分支的修改:这个案例中只需要关注最新的两个修改
2、feature分支的修改:同样只需要关注最新的两个修改,
3、执行变基操作:
4、提示有冲突
- 使用 git status 查看冲突,
- 修改有冲突的地方,
- 把修改后的内容添加到暂存区 git add .
- 继续变基:git rebase --continue
5、变基后的提交历史:
提交历史中,main分支的两个提交被合并到了feature分支,feature分支的两个提交在main分支的基础上重新进行了提交,它们的commitId变了,并且这个过程中没有Merge类型的提交,这就是变基操作相较于merge操作的不同。
案例2:变基操作的回退
1、查看当前分支的提交历史,使用reflog命令
2、回退到rebase finish之前,也就是它的上一个提交:git reset --hard 1bfef78
案例3:和案例1同样的情况,merge操作是怎么做的?
1、开始merge,发生冲突
2、查看冲突详情
3、解决完冲突之后 git add .
将修改添加到暂存区
4、继续merge:git merge --continue
,进入如下页面,确认没有问题,退出编辑器即可。
5、完成,查看合并结果
图形化显示提交关系:
可以看到,merge相较于rebase的不同在于,rebase可以生成线性的提交历史,没有merge提交,让提交历史更干净。
交互式变基 【整理提交历史】
命令:git rebase -i commitId
,通常用于整理提交历史,例如,将多个提交合并在一起,或者修改commit message
案例:以当前分支的某个提交为基础,修改它之后的提交历史,git rebase -i f218688
pick ddfd739 f2_dev_dev: 新增c2.txt
pick 8696413 f1: 新增c1.txt
pick 380d56c Revert "Merge branch 'dev' into feature/20250727-l-merge"
pick 992d432 f2_dev: 新增c2.txt
pick 74547ab f1: 新增c1.txt
pick be786a9 Revert "Merge branch 'dev' into feature/20250727-l-merge"
# Rebase f218688..33e69c9 onto f218688 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# create a merge commit using the original merge commit's
# message (or the oneline, if no original merge commit was
# specified); use -c <commit> to reword the commit message
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
# to this position in the new commits. The <ref> is
# updated at the end of the rebase
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
执行命令后会弹出上面的界面,展示用户可以编辑的提交,例如,默认的pick,表示直接使用,把某个提交前的pick换成r,表示编辑该提交的commit message,把pick换成s,表示把当前提交和前一个提交合并。用户依据这些规则,变基完成后,退出当前界面,git会自动进入下一个界面。
我使用这个命令编辑过提交历史,主要是发现之前的某个commit message中有错别字,想要修改一下,但是rebase操作会修改commitId,是破坏性的,如果分支是共用的,尽量不要用这种方式。
择优挑选 cherry-pick
命令:git cherry-pick [commit] : 选择某个提交,并且把它合并到当前分支,它会创建一个新的提交。
- 选项:
- -n:应用更改但不自动提交,允许用户手动提交
和merge命令类型,它可以在当前分支合并其它分支的修改,不同之处在于,它可以选择某些指定的提交,而避开其它提交,某些情况下会特别有用,尤其是处理异常情况的时候,例如,开发代码的时候,你发现你的代码不小心写在其它分支上了,并且已经产生了提交,但是还没有推送到远程仓库,此时,你可以在当前分支回退提交,然后,在正确的分支,使用cherry-pick,把你的提交合并到正确的分支。
案例:git cherry-pick d573d0a ,把指定提交合并到当前分支,支持同时指定多个提交,合并完成后,会产生新的提交
回退merge操作
在合并了其它分支的代码之后,如果想要回退本次合并,该如何操作?
共分为三种情况:
- 情况一:合并了一半,不想要该分支的代码了,直接git merge --abort,终止合并
- 情况二:合并完成了,但是还没有推送到远程仓库
- 情况三:合并完成了,并且已经推送到了远程仓库
解决方案:
方式一:
- 第一步:本地回退
git reset --merge HEAD~1
,执行这个命令时,最新的提交最好是一个merge类型的提交,否则达不到回退merge操作的效果,而是会像普通reset --hard
一样。 - 第二步:强制推送远程仓库。如果合并之后不小心把合并操作提交到远程仓库,回退之后,还需要执行
git push -f
,强制推送,把回退操作强制推送到远程仓库。这是一个破坏性的操作,需要先和同分支的其它同事打个招呼。
方式二:回退到当前分支的上一个提交,这里要注意,必须是当前分支的上一个提交,找到提交之后执行命令 git reset --hard <commitId>
案例:当前分支的上一个提交,因为merge后会引入其它分支的提交,如果当前分支上一次提交的时间在其它分支最后一次提交之前,merge后,其它分支的最后一次提交看起来就是在当前分支的上一次提交,千万不要弄混。
取消路径图之后再看:merge操作在第一行,当前分支的最后一次提交在第四行
方式三:使用git revert命令,git revert -m 1 <commitId>
,参数 -m 1 表示保留当前分支的修改,因为merge类型的提交有两个父提交,git需要知道该回退哪个父提交的操作,1表示第一个父分支,也就是当前分支,2表示第二个父分支,参数 commitId 是merge提交对应的提交id。
回退merge操作时,git reset命令和git revert命令的比较:
- 提交历史是否干净:git reset命令可以保证提交历史是干净的,git revert则不可以,因为git reset直接回退了merge提交,而git revert命令是在merge提交的基础上生成一个相反的提交,所以使用git reset来处理,可以保证提交历史是干净的。
- 是否需要强制推送远程仓库:git reset需要,git revert不需要
实际上git revert操作不适合回退merge操作,它有一个隐患:它会删除另一个分支的提交。例如,用户在当前feat分支开发,开发完成后把代码部署到dev分支测试,但是用户不小心把dev分支的代码合并到了当前分支,也就是把其他人的代码合并到了自己分支,此时,使用git revert操作回退这次合并,然后,用户再把自己的代码合并到dev分支时,会发现无意间删除了别人的代码,因为使用git revert在自己的分支回退了其他人的代码,合并到dev时,git经过三方比较,默认用户的修改是最新的,就会删除dev分支上其他人提交的代码。需要注意这一点。
查看日志
很多时候,用户需要分析提交日志,来排查问题,这里介绍几个常见的操作
查看某个分支最初的提交
查看某个分支最初的提交: git merge-base HEAD master ,这个命令可以获取当前分支和master分支的公共分叉点,这个公共分叉点通常就是当前分支的最初提交,如果当前分支不是从master分支切出来的,就另当别论。
图形化地显示提交关系
图形化地显示提交关系: git log --graph --oneline --decorate
案例:展示并行开发的过程中分支切换的过程
、
图形化地显示提交关系2
上面展示的提交内容过于简单了,例如,每个提交的提交时间、提交人、分支信息,就没有展示出来。
这里介绍一条命令,可以按照用户指定的格式来展示提交信息。
配置别名:git config --global alias.lgg “log --graph --pretty=format:‘%Cred%h%Creset %C(cyan)%ad%Creset %C(green)%an%Creset %C(yellow)%d%Creset %s’ --date=format:‘%Y-%m-%d %H:%M:%S’”,这个别名支持图形化地查看提交历史,并且不同内容会被渲染为不同颜色,
参数介绍:
- %h:缩写的 commit hash (前7位)
- %ad:作者日期 (–date=short 指定为短格式)
- %an:作者名字
- %d:引用名称 (分支/tag)
- %s:提交信息主题
删除别名:git config --global --unset alias.别名
效果展示:执行命令 git lgg ,因为在上面配置了别名,所以就不用敲复杂的命令了
查看某个文件中某一行代码的修改记录
方式一:git blame 文件名,查看这个文件最新的修改记录
案例:git blame Person.java,查看一个文件现在的内容是由谁提交的
方式二:git log -L 起始行,结束行:文件名,查看指定行好内的提交记录
案例:git log -L1,8:Person.java,它会展示出所有有修改指定内容的提交
根据内容来模糊查找是谁做的提交
命令:git log -S"内容" --patch 文件名,只可以查看指定文件里的内容。
内容比较
比较第二个分支相对于第一个分支做的修改
命令:gigit diff 分支1…分支2
克隆仓库后的操作
因为克隆仓库后,只有主分支,如果需要直接在已有分支上进行开发,需要执行一些命令
1、查看所有分支,包括远程分支:命令:git branch -a,新拉取项目后,使用这个命令来查看所有分支,因为此时本地只有master分支
案例:执行命令 git branch -a
2、查看当前分支对应的远程分支:git branch -vv,这个命令会展示当前分支的最新提交和它对应的远程分支
3、直接创建本地分支并关联远程分支:git checkout -b <本地分支名> origin/<远程分支名>
,这样就可以在已有的远程分支进行开发了。
通常是不需要这么做的,用户应该建立自己的开发分支,不过有些时候,需要和其他人一起开发某个功能,就需要这个操作。
深入学习
这里详细介绍一些git中的概念,只有经过深入使用之后,才接触到并理解这些概念。
提交:用户编写完一个功能点后,向git提交一次代码,git以提交为单位来跟踪文件,使用git来复原历史内容,也是把内容复原到某次提交。
提交id:每次提交代码,都会生成一个commitId,它是git根据修改的内容,采用消息摘要算法,生成的40位字符。因为git是分布式的,要保证每个提交id都是唯一的,本地指令中一般只使用前六位。
分支:一个指向提交的指针,当用户进行新的提交时,分支会自动更新,指向新的提交。提交之间存在引用,每个提交会指向其上一个提交。
- 创建分支:当创建新分支时,git只是创建了一个新指针指向当前提交
- 切换分支:切换分支就是移动 HEAD 指针指向不同的分支指针
HEAD:指向当前活动分支的最新提交
除了初始提交,每个提交都至少有一个父提交,指向创建当前提交时所基于的提交,普通提交有1个父提交,合并提交有2个或多个父提交。
在使用git时,我碰到一个问题,我想查看某个提交最初是在哪个分支上进行的,但是我发现做不到,最多只能找到包含该提交的分支,然后进一步判断。因为分支只是指向某个提交的指针,所以无法判断一个提交最初是在哪个分支上进行的。
操作案例
查看某个提交的父提交:git show --pretty=format:"%P" -s <commitId>
- %p 显示父提交的简短哈希
查看包含某个提交的所有分支:git branch --contains <commitId>
使用规范
这是企业级开发中通常会使用的规范,在这里做一个简单介绍
git flow
git 工作流,git分支管理模型,特别适合有固定发布周期的项目
git flow定义了5种主要的分支类型:
- master:当前线上环境使用的分支
- feature/xxx:功能开发分支,用户在这个开发开发代码
- develop:功能集成分支,开发完成的代码合并到develop进行测试,这不是必须的,取决于项目组
- release/xxx:发布准备分支,开发完成的代码合并到release分支进行发布
- hotfix/xxx:紧急修复分支,如果发现线上有bug,创建hotfix分支来编写修复bug的代码
提交规范
推荐使用如下提交格式:
<类型>(作用域):<主题>
<正文>
<页脚>
案例:
feat(user): 用户列表分页功能
- 按照用户名称模糊查询
- 按照创建时间倒序排序
prd 需求文档
提交类型:
- feat:新增功能
- fix:修复bug
- refactor:代码重构
- test:测试用例相关
- pref:性能优化
- docs:文档更新
- style:代码格式调整
作用域:说明本次提交的影响范围,例如,用户模块、订单模块
主题:一句话简要描述做了什么,必选,正文、页脚不是必选的,正式是对提交内容的详细描述,页面,通常是当前功能关联的需求文档之类的。
最佳实践:
- 原子性提交:每个提交只做一件事件,例如,如果当前用户手上有多个细小的功能点,用户认为自己可以在一个提交内做完,但是良好的习惯是,每个功能点对应一个提交,多个提交统一推送到远程分支,这样,如果发现某个功能点需要修改,可以找到它对应的提交,这样即方便查看代码,也方便使用git命令来回退修改。
- 先拉取再提交
- 验证后再提交:本地编译通过或确认项目可以启动后再提交
Pull Request
PR,git和github等代码托管平台的一种协作机制,主要用于代码审查,它允许开发者将自己修改的代码提交给项目的维护者,供其审查,并决定是否合并到主分支,通过代码审查,团队可以确保代码质量,减少错误。
用户克隆项目后,在自己的分支进行开发,开发完成后,就可以在代码托管平台创建PR,创建过程中,要指明自己做了哪些修改,方便维护者审查代码。
案例:这里以在gitee上创建的PR为例
使用经验
切换分支时报错
切换分支:git checkout -b <本地分支名> origin/<远程分支名>
报错:fatal:‘xxx’ is not a commit and a branch ‘xxx’ cannot be created from it
原因:因为远程新建的分支没有更新到本地,用checkout命令是从本地仓库找分支的,本地仓库只有在进行网络请求时才会跟远程仓库交互,应该先执行git fatch命令
拉取远程库时报错
报错信息:You have divergent branches and need to specify how to reconcile them.
解决方法:在本地执行git命令 git config pull.rebase false,它用来配置pull操作的默认行为,执行这个命令后,git会将合并作为拉取远程分支更新时的默认策略,而不是变基(rebase)。从 Git 2.27 版本开始,Git引入了一个新特性,要求用户明确指定在拉取操作中如何处理已经偏离的分支。这意味着如果用户没有明确配置 pull.rebase 的值,Git 会显示警告信息,并要求用户设置一个默认行为。这样做的目的是为了避免在没有明确意图的情况下不小心覆盖掉分支上的提交历史。在 Git 的早期版本中,当用户执行 git pull 命令时,如果没有指定特定的策略,Git 默认会尝试快进合并(fast-forward merge)。如果快进合并不适用,Git 会执行一个普通的三方合并,创建一个新的合并提交。
推送代码时报错
报错内容:fatal,The current branch xxx has no upstream branch,当前分支没有关联的远程分支
报错信息中会给出解决方案,直接执行报错信息中提供的命令即可
在IDEA中使用git
作为一个java程序员,通常是在idea中使用git,这里介绍一些常用操作
配置git和github
配置git:file - settings - version control - git,在 path to git executable 对话框中,选择 git.exe 所在的路径
配置github:file - settings - version control - github,主要是配置仓库地址和github账号,点击 + 号,选择 log in with token,点击 generate,登录github,生成 token,复制到IDEA中,点击add account
使用经验
解冲突
比起命令行,IDEA提供了图形化的界面来查看冲突情况,更加直观。
如果两个分支对于相同的依赖做了不同的升级:
- 如果依赖是当前项目中的,比如一个模块依赖另一模块,选自己的,因为同一个项目中,合并代码时会把所有模块的代码都合并,等于会把别人的修改也合并进来,所以选择自己的即可,或者和同事商量一个新的版本号。合并完成后重新打包。
- 如果依赖是第三方的,需要商量
对于代码冲突,merge即可,如果是合并master分支时遇到冲突,通常是选择master分支中的代码。
案例:合并master分支的时候发现冲突,这通常意味着某些流程上的疏忽,或者和其他同事修改了相同位置的代码,并且其他同事的代码先上线了,此时,需要全盘选择master分支的更改,覆盖自己的更改。
1、合并时发现冲突
2、最好查看一下冲突内容,知道一下修改了什么,避免无脑合并。点击merge
左边是来自当前分支的更改,右边是来自待合并分支的更改,中间是合并结果。
查看两个分支之间的差异
第一步:与分支比较
第二步:选择一个分支,注意,origin开头的是远程分支