Featured image of post Git

Git

版本控制工具

Hook

想象你写了一晚上的论文, 但是不小心被小组成员改坏了. 或者debug发现bug越来越多以至于无法修复, 整个项目基本要推倒重来. 上诉这种类似的情况其实屡见不鲜. 不只是计算机专业的同学会遇到, 其他非专业同学在编辑ppt, word, 或者设计工程图的时候也一样会遇到类似的问题. 而git的出现就是为了解决这一类的问题.

Feature

git 作为版本控制工具最基本的功能就是管理文件版本. 可以将每一次文件的修改都记录起来. 当项目发生不可挽回的错误的时候. 只要.git文件夹还在就能发动败者食尘功能直接将时间会回滚到上一次提交的时候. 除此之外还提供了团队协作的功能. 不同的团队成员可以在同一个仓库下的不同brach对项目进行迭代又不会相互影响.

败者食尘

参考视频

Quick start

# ubuntu/debian
apt install git

# mac
brew install git

# Configuration
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

#initialization | it will create a hidden folder which is called .git
git init

Common command

# check status
git status

# add all file of this folder
git add .

# simple commit
git commit -m "<commit info>"

# check commit history | type q to quit
git log

# connet to remote repository | orgin: set the name of the remote repo
git remote add origin <url of remote repository>

# switch to the main branch
git branch -M main

# push to the remote repo | when you use git push it will push to the defualt repo that your pulled before.
git push -u origin main | git push

# pull the latest version of the remote repo
git pull

.gitignore: you can create a this file and fill the files or dict that you do not want to push on remote.

# add all the changes to the this commit and commit it with only one line code
git commit -am "args"

# clone a remote repo | clone a remote repo
git clone <url>

# fetch
git fetch <url> 

当你要使用私有远程仓库的时候,就需要验证你的个人信息,确保你有权利访问这个远程仓库. 这个时候就需要使用ssh生成一对密钥对, 然后将公钥放在远程仓库上. 有关这方面的知识如果不是很明白可以先去学学ssh相关的内容.

Advance command & feature

git checkout

  1. 切换分支 | 创建切换到新分支
git checkout -b <branch_name>
# 创建并移动到新分支

git branch <branch_name>
git checkout <branch_name>
# 这两行等等效上面这行
  1. 放弃当前未保存的修改
git checkout -- <file_name>
# 放弃该文件的修改并回滚到上一次提交的状态
  1. 查看历史提交
git checkout -b <commit_id>
# 提交历史id 可以通过git log 查看或者gui查看

git stash

git stash
git stash save "<comment>"
# 存档当前的更改并回到最新一次提交的状态 | archive the modification and roll back to the latest commit

git stash list
git stash list 
# 查看存档

git stash pop stash@{<id>} 
git stash pop stash@{<id>} 
git stash pop
# 将缓存剪切出来

git stash apply stash@{<id>}
git stash apply stash@{<id>}
git stash apply
# 将缓存复制出来

git stash clear
#清空缓存

git reset

git reset 命令用于将当前分支的HEAD指针重置到指定的提交状态。它有三种常用模式:

# 撤销最后一次的提交
git reset --soft HEAD~1

# 软重置 - 保留工作区和暂存区的更改
git reset --soft <commit_id>
# 仅移动HEAD指针到指定提交,不改变工作区和暂存区

# 混合重置(默认) - 保留工作区更改,但清空暂存区
git reset --mixed <commit_id>
# 或者简写为
git reset <commit_id>
# 移动HEAD指针,并重置暂存区,但保留工作区更改

# 硬重置 - 丢弃所有更改
git reset --hard <commit_id>
# 移动HEAD指针,重置暂存区和工作区,丢弃所有更改

使用场景:

  • 撤销最近的提交
  • 合并多个提交为一个
  • 彻底撤销错误的更改

git merge | 官方文档

git merge 命令用于将一个或多个分支的更改合并到当前分支:

# 合并指定分支到当前分支
git merge <branch_name>

# 创建合并提交,即使快进合并可用
git merge --no-ff <branch_name>

# 合并时产生一个squash提交,不保留原分支的提交历史
git merge --squash <branch_name>

git rebase | 官方文档

