git commitid向多个分支提交代码_40分钟git从入门到放弃(苦笑)

本文旨在帮助读者从原理上理解Git常用命令,特别是提交、分支和冲突解决。Git是一个内容寻址文件系统,由blob、tree和commit对象组成。冲突可能因文件内容交叉修改、文件删除或新增同名文件引起。建议通过频繁推送和获取代码来避免冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

7338dbb86a076c5d75e05cc68d664dc6.png

有这么一个段子...

老杨一推代码, 所有的开发同事便都看着他笑, 有的叫道, “ 老杨, 你又把代码合丢了!” 他不回答, 对产品说, “ 追加两个需求, 只做性能优化。” 便排出一台MacBook Pro。 他们又故意的高声嚷道, “ 你推错分支了!” 老杨睁大眼睛说, “ 你怎么这样凭空污人清白……”, “ 什么清白? 我前天亲眼见你写Bug把流水线搞挂, 又改崩生产代码“。 老杨便涨红了脸, 额上的青筋条条绽出, 争辩道, “ 流水线报错不算错, 合代码挂掉的事能叫Bug吗?” 接连便是难懂的话, 什么“ Merge大法好, GitFlow卍解”, 什么“ 拉代码不需要Rebase” 之类, 引得众人都哄笑起来: 店内外充满了快活的空气。

用 git 的, 谁没合丢过代码呢? (反正我是合丢过...)

好, 痛定思痛, 我决定从原理上理解Git, 所以决定写下这篇文章(才不会说是因为buddy叫我准备一场针对 git 的 session).

PS: git官网上有看都看不完的长篇大论, 甚至有人专门为理解git的一种workflow出书...这些都说明"看这篇文章就想把git搞透彻是不可能的"(因为写文章的人都没有把git搞透彻(再次苦笑)).

So: 这篇文章不会是一个体系完善的Git教程, 只会针对性地从原理上理解一些我们常用的Git命令, 最终目标就是能够在基本的多人合作的场景下正常工作, 同时对于复杂的场景能有用于检索资料的先验知识.

目录

  • 目录
  • Git的那些概念
    • Git的本质是什么?
    • Git中存储的对象
      • 1. blob对象
      • 2. tree对象
      • 3. commit对象
      • ref(引用)
  • Git的那些常用操作
    • file-level
    • commit/branch-level
    • repo-level
  • Git的那些冲突
    • 出现冲突的可能情形
    • 规避冲突的优选操作
    • 其他的奇技淫巧
      • 别名
      • 子模块(submodule)

Git的那些概念

Git的本质是什么?

Git的本质其实是一个内容寻址(content-addressable)文件系统, 它的核心部分是一个简单的键值对数据库。我们可以向该数据库写入值(object), 它会返回一个键(object的引用地址, SHA-1字符串), 通过该键可以在任意时刻再次检索值. 我们所操作的版本控制, 其实就是不断地向这个文件系统写入操作日志.

那么这些被写入的操作日志是什么呢?

Git中存储的对象

1. blob对象

对应的值是单个文件内容的快照, 这个快照不包括文件名。

e8b64f38f473c534749e46f3f063d127.png

2. tree对象

以树的形式记录的目录结构和文件的索引, 每个普通结点(有子级的结点)都是一个子级的tree对象的包裹体, 每个叶子结点(无子级的结点)都是一个blob对象的包裹体, 这些包裹体会附带文件(夹)的名称等元数据。

02f2868ba6b50ca0bfcca37d83868cfa.png

3. commit对象

对应的值包含一个数据集(通常称为Comments, 这个数据集包含父级commit的地址(SHA1值, 通常也被称作commitid)、作者以及提交message等信息)以及一个当前commit对应的变更的tree, tree的内容为当次提交的变动快照(没有发生变动的文件不会被加入快照)。

02cd1f5b439967583472b8bda9f9ce50.png

总结一下, 三者关系:

81046166a92edde68bd1264c7649cad2.png

所以, 当存在多个提交时,

#bash
git init
echo "version 1" > test.txt
git add .
git commit -m "frist commit"

echo "version 2" > test.txt
echo "new file" > new.txt
git add .
git commit -m "second commit"

mkdir bak
echo "version 1" > bak/text.txt
git add .
git commit -m "third commit"

最终的整体结构应该是这样:

1bd0b7adff7f5dd68375693268bf70da.png

ref(引用)

  • branch: 指向某一系列提交之首的引用
  • HEAD: 指向目前工作基点提交的引用
  • tag:
    • lightweight tag: 指向任意提交的引用
    • annotated tag: 指向一个标签对象的引用(注:标签对象其实和上面提到的三种一样也是对象, 不过非常罕见, 结构类似commit, 不过内含数据不是指向变动快照的tree, 而是指向commit)
  • remote branch: 指向某服务器端某一系列提交之首的引用

Git的那些常用操作

file-level

456f48ee37f55015d39af159cbd71f83.png
  • add @path: 标记文件的stage状态
  • reset @path: 取消文件的stage状态
    • --hard: 取消文件的stage状态并恢复其到unmodified状态
  • checkout @path: 等价于reset @path --hard

