概念
graph TB;
R(远程仓库<br>Remote Repository)
L(本地仓库<br>Local Repository)
S(暂存区<br>Stage Area)
W(工作区<br>Work Space)
W--add-->S
S--commit-->L
L--push-->R
R--pull-->W
R--clone/fetch-->L
L--checkout-->W
工作区
工作区(Work Space)是指在本地文件系统中存储库目录, 可理解为在本地计算机上存放源代码目录
作用
用户可在工作区进行代码编辑、添加、修改和删除等操作, 所有文件最新修改都体现在工作区中
状态
-
已修改(modified), 表示文件已修改, 但还未提交到版本库中
-
已暂存(staged), 表示文件已修改, 并且已添加到暂存区, 准备提交到版本库
-
已提交(committed), 表示文件已提交到版本库
暂存区
暂存区(Staging Area)是.git目录下名为index文件, 本质是数据库
作用
暂存区作为缓冲地带, 其分离工作区与版本库交互, 使得工作区中更改不会立即进入版本库, 而是先进入暂存区
本地仓库
本地仓库是在开发人员自己电脑上git仓库, 包含项目全部提交历史
作用
本地仓库用于存储本地工作区和暂存区变更(新增、修改、删除)文件
对工作区中文件修改, 并提交到暂存区和本地仓库后, 更改就永久保存在本地仓库历史记录中
远程仓库
远程仓库是在远程服务器git仓库, 用于存储备份、共享和协同工作代码
作用
远程仓库允许团队成员将本地更改推送到远程仓库, 并从远程仓库拉取其他成员更改
操作
-
克隆(clone), 将远程仓库复制到本地
-
推送(push), 将本地仓库更改上传到远程仓库
-
拉取(pull), 将远程仓库更改下载到本地仓库
版本管理
初始化
初始化本地仓库, 执行后自动创建.git目录, 用于管理版本库
git init
- 示例, 将git_test作为本地仓库
状态
查看文件状态
git status
未添加文件
- 示例, 未添加文件时查看状态
未添加暂存区
- 示例, 创建main.txt查看状态
已添加暂存区
- 示例, 添加暂存区后查看状态
已提交
- 示例, 提交文件后查看状态
修改
修改文件
- 示例, 新增文件main.cpp
红色表示文件位于工作区并已修改
撤销
撤销工作区内文件修改
将文件恢复到上一次提交状态
git restore 文件名
撤销工作区内文件删除
从上一次提交中恢复被删除文件到工作区
git checkout -- 文件名
添加
将工作区文件添加至暂存区, 文件名为.
表示全部文件
git add 文件名
- 示例, 将main.cpp添加到暂存区
绿色表示文件已添加暂存区
撤回
将已添加至暂存区文件撤回至工作区
git rm --cached 文件名
- 示例, 将main.cpp撤回
提交
将暂存区文件提交至本地仓库
git commit (-m "commit信息")
- 示例, 提交main.cpp
(1) 若是未设置邮箱用户名, 需要先设置
(2) 设置完成后正常提交
查看
查看每次提交记录, 包含commit ID、作者、提交时间信息
git log
- 示例, 查看git_test仓库提交记录
修改
设在commit后修改内容
修改最近一次 commit
git commit --amend
回退
设原始main.cpp
#include<iostream>
int main() {
std::cout << "[AAAAAAAA]" << std::endl;
return 0;
}
设修改后main.cpp
#include<iostream>
int main() {
std::cout << "[Update]" << std::endl;
return 0;
}
未暂存回退
表示已修改未添加至暂存区(未执行git add
)回退
指定文件回退
文件名为.
时表示全部, 文件恢复至修改前状态
git checkout -- 文件名
- 示例, 修改main.cpp后, 在未添加到暂存区时回退
已暂存回退
表示已修改已添加到暂存区(已执行git add
)回退
注意, git reset
只撤回添加操作, 不撤回文件修改
回退指定文件
git reset HEAD 文件名
回退所有修改
git reset
- 示例, 将修改后main.cpp添加到暂存区后回退
发现文件被撤回到工作区, 但修改后内容不变
已commit退回
表示已修改并已commit回退, 分三种情况
git中HEAD
表示当前提交, 上版本表示为HEAD^
, 前100个版本表示为HEAD~100
设将修改后main.cpp提交到本地仓库
reset –hard
删除工作空间改动, 撤销git commit
, 撤销git add .
# 回退到上次commit
git reset --hard HEAD^
# 回退到任意版本
git reset --hard 某次commit_id
- 示例,
reset --hard
回退
回退后, 暂存区和工作区均会回到上次提交时状态, 所有自上次后修改全部被恢复
reset –soft
不删除工作空间改动, 撤销git commit
, 不撤销git add .
git reset --soft HEAD^
- 示例,
reset --soft
回退
回退后, 被修改文件会在暂存区, 且文件自上次修改后内容不会删除
reset –mixed
不删除工作空间改动, 撤销git commit
, 并撤销git add .
git reset --mixed HEAD^
- 示例,
reset --mixed
回退
回退后, 修改后文件会在工作区, 且文件自上次修改后内容不会删除
分支
git分支允许在代码库中创建多个开发线
gitGraph
commit id: "main-1"
commit id: "main-2"
branch dev
checkout dev
commit id: "dev-1"
checkout main
branch fix
checkout fix
commit id: "fix-1"
每个分支本质上都是代码库一个快照, 可在不影响其他分支情况下独立进行开发和修改
graph LR;
P(特点)
P-->A(代码隔离)-->A1(分支之间代码独立, 可在不同分支上修改, 互不干扰)
P-->B(代码合并)-->B1(合并操作会将分支上所有更改应用到目标分支上)
P-->C(版本控制)-->C1(可创建多分支来跟踪不同版本或不同阶段开发)
查看
查看当前分支
创建仓库时默认创建master(main)分支
git branch (-a, 表示所有分支)
- 示例, 查看git_test仓库所有分支
查看远程分支
git branch -r
创建
仅创建分支
git branch 分支名
创建分支并切换
git switch -c 分支名
- 示例, 创建dev分支并切换
gitGraph
commit
commit
branch dev
checkout dev
commit
修改
切换分支
git switch 分支名
- 示例, 切换回master分支
重命名
重命名当前分支
git branch -M 新分支名
删除
git branch -d 分支名
- 示例, 删除dev分支
合并
合并代码时分两种情况,
-
完全合并, 需要另一个分支所有变动, 采用
git merge
-
选择合并, 只需要部分变动(某几次提交), 采用
git cherry-pick
完全合并
git merge 待合并分支名
正常合并
git switch B分支
git merge A分支
- 示例, 将dev分支合并到master分支
(1) 设master分支上创建文件main.cpp, 提交
#include<iostrea>
int main() {
std::cout << "[master]" << std::endl;
return 0;
}
(2) 新建dev分支并切换, 对main.cpp文件进行两次修改, 并提交
第一次修改
#include<iostrea>
int main() {
std::cout << "[master]" << std::endl;
+ std::cout << "[dev] fix 1" << std::endl;
return 0;
}
第二次修改
#include<iostrea>
int main() {
std::cout << "[master]" << std::endl;
std::cout << "[dev] fix 1" << std::endl;
+ std::cout << "[dev] fix 2" << std::endl;
return 0;
}
依次提交
gitGraph
commit id: "first commit"
branch dev
switch dev
commit id: "dev first commit"
commit id: "dev second commit"
switch main
merge dev id: "merge dev"
(3) 切换回master分支, 合并dev分支所有提交
冲突情况
两个分支合并时可能会出现冲突错误
- 示例, 两分支修改同文件同位置, 导致合并冲突
(1) 设在master分支上创建main.cpp, 并提交
#include<iostream>
int main() {
std::cout << "Hello World" << std::endl;
return 0;
}
gitGraph
commit id: "master first update"
(2) 创建dev分支切换, 修改main.cpp, 并提交
#include<iostream>
int main() {
std::cout << "[dev] Hello World" << std::endl;
return 0;
}
gitGraph
commit id: "master first update"
branch dev
switch dev
commit id: "dev first update"
(3) 切回master分支, 再修改main.cpp, 提交
#include<iostream>
int main() {
std::cout << "[fix master] Hello World" << std::endl;
return 0;
}
gitGraph
commit id: "master first update"
branch dev
switch dev
commit id: "dev first update"
switch main
commit id: "master second update"
(4) 将dev分支合并到master分支
提示冲突, 因为两个分支修改同一文件
gitGraph
commit id: "master update"
branch dev
switch dev
commit id: "dev update"
switch main
commit id: "master update second"
merge dev type: REVERSE
main.cpp内容如下
<<<<<<<
表示当前修改, >>>>>>>
表示传入修改
冲突是因两个分支都修改文件同一位置所导致
(5) 解决冲突
设保留双方更改, 修改后提交
#include<iostream>
int main() {
std::cout << "[fix Master] Hello World" << std::endl;
std::cout << "[dev] Hello World" << std::endl;
return 0;
}
gitGraph
commit id: "master update"
branch dev
switch dev
commit id: "dev update"
switch main
commit id: "master update second"
merge dev id: "merge dev"
查看master分支与dev分支上main.cpp
选择合并
git cherry-pick
可选择其他分支单次/多次提交, 并作为新提交引入到当前分支
git cherry-pick 某次commit_id
- 示例, 将dev分支上某次提交合并到main分支
(1) 设master分支上创建main.cpp, 并提交
#include<iostream>
int main() {
std::cout << "Hello World" << std::endl;
return 0;
}
(2) 切换到pick分支, 对main.cpp进行三次修改并提交
第一次修改
#include<iostrea>
int main() {
std::cout << "[master]" << std::endl;
+ std::cout << "[pick] fix 1" << std::endl;
return 0;
}
第二次修改
#include<iostrea>
int main() {
std::cout << "[master]" << std::endl;
std::cout << "[pick] fix 1" << std::endl;
+ std::cout << "[pick] fix 2" << std::endl;
return 0;
}
第三次修改
#include<iostrea>
int main() {
std::cout << "[master]" << std::endl;
std::cout << "[pick] fix 1" << std::endl;
std::cout << "[pick] fix 2" << std::endl;
+ std::cout << "[pick] fix 3" << std::endl;
return 0;
}
(3) 选择合并
将pick分支 commit “pick 2 update”提交合并到master分支
gitGraph
commit id: "master 1 update"
branch pick
switch pick
commit id: "pick 1 update"
commit id: "pick 2 update"
commit id: "pick 3 update"
switch main
cherry-pick id: "pick 2 update"
提示冲突, 查看main.cpp, 选择保留双方修改, 解决冲突后提交
后续可用git cherry-pick --continue
继续合并其他提交, 或用git cherry-pick --abort
放弃合并
应用多提交
按顺序逐一应用提交到当前分支上
git cherry-pick commit1 commit2...
应用范围提交
可指定提交范围应用
git cherry-pick 开始commit^结束commit
标签
标签(tag)标记特定commit, 用于记录项目开发过程中重要里程碑或发布版本
创建
指定commit作标签
git tag 标签名 (某次commit_id, 为空表示最新commit)
- 示例, 标签最新commit
git tag v1.0
gitGraph
commit id: "master update 1"
commit id: "cherry pick ok" tag: "v1.0"
- 示例, 标签commit 7d4b4bf19eaede7d9c7ef…
gitGraph
commit id: "master update 1" tag: "v0.9"
commit id: "cherry pick ok" tag: "v1.0"
添加描述信息
git tag -a 标签名 -m 描述信息 某次commit_id
删除
删除本地tag
git tag -d 标签名
- 示例, 删除tag v0.9
删除远程tag
git push origin :refs/tags/标签名
- 示例, 删除远程仓库v0.1标签
查看
git tag
远程仓库
远程仓库通常位于远程服务器, 如GitHub、GitLab、Bitbucket或Gitee等代码托管平台
克隆
通过克隆可将远程仓库内容复制到本地, 创建一个与之相同本地仓库
完全克隆
克隆远程仓库所有提交记录
git clone 仓库地址 (存放路径)
示例, 克隆github上仓库到本地
拉取最近记录
git clone --depth 提交次数 参数地址
查看
远程仓库地址
git remote -v
- 示例, 查看test_project项目
删除
按名称删除
git remote rm 远程主机名
- 示例, 删除远程仓库
按URL删除
git remote set-url --delete origin 远程仓库地址
添加
git remote add 远程主机名 远程仓库地址
- 示例, 添加远程仓库
拉取
从远程仓库获取更新, 使本地仓库保持同步
git pull 远程主机名 远程分支名:本地分支名
若远程分支是与当前分支合并, 可简写为git pull
- 示例, 从远程仓库master分支拉取更新到本地master分支
手动合并
git pull
过程可分解为,
(1) 通过git fetch
从远程主机将远程最新内容拉到本地, 用户可决定是否合并到本地分支中
git fetch 远程主机名 远程分支名
取回更新后, 会返回FETCH_HEAD
, 指某个分支在服务器上最新状态
通过git log -p FETCH_HEAD
查看更新文件名、作者和时间、代码, 可以通过这些信息来判断是否产生冲突
(2) 通过git merge
将拉取下来最新内容合并到当前所在分支中, 若提示冲突, 需手动解冲突后提交
git merge FETCH_HEAD
- 示例, 本地仓库通过手动合并远程仓库master分支更新
推送
git push
将本地分支更改上传到远程仓库
git push 远程主机名 本地分支名:远程分支名
设存在远程仓库http://192.168.1.128:3000/test_user/demo.git
推送已创建仓库
git remote add origin 仓库地址
git push origin 分支
- 示例, 本地建立demo项目
新建main.cpp提交, 将本地分支推送到远程仓库master分支
推送本地分支
git push 远程主机名 本地分支名:远程分支名
若本地分支与远程分支一致, 则可简写为git push
- 示例, 本地新建dev分支, 推送到远程仓库
设置跟踪关系(可选)
为在以后推送中不再每次指定远程分支名, 可使用-u(或–set-upstream)标志来设置跟踪关系
git push -u 远程仓库名 本地分支名
设置跟踪关系后, 后续只需调用git push
即可将更改推送到所关联远程分支
同时推送到多仓库
添加多个远程仓库地址, 就可同步push
到多个库
git remote set-url --add origin 远程仓库地址
推送本地标签
git push origin 分支名 --tags
- 示例, 本地master分支建立tag:v0.1并上传
子模块
git子模块(Git Submodules)允许将一个git仓库作为另一个git仓库子目录
graph LR;
X(作用)
X-->A(依赖管理)-->A1(可将三方库作为子模块包含, 方便管理依赖关系)
X-->B(版本控制)-->B1(可单独跟踪和更新子模块版本)
X-->C(代码复用)-->C1(多个项目之间共享相同代码库, 而无需复制代码)
X-->D(隔离性)-->D1(可在不影响主项目情况下更新或修改子模块)
初始化
自动拉取
使用--recursive
在克隆项目同时拉取子模块
git clone --recursive 项目地址
- 示例, 克隆项目时拉取子模块
手动拉取
若克隆项目时未拉取子模块, 可在主项目根目录手动拉取
git submodule init
git submodule update --recursive
查看
git submodule
- 示例, 查看子模块
添加
git submodule add 子模块仓库地址 (子模块路径)
- 示例, 添加子模块
修改
更新
更新子模块以获取最新改动
git submodule update --remote
提交
子模块修改后, 与普通项目提交方式一致
删除
移除子模块
git submodule deinit -f 子模块本地路径
删除子模块目录
git rm 子模块本地路径
- 示例, 移除Imgur子模块