git rebase 用于将一系列提交"变基"到另一个基础提交上。通俗地说,它会把你的提交历史"移动"到另一个分支的最新提交之后,使得提交历史看起来更线性、更整洁。

# 将当前分支的提交应用到 <base_branch> 的顶部
git rebase <base_branch>

# 例如,你在 feature 分支上,想把 main 分支的最新更改合并进来
git checkout feature
git rebase main

交互式 Rebase (-i)

这是一个非常强大的功能,允许你修改、合并、重新排序或删除一系列提交。e.g. 在某一个提交中有一个提交没有意义需要删除中间的某个提交.

# 重新整理最近的3个提交
git rebase -i HEAD~3

执行后会打开一个编辑器,显示这3个提交,你可以执行以下操作:

  • pick:保留提交(默认)
  • reword:修改提交信息
  • edit:修改提交内容
  • squash:将该提交与前一个提交合并
  • fixup:与 squash 类似,但丢弃该提交的提交信息
  • drop:删除提交

Git Merge vs. Git Rebase

git mergegit rebase 是两种将一个分支的更改集成到另一个分支的主要方法。它们的核心区别在于如何处理提交历史。

Git Merge

  • 保留历史merge 会保留原始分支的完整、精确的提交历史。
  • 合并提交:它会在目标分支上创建一个新的"合并提交",这个提交有两个父提交,分别指向两个被合并的分支。
  • 非线性历史:这会导致提交历史图谱看起来像一个分叉再汇合的图形,可以清晰地看到分支从哪里来,又在哪里合并。
  • 优点:简单直接,保留了真实的开发轨迹,便于追溯。
  • 缺点:如果分支活动频繁,合并提交会非常多,导致主分支历史变得混乱。

Git Rebase

  • 重写历史rebase 会找到两个分支的共同祖先,然后将当前分支的所有提交"重放"(replay)到目标分支的顶部。
  • 线性历史:它会创建一个线性的、干净的提交历史,看起来就像所有开发都是在一条直线上完成的。
  • 优点:历史记录非常整洁,没有不必要的合并提交,易于阅读和理解。
  • 缺点
    1. 重写了历史:这可能会使追溯问题变得困难,因为它改变了提交的原始上下文。
    2. 风险永远不要在已经推送到远程并被团队成员使用的公共分支上使用 rebase。因为这会为团队其他成员造成历史记录不一致的问题,导致严重的协作混乱。

总结

特性 git merge git rebase
历史记录 非线性,保留分叉 线性,整洁
易用性 简单,不易出错 相对复杂,有风险
协作 安全,适合公共分支 危险,只应在私有分支上使用
结果 产生一个合并提交 不产生合并提交,但会创建新的提交

何时使用?

  • 使用 git merge 将功能分支合并到 maindevelop 等主分支上。
  • 使用 git rebase 在将你的功能分支合并到主分支之前,用来同步主分支的最新更改,保持你的分支历史干净。

冲突处理 (Conflict Resolution) | 官方文档

在使用 Git 进行合并 (git merge) 或变基 (git rebase) 操作时,如果两个分支修改了同一个文件的同一部分,或者一个分支修改了文件而另一个分支删除了它,Git 无法自动解决这些差异,就会发生"冲突"(Conflict)。

如何识别冲突

当冲突发生时,Git 会停止当前操作并通知你。你可以通过 git status 命令查看哪些文件处于冲突状态。

在冲突的文件中,Git 会插入特殊的标记来指示冲突的部分:

<<<<<<< HEAD
// 这是你当前分支(HEAD)的更改
=======
// 这是你正在合并(或变基)的分支的更改
>>>>>>> branch-name-or-commit-id
  • <<<<<<< HEAD======= 之间的内容是当前分支(你的工作)的更改。
  • =======>>>>>>> branch-name-or-commit-id 之间的内容是你正在合并/变基的分支(传入的更改)的更改。

解决冲突的步骤

  1. 识别冲突文件:运行 git status 检查哪些文件有冲突。
  2. 手动编辑文件:打开每个冲突文件,手动修改内容,决定保留哪些更改、丢弃哪些更改,或者如何组合它们。在解决完冲突后,务必删除 <<<<<<<=======>>>>>>> 这些冲突标记。
  3. 暂存已解决的文件:对每个解决冲突后的文件运行 git add <filename> 将其标记为已解决。
  4. 完成操作
    • 如果是 git merge:在所有冲突都解决并暂存后,运行 git commit 完成合并。Git 会自动生成一个合并提交信息,你可以修改它。
    • 如果是 git rebase
      • 解决冲突并暂存后,运行 git rebase --continue 继续变基过程。
      • 如果你决定放弃当前的变基操作并回到变基前的状态,可以运行 git rebase --abort