commit/branch-level

以下一些操作可以到learngitbranching上进行交互式演示, 接下来在讲解相关概念的时候会在这里做相关的命令演示, 小伙伴们可以在上面操作一下

注:

  1. 以下的所有commitid都可以换成branch(或者说ref)
  2. 以下的所有commitid都会是简称(例如c1等)
git clone # 初始化模拟仓库
  • checkout @commitid=null: 转移HEAD到指定提交
git checkout c0 # 将HEAD移动到c0(master保持不变, 此时HEAD为detached HEAD, 直接指向了commit而非某个ref)
  • commit: 提交
    • --amend: 与前一个提交合并提交(改写)
git commit #生成一个新提交
git commit --amend #改写基点提交, 当前worktree内容融合基点提交的内容重新生成一个提交
  • branch: 创建一个分支
git branch branch1 # 基于当前生成一个新分支
git checkout branch1 # 移动HEAD到这个新分支(branch1)
  • reset @commitid=null: 重置HEAD及其所指向的ref到指定提交
    • --hard: 抛弃reset过程中的所有文件变更()
git reset master # 重置HEAD及其所指向的ref到master所在提交
# 重置HEAD及其所指向的ref到"c2'"提交
git reset c2'

PS: 关于reset和check的区别

f41b5d16a02e5c7491f8d94839c528a3.png
  • revert @commitid: 提交一次与某次提交的内容完全相反的提交
# 生成一次和c2'相反的提交(抵消/还原c2'提交并向前移动HEAD, 新版本的git还支持批量revert(git revert start..end)
git revert c2'
  • cherry-pick @commitid: 将某个commit的变动叠加到HEAD所指向的commit上(会创建一个和之前的commit内容一样的提交, 不过两者具有不同的sha1值)
git cherry-pick c1 # 将c1提交的变动快照同步到当前HEAD位置, 新版本的git还支持批量cherry-pick(git cherry-pick start..end)
  • tag: 标记具有某种特殊意义的提交(里程碑)
git tag OnMerge # 给当前提交取名"OnMerge", 可以很方便地回滚到特定版本
  • merge @commitid: 将某一次提交的内容整合到HEAD(生成一次merge提交叠加在HEAD之上并移动HEAD)
git checkout master # 模拟其他人在master上进行了两次提交
git commit # 模拟一次提交c3
git commit # 再模拟一次提交c4
git checkout branch1 # 切回HEAD到branch1
git merge c3 # 单独合并c3的变动到当前HEAD
git reset c1'
git merge master # 合并master上所有的变动到当前HEAD
  • rebase @commitid: 将某一次提交的内容整合到HEAD(把所有不在commitid对应提交所在提交链上的提交叠加在该提交上)
    • -i 交互式, 可以选择哪些提交要被rebase到指定位置后
git checkout master # 模拟其他人在master上进行了两次提交
git commit # 模拟一次提交c7
git commit # 再模拟一次提交c8
git checkout branch1 # 切回HEAD到branch1
git rebase c7 # 把当前分支的变更叠加到c7上
git rebase OnMerge # 撤销一下
git rebase master -i # 把当前分支的变更叠加到master上(效果等效于merge, 不过rebase假定自己的编辑都是基于master的)

git checkout master # 如果经常保持rebase, 那么当branch1需要被merge回master时, 会非常容易
git merge branch1 # 冲突已经在频繁的rebase中被解决的, 这里的merge都会是"快速前进"
# 可以说是多分支开发中的"单分支开发"

repo-level

  • fetch: 同步remote repo的状态
  • remote: 管理remote repo及相关分支, 几乎只需要单次配置, 不做赘述.
  • push: 将本地的commit推送到服务器(默认不会推送tag)
    • push --tag: 连tag一起推送

Git操作实体的总体结构

799b07ddc59170909648d8f4903bf37a.png

Git的那些冲突

出现冲突的可能情形

  • 他人和自己编辑了对同一文件的编辑内容存在交叉行
  • 他人删除了自己编辑的文件
  • 他人和自己新增了同名的文件

规避冲突的优选操作

  • 代码稳定后尽快推送(尤其针对公共内容的改动)
  • 勤获取代码(Build前自动获取最新代码)

其他的奇技淫巧

别名

git config --global alias.co checkout

子模块(submodule)

# 在现有的git仓库内执行
git submodule add -b master https://example.com/another-repo.git another-repo # 将another-repo添加为当前git仓库的子模块, 并存放到another-repo路径下
git submodule update --remote # 从远端更新当前仓库的子模块

或着针对已经存在的子模块

# 在现有的git仓库内执行
git submodule add another-repo # 将another-repo目录添加为当前git仓库的子模块(前提是another-repo需要是一个git仓库), 不指定分支的时候, git会默认将当前HEAD所在的commit作为子模块(即子模块处于非track状态)
git push --recurse-submodules=<check|on-demand> # push之后子仓库状态即可同步, 其他团队成员可以通过git submodule update --remote来更新本地子模块, check9默认)为循环检测子模块, on-demand为仅提交当前仓库的一级子模块

EOF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值