Git Github Tutorial
Git & Github Tutorial
教程地址:Git & GitHub Tutorial | Visualized Git Course for Beginner & Professional Developers in 2024
git自动跟踪每个代码更改,允许多个人无缝处理同一个项目,让成员浏览项目历史纪录
1.检查git版本
git --version
2.配置姓名和邮箱
为了跟踪谁对项目进行了更改,使用姓名和电子邮件配置git
git config --global user.name 'yourname'
git config --global user.email 'youremail'
仓库(repo)是git跟踪项目中所有内容的地方,将仓库视为存储所有代码版本的文件夹
3.创建本地仓库 (git init)
在终端创建一个本地仓库
git init
.git文件夹中包含了提交历史(commit history)、分支情况(branches)、远程仓库(remote repo)情况等
分支默认名称为master,请用main、trunk、development等其他名称替换
4.修改初始化默认分支名称
修改使用git init初始化时的分支名称
git config --global init.defaultBranch main
验证一下刚才的设置,当我重新创建一个新文件夹并初始化本地仓库时,看看默认分支名称是否为main
分支(branch)是你项目的并行版本
5.查看git状态(git status)
现在我们来添加一些文件让git对其进行跟踪
git status
我们看到刚刚创建的文件还没有被git跟踪
目前在主分支
尚无提交
有两个未跟踪的文件
6.将文件添加到暂存区(git add)
为了让git能够跟踪两个文件,后续对这两个文件的更改git会记录下来你的更改操作,我们将文件添加到git暂存区(staging)
git add README.md
如果想要将所有修改过的文件都加入到git暂存区
git add .
这里的点(.)告诉git将所有创建、修改、删除的文件添加到git暂存区
7.提交文件(git commit)
提交(commit)我们将文件添加到git暂存区后需要提交它,提交就像是在某个时间点拍摄项目的快照,可以将其视为创建文件夹的全新副本,并告诉git记住你何时何地执行了此操作。如果将来发生任何事情,你将使用你指定的提交名称退回到此文件夹以获取并查看其中的内容
定期提交更改可以帮助你跟踪项目进度,并轻松恢复到以前的版本
接下来我们将hello.py文件加入git暂存区后提交
git add hello.py
git status
git commit -m 'add hello and readme'
这里的m代表message消息,消息的标准是“祈使语气‘(类似命令式的语气),这样别人在合并你的分支时就明白,如果将你的分支合并进来会发生什么情况
查看所有提交commit的历史记录
git log
log中包含了提交的ID(hash值)、作者姓名、邮箱、提交时间、提交信息
提交hello文件后HEAD指针(指向分支的最新节点)由readme节点转移到了hello节点
8.切换分支
8.1.1 git checkout
当前版本的代码存在一些缺陷,想回到上一个版本重新修改代码,这时就需要切换分支
假设我要从当前节点切换到前一个节点
git checkout 提交readme那个节点的哈希值
注意单单checkout不会删除原来节点的内容
我们从hello文件那个节点切换到readme那个文件的节点
切换后发现之前节点的hello.py文件消失了(其实它还在并没有被删除),HEAD指针(本来就应该指向最新提交的节点) 指向了当前节点
切换后出现了警告“HEAD指针不再指向最新节点”
虽然表面上切换之前的节点消失了,但其他它还存在,并没有被删除
git checkout main
从readme节点切换到了主分支main的最新节点
切换后我们发现“消失”的hello.py又出现了
8.1.2 git checkout -f
如果想要切换分支的同时放弃当前分支的新修改
我当前在“add hello.py”节点上,我对hello.py文件添加一行
当我使用checkout切换时,提示错误,我对该文件做出了修改,但没有提交,如果我不想要修改过的代码了,那么我强制进行切换,直接抛弃修改过的代码
git checkout -f README节点的哈希值
当前我们在readme这个节点,我们切换回hello那个节点,检查一下之前在hello.py添加的那行代码还在不在
git checkout hello节点的哈希值
原来在hello文件中修改的代码,确实在git checkout -f 时被抛弃了
9.git和github的区别
git是一个用来跟踪项目更改的工具
github是一个云平台吗,允许你存储你的git仓库,并与其他人协作将你本地仓库推送到github远端仓库
git init是在初始化本地仓库,git push是将本地仓库推送到github远端仓库
10.github上创建远端仓库
远程仓库名为origin(等价于远程仓库的URL)
11.更改主分支名称(git branch -M)
如果本地的主分支名称还没有更改为main,请使用
git branch -M main
12.本地仓库链接到远端仓库(git remote add origin)
在本地终端运行
git remote add origin 远程仓库URL
13.本地仓库推送到远程仓库(git push -u origin)
git push -u origin main
origin等价于远程仓库的URL,这里用origin直接代替
main是本地仓库主分支名称
14.分支(branch)
14.1 为什么需要分支?
git的分支允许你创建项目的不同版本,在特定时刻复制项目代码,在这个复制的版本上进行更改,这些更改不影响主分支的代码
可以将在其他分支中开发的新功能合并(merge)到主分支
在主分支外使用单独分支的原因:
1.为项目实现不同功能
2.错误修复
各个分支的功能
master 分支
master 为主分支,也是用于部署生产环境的分支,确保master分支稳定性
master 分支一般由develop以及hotfix分支合并,任何时间都不能直接修改代码
develop 分支
develop 为开发分支,始终保持最新完成以及bug修复后的代码
一般开发的新功能时,feature分支都是基于develop分支下创建的
feature 分支
开发新功能时,以develop为基础创建feature分支
分支命名: feature/ 开头的为特性分支, 命名规则: feature/user_module、 feature/cart_module
release分支
release 为预上线分支,发布提测阶段,会release分支代码为基准提测
当有一组feature开发完成,首先会合并到develop分支,进入提测时,会创建release分支。
如果测试过程中若存在bug需要修复,则直接由开发者在release分支修复并提交。
当测试完成之后,合并release分支到master和develop分支,此时master为最新代码,用作上线。
hotfix 分支
分支命名: hotfix/ 开头的为修复分支,它的命名规则与 feature 分支类似
线上出现紧急问题时,需要及时修复,以master分支为基线,创建hotfix分支,修复完成后,需要合并到master分支和develop分支
引用:您必须知道的 Git 分支开发规范
14.2 创建分支(git branch)和切换分支(git checkout)
git branch 分支名称
git checkout 分支名称
创建的同时切换分支
注意执行该命令时所处的分支,因为这个创建分支是在当前所处分支上进行的,所以创建分支前要检查当前所处分支
git checkout -b 分支名称
如果不想检查当前所处分支,而是就直接指定在某个分支上创建新分支
git branch 新分支名称 指定分支名称
我当前在主分支上,但是我不想从主分支上建立新分支,我直接在指定分支new-branch上创建新分支feature-B
git branch feature-B new-branch
由于我们仅仅是对本地仓库进行更改,并没有同步到远端仓库,所以github中并没有发生变化
14.3 推送分支(git push --set-upstream)
我们将新建分支推送到远端仓库
git push --set-upstream feature-B
git push:将本地仓库的提交推送到远程仓库
–set-upstream(简写为 -u):建立本地分支与远程分支的跟踪关系
origin:远端仓库URL(默认名称,可自定义)
feature-B:本地分支名,同时也是目标远程分支名(若不存在则会创建)
我们看到目前远端仓库中有主分支main、和刚刚推送的分支feature-B,而分支new-branch并没有在远端
origin为远端仓库URL、main为本地主分支名称、feature-B为本地分支名称、new-branch为本地分支名称、HEAD头指针(黄色标签,指向当前分支)
我现在切换到未推送的new-branch分支
git checkout new-branch
把未推送的new-branch分支也推送到远端仓库
git push -u origin new-branch
我们看到分支new-branch前面多了一个origin(远端仓库URL)
执行 git push -u origin feature-B ,会直接推送本地 feature-B 分支到远程仓库 origin ,并建立好跟踪关系,后续在 feature-B 分支上可直接用 git push 和 git pull 操作(同步了分支后,后续就可以直接在分支上git push)
由于modify hello.py没有推送到远端,需要把主分支main再次推送到远端
git push -u origin main
15.将远端仓库同步更新到本地仓库(git pull)
如果其他协作者将自己修改的代码上传到了远端仓库,自己需要在其基础上开发,这时就需要将远端仓库的代码同步到自己的本地仓库
16.拉取请求(pull request)后合并(merge)
如果你在其他分支上完成了功能的开发,你得把你的分支合并到主分支上,在合并前就需要拉取请求(pull request)让团队负责人进行审核和反馈,一旦请求获得批准,你的分支就能成为主分支的一部分
16.1 新建拉取请求
16.2 解决冲突
显示其他分支中具体的修改内容,如果其他分支相比主分支在相同文件中有改动,那么就需要解决冲突问题,比如主分支在第100行有代码,其他分支在100行是空行
16.3 拉取请求
若没有冲突或解决了冲突,那么就可以创建拉取请求(create pull request)
添加请求的title和description
拉取请求后提示该分支与主分支没有冲突
16.4 其他分支合并到主分支
没有冲突后,我们确认合并
当然也可以在本地终端执行,确认当前所处分支
git merge
合并后,我们发现主分支上出现了featureB分支的内容,代表合并成功
合并后查看一下项目分支信息
如果某分支的ahead值为0,表明该分支的所有提交都合并到了主分支,我们可以将该分支删除了,因为该分支的开发功能都合并到了主分支,所以该分支的任务已经完成了,可以将其删除
16.5 分支的Ahead和Behind
了解一下Ahead和Behind
在多个分支并行开发时,我们经常需要了解每个分支相对于主分支的提交差异。这就是git ahead/behind信息的作用。
git ahead表示分支比主分支多了多少个提交,而git behind表示分支比主分支少了多少个提交。通过了解这些信息,我们可以知道主分支和分支之间的提交差异,从而更好地管理和合并代码。
git ahead表示分支上相对于主分支多出来的提交数。如果分支有3个提交,而主分支只有2个提交,则分支比主分支多1个提交,即git ahead为1。
git behind表示分支上相对于主分支少了多少个提交。如果分支有2个提交,而主分支有3个提交,则分支比主分支少1个提交,即git behind为1
–引用:Git分支中主分支与分支之间的git ahead/behind信息
个人理解:如果这里某个分支ahead值大于0,代表分支的某些东西还没有合并到主分支上。如果这里某个分支behind值大于0,代表该分支的内容少于主分支需要pull更新该分支
核心概念:提交差异的本质
当我们说某个分支ahead N或behind M时,实际上是在比较两个分支的共同祖先之后的提交差异:
ahead N:分支 A 比主分支(如main)多了N 个未合并的提交。
behind M:分支 A 比主分支少了M 个提交(即主分支有 M 个提交未同步到分支 A)
ahead/behind 的实际含义
ahead > 0
分支有新提交未合并到主分支,通常意味着:你在分支上开发了新功能,需要通过git merge或git rebase将变更同步到主分支。若主分支是发布分支,这些变更尚未发布。
behind > 0
分支落后于主分支,可能需要更新以获取主分支的最新变更:若要在分支上继续开发,建议先git pull主分支的更新,避免后续合并时冲突过大。若准备将分支合并到主分支,建议先同步主分支的最新代码(git rebase main),保持提交历史整洁。
如何处理 ahead/behind 状态
场景 1:分支 ahead N,需要同步到主分支
git checkout main
git merge feature # 将feature的变更合并到main
场景 2:分支 behind M,需要更新
git checkout feature
git pull origin main # 合并主分支更新到当前分支
# 或使用rebase保持线性提交历史
git rebase main
场景 3:同时 ahead N 且 behind M(常见于长期分支)
# 1. 先同步主分支更新
git checkout feature
git pull --rebase origin main# 2. 解决可能的冲突后,推送更新后的分支
git push -f origin feature # 若使用rebase,可能需要强制推送# 3. 最后将分支合并到主分支
git checkout main
git merge feature
16.6 将远端仓库的更改同步到本地仓库
以上合并操作均发生在远端仓库,我们的本地仓库还没有发生变化,如下图
先切换到主分支
git checkout main
拉取远端仓库
git pull
或者不切换分支而是直接使用
git pull origin main
我在远程仓库中将分支featureB合并到了主分支,随后删除了分支featureB,接着在本地git pull同步更新,但我发现featureB仍存在
git pull 主要用于拉取远程分支的更新并合并到本地分支 ,它并不会自动删除本地已经不存在于远程的分支引用。在远程删除分支后,本地仍保留着该分支的相关信息,是本地的远程分支跟踪记录未被更新
git fetch -p
-p 是 --prune 的缩写,该命令会从本地仓库中移除那些远程仓库已不存在的分支的跟踪信息
我们发现featureB前面的origin消失了
如果不仅远程分支被删除,你也想删除本地的 featureB 分支 ,在完成上述清理操作后,先确保不在 featureB 分支上(可切换到其他分支,如 git checkout main ) ,然后使用以下命令删除本地分支:
我们刚刚从本地仓库中移除那些远程仓库已不存在的分支featureB的跟踪信息,但是本地分支中featureB仍存在
如果 featureB 分支的所有提交都已经合并到其他分支,此命令可安全删除分支
git branch -d feature-B
若 featureB 分支存在未合并的提交,使用 -D 强制删除,但会丢失未合并的提交内容,需谨慎操作
git branch -D featureB
17.解决合并冲突(Resolving Merge Conflicts)
17.1 什么是合并冲突?
当两个或多个开发人员编辑相同的代码时,git会感到困惑,这被称为合并冲突
冲突后到底要选择保留哪些代码或者哪个开发人员的更改?这就是在解决冲突
17.2 冲突的产生
案例:
在主分支上有一个README.md内容为一行文字:Hello Git!
我在主分支上创建两个分支featureA、featureB,从主分支切换到分支featureB
git commit -m 'add README'
git branch featureA
git checkout -b featureB
在featureB中我们更改一下README的内容,修改为:Welcome to Git! - This is coming from featureB
修改后我们将修改过的readme添加到git 暂存区
git add .
主分支的readme与featureB分支的readme内容是不一样的
比较两个文件,拉取请求,创建拉取请求
在featureB分支上,提交修改过的readme
git commit -m 'Modify readme by changing the heading and adding a new line'
将featureB分支同步到远端仓库
git push -u origin featureB
我们切换到featureA分支,对readme也进行修改
git checkout featureA
添加到暂存区,提交修改后的commit,将分支featureA同步到远端仓库
git add .
git commit -m 'add a few lines on readme'
git push -u origin featureA
我们在featureA上也拉取请求,目前两个分支各有一个拉取请求
现在我们将featureA中readme内容合并到主分支
合并后的主分支中readme内容为
现在我们把featureB中的内容合并到主分支发现了冲突,合并操作被阻止了
点击解决冲突(Resolve conflicts)
冲突的来源是:featureA修改了readme文件且合并到了主分支,但是featureB也修改了readme文件中的同一行,所以在featureB提出合并请求时出现了冲突
git遇到冲突后希望你解决冲突,决定保留哪个版本的代码,然后手动更新代码
你和协作者在同一代码上工作时就会因对同一行代码进行修改而发生冲突,最终无法合并到主分支
17.3 解决冲突的标准流程
先从featureA分支切换到主分支
git checkout main
从远端仓库拉取最新的更改
git pull
切换到featureB分支
git checkout featureB
HEAD头指针(黄色tag)已经移动到了featureB
正常流程是把我们的分支合并到主分支上去,为了减少团队负责人解决冲突的工作量,我们先把最新主分支拉到本地,把主分支合并到我们的分支后解决冲突内容,最后请求团队负责人将我们的分支合并到主分支
目前我们在featureB分支,我们将主分支合并到featureB分支
git merge main
pycharm上方有个Git->Resolve Conflicts
点击merge,在这个对话框里解决冲突
比如我把featureB的合并到main
我们把需要的内容都合并到中间Results,之后点击右下角Apply
当前我们在featureB分支上,我们把这个合并结果添加到暂存区,后提交,push推到远端仓库
git add .
git commit -m 'resolve merge conflicts'
git push
我们看到featureB已经与main没有冲突了
我们解决冲突后给团队负责人一个comments,说明我们已经解决冲突请求合并到主分支
检查一下我们修改过的文件中的内容
如果还有问题可以写comment,点击上面红框中右上角的对话框,如果没有就可以让团队负责人合并我们的分支了
现在主分支的情况
我们看到我们刚刚的操作,把最新主分支拉到featureB分支,修改冲突后,提取请求,合并到主分支
18.git使用的常见步骤
1.clone the repository
2.create a new branch from the main or another branch
3.make your changes
4.push the branch to the remote repo
5.open a pull request
6.merge the changes
7.pull the merged changes into your local main branch
8.repeat from step 2
19.重置历史提交记录(reset)
19.1 工作区和暂存区
工作区:你实际编辑文件的地方,是 “未完成” 的修改。
暂存区:用于准备提交的中间区域,是 “已完成且准备好提交” 的修改。
Git 的核心流程:工作区修改 → git add → 暂存区 → git commit → 本地仓库 → git push → 远程仓库
19.2 软重置(soft reset, git reset --soft)
作用:将 HEAD 移动到指定提交,不改变暂存区和工作区。
场景:撤销提交,但保留所有变更(相当于 “取消提交”,但变更仍在暂存区)。
git reset --soft <commit-hash>
回溯到历史记录3,但保留历史记录3之后的所有历史记录(这些提交commit记录在暂存区,没修改的和修改完没提交的在工作区)
也就是说已经被git跟踪的代码(暂存区)、以及没修改的和修改完代码没提交的均不会被删除
我们尝试一下软重置
git reset --soft <reademe的hash>
HEAD头指针回到了readme那里
其他提交记录均不显示在log中
我们回到原来的状态
git reset --soft <最新的hash>
HEAD头指针重回最新提交记录
19.3 硬重置(hard reset, git reset --hard)
回溯历史记录3,但删除历史记录3之后的所有历史记录(这些提交commit记录在暂存区,没修改的和修改完没提交的在工作区)
也就是说已经被git跟踪的代码(暂存区)、以及没修改的和修改完代码没提交的均会被删除
19.4 默认重置(mixed reset,git reset)
作用:将 HEAD 移动到指定提交,重置暂存区,但保留工作区的修改。
场景:取消暂存(unstage)文件,但保留工作区的修改
git reset <commit-hash>
把已经被跟踪的代码移除暂存区,那些还没来改完的代码(没有给git跟踪的代码)保留下来
19.5 git reset、git reset --soft、git reset --hard区别
提交的commit记录在暂存区(被git跟踪的代码)
没修改的和修改完没提交的在工作区(还没有被git跟踪的代码)
20.撤销历史提交记录(git revert)
应用场景:我已经提交了commit信息,但是我想反悔,不想提交了这个信息,或者公共分支上我提交了commit信息,别人也提交了commit,但是我的commit的代码有些问题,想撤回来不提交。–引用:git 教程 --git revert 命令
git reset 是回滚到对应的commit-id,相当于是删除了commit-id以后的所有的提交,并且不会产生新的commit-id记录,如果要推送到远程服务器的话,需要强制推送-f
git revert 是反做撤销其中的commit-id,然后重新生成一个commit-id。本身不会对其他的提交commit-id产生影响,如果要推送到远程服务器的话,就是普通的操作git push就好了。–引用:git 教程 --git revert 命令
git revert命令:是用于“反做”某一个版本,以达到撤销该版本的修改的目的。–引用:Git的三种后悔药:amend、revert、reset
假设某个项目已经开发了三个功能
进行了四次commit
可以用pycharm中集成的commit功能来提交,也可以终端输入命令
假设现在项目经理说:我们现在需要把功能二撤下来(对应second function),难道我们去手动取出功能二的所有代码(当然可能不止在一个文件中,可能包含在其他文件中的代码)?如果直接使用reset则会影响其他写的功能(third function),这时就使用git revert
在second function上右键后点击git revert
回退second function之前
回退second function之后
生成了新提交 revert “second function”
我们发现second function.py的文件消失了,但是third function和fourth function的文件还在,也就是说回退second function并没有影响到其他功能
我们将回退功能二之后的本地仓库更新到远端仓库
git push -u origin main
如果本身就在main分支,则命令简化为
git push
也就是说如果你想将现在所处的分支推送到远端,就直接push,如果现在所处的分支不是你想要推送的,但又不想切换分支,那就用-u origin 你想推送的分支名称
21.暂存(git stash)
git stash 是一个强大的工具,用于临时保存当前未提交的修改,让你可以在不提交的情况下切换分支或执行其他操作
此时有一个紧急bug需要处理,但是目前写了一些重要的代码但还没有写完,我如果直接切换分支的话,我写的重要代码就删除了,此时代码也不能commit因为还没写完,这时可以暂时保存起来,等到修复完紧急bug后再继续写
git stash
假设现在修复完了紧急bug,现在想回去继续写重要代码
查看暂时存储列表里之前保存的临时代码
git stash list
从存储列表中找到之前暂存的代码信息,恢复以前暂存的代码
git stash apply stash@{0}
恢复前
恢复后
22.Git GUI
其实在很多变成开发环境(IDE)中就有git的可视化操作界面
例如:pycharm