常用辅助命令

  • git merge --abort:在合并过程中,如果你觉得冲突太复杂,想放弃合并并回到合并前的状态,可以使用此命令。
  • git rebase --abort:在变基过程中,如果你想放弃变基并回到变基前的状态,可以使用此命令。
  • git mergetool:配置并使用外部合并工具(如 Kdiff3, Beyond Compare, VS Code 等)来图形化地解决冲突。
  • git checkout --ours <filename>:如果你想保留当前分支对 <filename> 的所有更改,而丢弃传入分支的更改。
  • git checkout --theirs <filename>:如果你想保留传入分支对 <filename> 的所有更改,而丢弃当前分支的更改。

记住,解决冲突是一个需要仔细和耐心的过程

分支管理最佳实践 (Branch Management Best Practices)

当我们需要给一个仓库实现新的功能或者修复bug的时候,最标准的做法就是创建一个新的分支.然后在分支上进行修改. 完成修改之后再使用it rebase 或者 git merge 来将新的修改合并到主分支上.

虽然技术上你可以重复使用同一个分支名,但在实际开发和团队协作中,“一次性分支(Feature Branch)” 是行业标准做法。

以下是为什么要"删了重开"的详细理由和最佳实践:

1. 为什么要删除旧分支?

  • 避免历史混乱(最重要) 如果你一直复用 devfeature 这个名字:

    • 第1次开发合并了功能 A。
    • 第2次开发合并了功能 B。
    • 半年后如果你想回头看"功能 A"的代码是从哪里分出来的,在这个复用的分支上会很难理清。
    • 如果你每次都起新名字(如 feat/add-login, fix/navbar-bug),Git 的历史线会非常清晰,一眼就能看出你在某个时间段是为了什么目的而改代码。
  • 防止"不干净"的起点 如果你复用旧分支,很容易忘记把该分支更新到 main 的最新状态就开始写新代码。这会导致你基于"旧代码"开发,最后合并时产生大量不必要的冲突。

    • 创建新分支 强制你必须基于最新的 main 开始,保证起跑线是干净的。
  • 心理上的"完成感" 删掉一个分支代表"这个任务彻底搞定了"。保持分支列表清爽,只留下 main 和当前正在做的任务,能减少心智负担。

2. 标准操作流程

在你把 aider 分支合并到 main 之后,标准的下一轮开发动作如下:

Step 1: 删除旧分支 (清理战场)

既然 aider 里的代码已经进 main 了,它就没用了。

git branch -d aider

(注:如果代码还没合并,Git 会阻止你删除。如果已合并,-d 会安全删除。)

Step 2: 创建新分支 (开始新征程)

根据你要做的新任务,起一个有意义的名字。

git checkout -b feat/add-search-bar  # 比如你要做搜索功能
# 或者
git checkout -b fix/image-loading    # 比如你要修 bug

3. 如何给分支起个好名字?

既然要频繁创建新分支,起名就很关键。推荐使用 前缀/描述 的格式:

  • feat/ (feature): 开发新功能
    • feat/user-login
    • feat/dark-mode
  • fix/: 修复 Bug
    • fix/header-alignment
    • fix/typo-in-readme
  • docs/: 仅仅修改文档
    • docs/update-install-guide
  • refactor/: 代码重构(不改变功能)
    • refactor/optimize-loop

4. 只有一种情况可以"复用"分支

如果你维护的是一个 长期存在的开发分支(通常叫 developdev),这种分支是不会删除的。

  • main: 存放随时可以发布的稳定代码。
  • develop: 存放最新的开发进度(可能不太稳定)。

在这种模式下,你依然不会直接在 develop 上写代码,而是:

  1. develop 切出一个 feat/xxx 分支。
  2. 开发完合并回 develop
  3. 删掉 feat/xxx
A winner is just a loser who tried one more time.
Robust AI
使用 Hugo 构建
主题 StackJimmy 设计