教程——git必备操作
教程——git必备操作
zhangzhanggit
一、git安装、配置、初始化配置
1.基础配置
配置名字
1 | $ git config --global user.name "zhangzhang" |
查看配置
1 | git config --global user.name # 查看用户名 |
2.创建仓库
在当前文件夹新建一个项目:在当前文件夹初始化一个 Git 仓库,初始化后该文件夹成为本地 Git 仓库,支持所有 Git 本地操作(如提交、版本回退等),不需要联网即可使用。
1 | $ git init |
3.克隆别人的项目
去项目界面github上,复制URL:
本地用git clone去克隆网络上的项目
1 | git clone https://github.com/torvalds/linux.git |
二、git文件状态、提交版本
如何修改文件,创建我们的版本
- 当我们$git init创建完仓库,会赋予这个仓库每一个文件都有一个状态
- 如果是自己新建的仓库,那么仓库里所有文件都是未被跟踪的
- 当你生成一个版本之后,这个未被跟踪的文件就不会在这个版本里面
- 那么他之前的状态,或者之前在哪里修改,就啥也不知道了
文件的 4 种核心状态
- 未跟踪:文件在仓库目录中,但 Git 完全 “没注意到它”(从未被
git add过)。 - 未修改:文件已被 Git 跟踪,且当前内容和 ==Git 记录的 “最新版本” 完全一致==。
- 已修改:文件已被 Git 跟踪,但内容被修改了(和 Git 记录的版本不一致)。
- 暂存(缓存):文件已被标记为 “准备提交”,处于 Git 的 “暂存区” 中。
1.单独的跟踪一个文件/目录
1 | $ git add <name> |
- 如果这个文件被跟踪,就在这个仓库永远被跟踪
2.取消被跟踪的话
1 | $ git rm <name> |
或者加一个cache选项,让他保留在目录里面,但是不被跟踪
1 | $ git rm-cache <name> |
3.对文件修改
对代码改动
4.对修改的文件的状态设置成缓存状态
1 | git add <file-name> |
5.取消缓存状态
1 | git reset HEAD <name> |
6.对以暂存的文件提交此次修改,形成新的版本
1 | git commit |
详细解释:
1. 从 “未跟踪”→“暂存”:
git add <name>- 作用:告诉 Git“开始跟踪这个文件 / 目录”,并将其放入暂存区(标记为 “准备提交”)。
- 例:新建文件
test.txt是 “未跟踪”,执行git add test.txt后,它进入 “暂存” 状态。
2. 从 “暂存”→“未修改”:
git commit- 作用:将暂存区的所有文件,以 “版本快照” 的形式提交到 Git 的版本库(本地仓库的历史记录)。
- 提交后,文件内容和版本库的 “最新版本” 一致,因此状态变为 “未修改”。
3. 从 “未修改”→“已修改”:
修改文件(手动编辑)- 作用:你在本地修改了已跟踪的文件,导致文件内容和版本库的 “最新版本” 不一致,状态变为 “已修改”。
4. 从 “已修改”→“暂存”:
git add <name>- 作用:对 “已修改” 的文件,再次执行
git add,会将新的修改内容放入暂存区,标记为 “准备提交新修改”。
5. 从 “暂存”→“未修改”(提交新修改):
git commit- 作用:将暂存区中 “已修改后重新暂存” 的内容,提交为新的版本,文件状态回到 “未修改”。
6. 从 “已跟踪”→“未跟踪”(取消跟踪):
git rm --cached <name>- 注意:你写的
git rm-cache是笔误,正确命令是git rm --cached <name>。 - 作用:仅从 Git 的跟踪中移除文件(保留本地文件),使其回到 “未跟踪” 状态。
- 对比
git rm <name>:会直接删除本地文件,并取消 Git 跟踪(慎用)。
7. 从 “暂存”→“已修改”(取消暂存):
git reset HEAD <name>- 作用:将暂存区的文件 “撤回到已修改状态”,即取消 “准备提交” 的标记。
- 例:暂存了文件但后悔了,执行
git reset HEAD test.txt,文件从 “暂存” 回到 “已修改”,可重新修改或提交。
- 都是要git add
然后再git commit(先 git add将修改放入暂存区,再git commit将暂存区的内容提交到版本库) - 修改→暂存→提交
- 工作区→暂存区→版本库
7.查看文件状态
如何查看某文件有修改但无暂存
1 | git status |
查看更细致的:哪里被修改了(第几行第几个字母)
1 | git diff |
8.查看历史提交
1 | git log |
- commit后面的一串字符就是提交的哈希值,通过这个可以找到提交内容
1 | git log --graph |
- 以一个图形化的方式去呈现
三、远程仓库/remote
1.连接远程仓库并起名字
git remote add 命令需要在一个已经初始化的 Git 仓库中执行(即执行过 git init 的目录)
在
github里面new repository,取一个名字,进到下一步,复制HTTPS的链接,切换到本地git里面进入项目路径,输入以下内容,连接上远程的仓库,并起名字叫
orgin+上面复制的HTTPS的连接
1 | git remote add orgin https://github.com/等等等 |
2.测试连接
1 | git remote |
3.改名字
1 | git remote rename orgin origin |
4.用HTTPS链接将本地的代码推送到远程的仓库中(需要输入密码或令牌)
此时如果你本地test文件夹不能是空的
Git 只能推送 “有提交记录” 的分支,所以需要先在本地做一次提交:
1
2
3
4
5
6
7
8# 1. 创建一个测试文件
touch test.txt
# 2. 将文件加入暂存区
git add test.txt
# 3. 提交到本地版本库(-m 后跟提交说明)
git commit -m "初始化提交:添加 test.txt"把本地的master分支推送上去
1 | git push origin master |
github会让你输入你的github用户名和github上的tokenstoken如何拿取:
法一:用Token
github->Settings->Developer settings->personal access tokens->Tokens
- 点击Generate token
- 这就是进入的密码
- 此时上传成功
5.用SSH协议拉取仓库里面的代码
核心作用是实现本地 Git 与远程仓库(如 GitHub、GitLab 等)的 “免密认证”,让你在推送(git push)或拉取(git pull)代码时,==无需每次输入账号密码==,同时提升安全性
1 | cd ~/.ssh |
- 查看目录中的密钥文件(可选,确认生成成功)
1 | ls |
- ls之后会展示生成的两个密钥:一个公钥(.pub),一个私钥
- 查看并复制公钥:cat test.pub(test是取的名字)
- 将复制的公钥复制到github
可以拉去某个仓库里面的代码了
1
git clone git@github.com等等等
四、分支/branch
1.概念
- 每次提交生成一个新版本的时候,都会生成一个提交对象,每个提交对象都有一个独一无二的哈希值
- 分支就是一个包含这个哈希值的一个文件,理解成指向一个提交对象的指针
- 可以在一个提交对象上新建多个分支(多个指针),因为分支是包含这个提交对象哈希值的一个文件,可以相加多少就加多少
- 初始化本地仓库的时候已经新建了一个master分支(后面的操作都是这个分支上的)
- 每次提交操作都是在这个master分支上进行的
- 每次提交的时候,分支也会跟着提交对象一起进行移动
- 若在第二次提交对象上新建一个分支,在master分支上做一些修改,然后提交一个新的提交对象
git commit
- 然后再刚刚新建的分支上再做修改,然后提交
- 发现第二次提交对象上分出了两个叉(两个分支)
2.理解
用 “指针贴便利贴” 的比喻理解:
提交对象 = 带编号的快照
每次
git commit就像给当前代码拍一张照片,这张照片有个唯一编号(哈希值),存着当时所有文件的状态。分支 = 贴在快照上的便利贴
分支就是一张便利贴,上面只写着一个编号(对应==某个==提交对象的哈希值)。
(本质是个小文件,内容只有那个哈希值)
假设现在
master分支的便利贴指着第二次提交对象(编号是B)。当你在
master上修改代码并执行git commit:- Git 会生成新的快照(第三次提交对象),编号是
C。 - 然后,
master这张便利贴会自动把旧编号B擦掉,写上新编号C—— 就像便利贴从 “第二次提交” 的快照上,“挪” 到了 “第三次提交” 的快照上,始终指着最新的那张照片。
- Git 会生成新的快照(第三次提交对象),编号是
多个分支 = 多个便利贴贴在同一个快照上
你可以在同一个快照上贴无数张便利贴(建多个分支),它们都指着同一个编号,互不影响。比如在
master分支的当前快照上,新建dev分支,此时两张便利贴指着同一个提交对象。
假设现在
master分支的便利贴指着第二次提交对象(编号B)。此时,你执行
git branch 俺是新分支(新建分支):- Git 会给你新贴一张叫 “俺是新分支” 的便利贴,上面写的编号也是
B—— 和master目前指着的 “第二次提交对象” 编号完全一样。 - 这就相当于在 **“第二次提交对象” 这同一张快照上 **,同时贴了
master和 “俺是新分支” 两张便利贴,它们都指着同一个编号B,互不干扰。
这个状态是 “多个分支贴在同一快照” 的瞬间(比如刚建分支还没提交时)。之后如果
master继续提交(生成第三次提交C),它的便利贴会挪到C;而 “俺是新分支” 如果也提交,会生成自己的新快照(比如第四次提交D),它的便利贴会挪到D:
- Git 会给你新贴一张叫 “俺是新分支” 的便利贴,上面写的编号也是
默认的 master 分支
git init后,Git 会自动贴一张叫master的便利贴(初始时可能没内容,第一次提交后就指着第一个快照)。提交时分支会 “移动”
每次在
master上git commit,会生成新快照(新编号),然后 ==master这张便利贴会自动从旧编号 “挪” 到新编号上,始终指着最新的快照==。
总结:
- 提交 = 存快照(带唯一编号)
- 分支 = 指向快照的标签(便利贴)
- 多分支 = 多个标签可指向同一个快照
- 提交后分支自动指向新快照
3.搞那么多分支干嘛
在版本控制过程中,同时推进多个任务,我们就可以创建每个任务单独的分支。使用分支意味着程序员可以把自己的工作从开发主线上分离开,开发自己分支的时候,不会影响主线分支的运行,对初学者来说,分支可以简单的理解为副本,一个分支就是一个单独的副本(分支底层其实也是指针的引用)
在master这个主线上运行,运行的同时要增加功能,那我们就可以把master赋值一份出来,在复制的地方进行开发,在开发完功能后就可以更新版本正常使用
当我们引入新功能有bug,就可以从master分支再引一个分支hot-fix(热修分支),对代码进行修改,修改后再合并到master
git流模型:
- master分支就是发布版的分支:
- 开发新特性:在feature分支上进行开发,把几个feature合并到develop分支(收集不同的feature分支,对他们进行测试)
- 测试没问题,可以发布,新建release分支,验证完没问题,把release分支合并到master分支
- hotfix分支是修复bug用的
同时并行推进多个功能开发,提高效率
各个分支在开发过程中,如果一个分支开发失败,不会对其他分支有任何影响。失败的分支删除重新开始即可。
4.操作分支
1 | git branch -v |
1 | git log |
1 | git status |
1 | git branch --list |
1 | git branch 分支名 |
查看确实已经创建好了
可以看一下下图最后一行末尾的绿色的还是“master”,说明还是在master分支上面的
5.修改代码并提交(==完整==过程)
- 从0开始
1 | mkdir test # 重新创建仓库目录 |
1 | vim test.txt |
再按i键进入插入模式
按一下esc然后输入:wq回车退出
可以查一下本地库状态
1 | git status |
添加到本地库(刚刚是未追踪)
1 | git add test.txt |
提交到本地库
1 | git commit -m "初始化提交:创建 test.txt" |
-m “提交信息”
再次查看文件
1 | cat test.txt |
查看提交版本
1 | git reflog |
6.合并分支(==完整==过程)
新建两个分支:feature-a 和 feature-b
1 | git branch feature-a |
在两个分支中修改同一文件的同一处
① 切换到 feature-a 分支并修改文件
1 | git checkout feature-a |
- 我没执行第二行echo,我是vim test.txt然后把第二行改成了696
② 切换到 feature-b 分支并修改同一行
1 | git checkout feature-b |
- 我也没执行第二行echo,我是vim test.txt然后把第二行改成了996
- 改之前是666(master版本)(
feature-b是从master分支创建的,它的时间线和feature-a是并行的,除非你把feature-a的修改合并到feature-b,否则两者的版本是独立的):
- 我改成996
步骤 3:合并分支会触发冲突
1 | # 切回 master 分支 |
执行到这一步,Git 会提示:
1 | Auto-merging test.txt |
步骤 4:查看并解决冲突
① 查看冲突文件
打开 test.txt,会看到类似这样的冲突标记:
1 | <<<<<<< HEAD |
我的:
1 | 初始内容 |
② 手动解决冲突:
我在记事本里面改成:
1 | 初始内容 |
③ 提交解决后的冲突
1 | git add test.txt |
此时 master 分支的提交历史中,会清晰记录 “合并冲突→解决冲突” 的过程,test.txt 也保留了最终的合并结果
1 | git reflog |
- 用这个查看合并状态
1 | git log --all --graph |
五、git 推送、拉取、跟踪远程分支
当本地仓库和远程仓库的内容完全不一致时,可以根据你的需求选择以下两种方式处理:
方式 1:以本地为准,强制覆盖远程仓库
如果确认本地内容是正确的,想强制用本地内容覆盖远程仓库:
1 | # 强制推送本地分支到远程,覆盖远程内容 |
这个origin是之前给远程仓库起的别名
方式 2:以远程为准,覆盖本地内容(放弃本地修改)
如果想丢弃本地所有修改,完全同步远程仓库的内容:
1 | # 拉取远程最新内容并强制覆盖本地 |
方式 3:保留双方内容,手动合并(推荐)
如果两边内容都有用,想合并后再同步:
先拉取远程内容并允许合并无关历史(如果之前提示过unrelated histories):
1
git pull origin master --allow-unrelated-histories
此时可能会出现冲突,打开冲突文件,手动编辑保留需要的内容(冲突标记为
<<<<<<<、=======、>>>>>>>)。解决冲突后提交并推送:
1
2
3git add .
git commit -m "合并本地与远程内容"
git push origin master
完整过程
1. 拉取远程所有分支信息
确保本地知晓远程仓库的所有分支(类似图中 git fetch --all):
1 | git fetch --all |
这一步会把远程仓库的 master、feature-a、feature-b(如果远程有的话)的最新提交拉到本地 origin/xxx 引用中。
2. 切换 / 创建本地分支并关联远程(若需)
切换到已有本地分支:如果你本地已有feature-a,直接切换:
1
git checkout feature-a
基于远程分支创建 / 关联本地分支:若本地没有feature-b,但远程有 origin/feature-b,可创建并关联:
1
2git checkout -b feature-b origin/feature-b
# 或简化为 git checkout --track origin/feature-b
3. 分支开发与同步
在分支上修改并提交:比如在feature-a上开发:
1
2
3
4git checkout feature-a
# (修改文件、提交)
git add .
git commit -m "feature-a:完成XX功能"推送到远程分支:将本地feature-a推送到远程同名分支:
1
git push -u origin feature-a # 第一次推送用 -u 建立跟踪,后续可直接 git push
4. 合并分支到 master
当 feature-a 和 feature-b 开发完成,需合并到 master:
1 | git checkout master |
六、储藏代码
代码写一半,突然有任务说想checkout切换到master分支,不行!
因为还有东西没写完->工作目录是脏的,直接git commit不推荐
当工作目录有未完成的修改(“脏目录”),需要临时切换到其他分支时,git stash 是最佳方案,流程如下:
1.完整 git stash 流程(以从当前分支切换到 master 为例)
- 修改feature-a的test
- 查看状态:是未修改,现在checkout去master分支是不行的(想让我们提交修改,或存储修改)
暂存当前未完成的修改
执行
git stash,将工作区和暂存区的所有修改(未提交的内容)保存到一个 “临时存储栈”,同时清空工作区,让目录变 “干净”:1
2git stash
# 输出类似:Saved working directory and index state WIP on feature-a: xxxxxxx 上次提交信息若想添加备注(方便后续识别),可加-m 参数:
1
git stash save "feature-a:未完成的XX功能"
确认工作区干净
执行
git status,显示nothing to commit, working tree clean即表示暂存成功。切换到
master分支此时可以正常切换分支了:
1
git checkout master
在
master分支处理完任务后,切回原分支比如处理完后切回
feature-a:1
git checkout feature-a
恢复之前暂存的修改
从 “临时存储栈” 中取出最近一次暂存的内容,恢复到工作区:
1
2git stash pop
# 执行后,暂存的修改会被恢复,同时该记录会从栈中删除若想保留栈中的记录(比如需要多次恢复),用git stash apply:
1
git stash apply
查看暂存栈(可选)
若有多次暂存,可通过
git stash list查看所有暂存记录:1
2
3
4git stash list
# 输出类似:
# stash@{0}: WIP on feature-a: xxxxxxx 上次提交信息
# stash@{1}: save "feature-a:未完成的XX功能"- 恢复指定记录(如恢复第 1 条):
git stash pop stash@{1}
- 恢复指定记录(如恢复第 1 条):
删除暂存记录(可选)
若不再需要某条暂存,可手动删除:
1
2git stash drop stash@{0} # 删除第0条记录
git stash clear # 清空所有暂存记录
核心逻辑
git stash 相当于给未完成的代码拍了个 “快照”,临时存起来,让你能干净地切换分支;处理完其他任务后,再 “快照还原”,继续之前的开发,避免了不合时宜的 commit(比如提交不完整的代码)。
我在 feature-a 分支上执行了多次 git stash 操作,将工作区的修改暂存到了 Git 的 “暂存栈” 中
- 我先通过
git stash list查看了已有暂存记录,发现有一条stash@{0}。 - 然后多次执行
git stash,每次都把当前工作区的修改(对test.txt的改动)保存到暂存栈,导致暂存栈里出现了stash@{0}、stash@{1}、stash@{2}多条记录,每条记录都对应一次对test.txt修改的暂存操作。
2.恢复之前的记录
- 恢复第一次保存的
1 | git stash apply stash@{2} |
七、重置/reset
1、git reset head~ --soft:“软重置”(保留修改,仅回退提交)
- 作用:将当前分支的
HEAD指针回退到上一次提交(head~表示上一个提交节点),但保留工作区和暂存区的修改。 - 场景:比如你刚提交了一个包含错误的 commit,想 “撤销提交但保留代码修改,重新整理后再提交”。
- 流程示例:
- 假设你执行了
git commit -m "错误的提交",但发现代码还需调整; - 执行
git reset head~ --soft,此时提交记录回退,但修改的文件仍在暂存区(可通过git status看到Changes to be committed); - 调整代码后,可重新执行
git commit -m "正确的提交"。
- 假设你执行了
2、git add <name>:暂存文件(重置流程的前置操作)
- 作用:将工作区的文件修改添加到暂存区,是
git commit的前置步骤,也常与重置流程配合(比如软重置后需重新暂存 / 提交)。 - 示例:修改了
test.txt后,执行git add test.txt,将该文件的修改暂存,为后续提交或重置做准备。
三、git commit:提交暂存区到版本库
- 作用:将暂存区的修改永久记录为一个 commit 节点,是版本控制的核心操作。
- 场景:当你完成一批功能开发,且通过
git add暂存了修改,就可以用git commit -m "提交说明"把这些修改固化为提交记录。
四、git reset head~ --hard:“硬重置”(丢弃所有未提交和已提交的修改)
- 作用:将
HEAD指针回退到上一次提交,同时强制覆盖工作区和暂存区,丢弃所有未提交的修改和本次提交的内容。 - 场景:比如你提交了一个完全错误的版本,且工作区还有未提交的无效修改,想彻底回退到上一个正确版本。
- 风险提示:此操作会永久删除未提交的修改和目标提交的内容,慎用!
- 流程示例:
- 执行
git reset head~ --hard; - 工作区和暂存区会被强制重置为上一次提交的状态,所有未提交的修改和本次错误提交的内容都会被丢弃。
- 执行
八、rebase
一、rebase 的核心作用
简单说:把一个分支的所有提交 “搬” 到另一个分支的最新提交后面,让提交历史呈现 “线性”(无合并节点),而不是 merge 产生的 “分叉”。
举个例子:
- 你在
feature-a分支开发时,master分支有了新提交(比如同事修复了一个 bug); - 用
rebase可以让feature-a的所有提交 “基于”master的最新提交,仿佛你是在master最新状态下开发的feature-a。
二、基础用法:git rebase <目标分支>
假设当前在 feature-a 分支,想让它基于 master 的最新提交:
1 | # 1. 确保目标分支(master)是最新的(先切到 master 拉取远程更新) |
执行后,Git 会:
- 暂时 “保存”
feature-a分支在master分支之后的所有提交; - 把
feature-a分支的起点 “移动” 到master的最新提交; - 重新应用之前保存的
feature-a提交(按顺序逐个应用)。
三、处理冲突:rebase 中最常见的场景
如果 feature-a 和 master 修改了同一文件的同一部分,rebase 会暂停并提示冲突:
1 | # 冲突提示类似: |
解决步骤:
打开冲突文件,找到冲突标记(
<<<<<<< HEAD到>>>>>>> 提交ID),手动编辑保留正确内容;标记冲突已解决:
git add <冲突文件>(如git add test.txt);继续 rebase 流程:
1
git rebase --continue
(Git 会继续应用剩下的提交);
- 若想跳过当前冲突的提交:
git rebase --skip(谨慎,会丢弃该提交); - 若想放弃整个 rebase 操作:
git rebase --abort(回到 rebase 前的状态)。
- 若想跳过当前冲突的提交:
四、rebase vs merge:什么时候用?
| 场景 | 推荐用 rebase |
推荐用 merge |
|---|---|---|
| 目的 | 让分支历史更简洁(线性),适合个人开发分支 | 保留完整的分支合并记录,适合团队协作主分支 |
| 操作后历史 | 无合并节点,提交按时间线排列 | 产生一个 “合并提交”,保留分支分叉记录 |
| 适用场景 | feature 分支基于 master 同步更新 | feature 分支完成后合并到 master |
五、注意事项(重要!)
- 不要在公共分支(如
master)上执行 rebase:rebase 会改写提交历史,若多人基于同一公共分支开发,会导致他人历史混乱; - rebase 后的分支推送需强制推送:因历史被改写,首次推送到远程需用
git push -f origin feature-a(仅限自己的 feature 分支,公共分支禁止!); - rebase 是 “改写历史”:它不会删除原提交,只是创建新的提交替代,原提交会在一段时间后被 Git 自动清理。
















































