Git有两个基本作用:

  1. 版本控制
  2. 团队开发

一、Git工作流程

image-20250323213044366

二、Git基本配置

设置用户信息

设置:(+如果要查看,只输入双引号前面的就好了)

1
2
git config --global user.name "yourname"
git config --global user.email "youremail"

有3种范围:--local只对某个仓库有效,--global对当前用户的所有仓库有效,--system对系统所有登录的用户有效。

要显示config的配置,加--list

三、Git基本使用

获取本地仓库

git init,执行之后工作目录下就会产生.git隐藏目录。

核心操作

  1. clone(克隆): 从远程仓库中克隆代码到本地仓库
  2. checkout (检出):从本地仓库中检出一个仓库分支然后进行修订
  3. add(添加): 在提交前先将代码提交到暂存区
    • 可以接单个文件名,也可以接通配符
  4. commit(提交): 暂存区 –> 本地仓库。本地仓库中保存修改的各个历史版本
    • 可以接-m后跟注释
  5. fetch (抓取) : 从远程库,抓取到本地仓库,不进行任何的合并动作,一般操作比较少
  6. pull (拉取) : 从远程库拉到本地库,自动进行合并(merge),然后放到到工作区,相当于fetch+merge
  7. push(推送) : 修改完成后,需要和团队成员共享代码时,将代码推送到远程仓库

辅助查看与操作

  1. git status:查看修改的状态
  2. git log [option]:查看提交日志,git-log以精简形式查看
    • git log:以默认形式查看提交日志
    • git log --all:显示所有分支的提交日志
    • git log --pretty=oneline:将提交信息显示为一行
    • git log --abbrev-commit:使得输出的 commitId 更简短
    • git log --graph:以图的形式显示提交历史,便于查看分支合并情况
  3. git reflog commitID:记录所有操作,可以回滚到任意地方
  4. git reset --hard commitID:版本回退
  5. 添加文件到忽略列表:创建 .gitignore 文件,列出要忽略的文件模式

回滚

如果在开发过程中,某个需求不需要了,此时分为3种情况讨论:

  1. 文件在工作区:执行git checkout file
  2. 文件在暂存区:执行git reset HEAD file,让这个文件回到工作区,然后执行1
  3. 文件在本地仓库:执行git reset -方式(有3种)
    1. hard:工作区、暂存区、本地仓库3个地方保持一致
    2. mixed:让文件保存在工作区
    3. soft:让文件保存在暂存区

四、Git分支

核心操作

  1. 查看本地分支:git branch

  2. 创建本地分支:git branch 分支名

  3. 切换分支:git checkout 分支名

    1. 可以直接切换到一个不存在的分支(创建并切换):git checkout -b 分支名
  4. 合并分支:git merge 分支名称,一个分支上的提交可以合并到另一个分支

  5. 删除分支:不能删除当前分支,只能删除其他分支

    1. git branch -d b1 删除分支时,需要做各种检查
    2. git branch -D b1 不做任何检查,强制删除

解决冲突

步骤:

  1. 处理文件中冲突的地方

  2. 将解决完冲突的文件加入暂存区(add)

  3. 提交到仓库(commit)

GitFlow

image-20250321162227327

  1. master (生产) 分支:线上分支,主分支,中小规模项目作为线上运行的应用对应的分支;

  2. develop(开发)分支:是从master创建的分支,一般作为开发部门的主要开发分支,如果没有其他并行开发不同期上线要求,都可以在此版本进行开发,阶段开发完成后,需要是合并到master分支,准备上线;

  3. feature/xxxx分支:从develop创建的分支,一般是同期并行开发,但不同期上线时创建的分支,分支上的研发任务完成后合并到develop分支;

  4. hotfix/xxxx分支:从master派生的分支,一般作为线上bug修复使用,修复完成后需要合并到mastertestdevelop分支;

  5. 还有一些其他分支,在此不再详述,例如test分支(用于代码测试)、pre分支(预上线分支)等等。

五、Git远程仓库

基本命令

  1. 对接远程仓库: git remote add <远端名称> <仓库路径>

  2. 查看远程仓库: git remote

  3. 推送到远程仓库:git push [-f] [--set-upstream] [远端名称 [本地分支名]:[远端分支名]]

    1. 如果远程分支名和本地分支名称相同,则可以只写本地分支git push origin master
    2. -f 表示强制覆盖
    3. --set-upstream 推送到远端的同时并且建立起和远端分支的关联关系
    4. 如果当前分支已经和远端分支关联,则可以省略分支名和远端名
  4. 查看本地分支与远程分支的关联关系:git branch -vv

  5. 从远程仓库克隆:git clone <仓库路径> [本地目录]

  6. 从远程仓库抓取/拉取:(如果不指定远端名称和分支名,就抓取所有分支)

    1. git fetch [remote name] [branch name]:将仓库里的更新都抓取到本地,不进行合并。
    2. git pull [remote name] [branch name]:将远端仓库的修改拉到本地并自动进行合并,等同于fetch+merge

解决冲突

远程分支也是分支,解决冲突的方式和本地相同(看上文)。

需要先拉取远程仓库的提交,经过合并后才能推送到远端分支:

image-20250321170448090

六、进阶命令

交互式变基

在本地使用,重新排序、合并、拆分、编辑或删除提交,从而整理提交历史,使其更加清晰。

rebase和merge的区别:都是实现合并分支,但是细节不同,rebase会把复杂的提交历史修订为干净整洁的线性结构,并且产生新的commitID

image-20250323213854932

使用步骤

  1. 执行git rebase -i HEAD~5,此时打开一个编辑器,显示最近的5个提交,每个提交前有一个命令(默认是pick

  2. 编辑提交列表,修改每一行前面的命令,例如pick改成reword

  3. Git会按照指令提交

常用命令

命令 缩写 作用
pick p 保留该提交(不做修改)
reword r 修改提交信息
edit e 暂停 rebase,允许修改提交内容(如增删文件)或提交信息
squash s 合并到前一个提交,并保留提交信息
fixup f 合并到前一个提交,但丢弃当前提交的提交信息
drop d 删除该提交
exec x 执行一个 shell 命令(如运行测试)

注意事项

  1. 不要修改已经推送到远程仓库的提交历史(除非确定没有其他人基于此工作)

  2. 如果遇到冲突:

    1. 解决冲突后,用 git add 标记为已解决
    2. 继续 rebase:git rebase --continue
    3. 或终止 rebase:git rebase --abort
    4. 解决后强制推送到远程:git push --force

储藏

临时保存未提交的更改,将当前工作目录和暂存区的修改保存到一个“储藏区”(stash stack),可以快速切换分支或处理其他任务,后续再恢复这些更改。

基本命令

基本命令 具体操作 说明
存(入栈) stash (push) 默认存入当前工作区的修改到栈中
stash save "注释" 可以连续存多次变动代码,添加注释方便区分
取(出栈) stash pop 取出栈顶的修改并应用到当前工作区,同时从栈中移除该修改
注意:确保此时 pop 的变动代码是你需要的,否则 pop 后可能需要重新压栈
stash apply 取出栈顶的修改并应用到当前工作区,但不从栈中移除该修改(类似 peek
清除 stash drop 丢弃栈顶的修改
stash clear 清空整个 stash 栈
查看 stash list 查看 stash 栈中的所有修改记录
stash show + 栈索引 查看指定索引位置的修改详情

使用场景

  1. 切换分支时,当前分支有未完成的代码。
  2. 需要紧急修复其他分支的 Bug,但不想提交当前代码。
  3. 临时保存实验性代码,避免污染提交历史。
  4. 合并冲突前,先保存当前改动。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 当前有未提交的修改,但需要切换到其他分支
git stash

# 2. 切换到其他分支并完成任务
git checkout other-branch
# ... 处理其他任务 ...

# 3. 返回原分支并恢复储藏
git checkout original-branch
git stash pop # 恢复并删除最近的储藏

# 4. 查看储藏列表(可选)
git stash list

# 5. 清空储藏(可选)
git stash clear

挑选

工作在多分支结构的提交维度上,与merge的区别:

  • merge:需要另一个分支上的所有变动
  • cherry-pick:需要另一个分支上的部分变动

提交情形

image-20250323214756528

当产生冲突时,会停下来让用户决定,此时有3种情况:

  1. --continue:解决冲突后,git add,再执行此命令继续合并
  2. --abort:放弃合并,回到之前的状态
  3. --quit:放弃合并,且不回到之前

比较不同

diff命令,主要讲解两点提交和三点提交的区别:

image-20250323215417921

七、铁令

  1. 切换分支前先提交本地的修改

  2. 代码及时提交,提交过了就不会丢

  3. 遇到任何问题都不要删除文件目录

tag与go modules

Git 和 Go Modules 的一个重要机制:Tag(标签)是打在具体的 Commit(提交)上的,而不是打在分支上的。

只要你的那个 Commit (e9680046ed2f) 存在于远程仓库中(无论它是在 feature 分支、develop 分支,还是悬空的),只要你给它打上了 v1.3.21 的标签并推送到远程,Go 就能引用到它。

具体操作流程

你只需要确保针对那个 Commit 打了标签并推送即可:

1. 在 go-idt 仓库操作

你甚至不需要切换分支,直接指定 Commit ID 打标签即可:

1
2
3
4
5
# 直接给指定的 commit hash 打标签 (无论它在哪个分支)
git tag v1.3.21 e9680046ed2f

# 单独推送这个标签到远程
git push origin v1.3.21

2. 在引用方(你的主项目)操作

1
2
3
# 修改 go.mod 中的 replace 为 v1.3.21
# 然后执行
go mod tidy

为什么这样做是可以的?

Go 的依赖管理工具(go mod)在拉取代码时,逻辑是这样的:

  1. 去找 code.sangfor.org/.../go-idt.git 这个仓库。
  2. 询问仓库:“有没有一个叫 v1.3.21 的标签?”
  3. 仓库回答:“有,这个标签指向 Commit e9680046ed2f。”
  4. Go 下载那个 Commit 的代码。

它完全不关心这个 Commit 是属于 master 分支还是某个临时的开发分支。

只有一个小注意事项

虽然技术上不需要合并到 master,但在管理上建议注意:

如果你的 v1.3.21 是基于一个临时分支打的,以后这个分支被删除了,或者这个 Commit 永远没合入主线,可能会导致代码历史比较混乱(虽然 Tag 依然存在,代码不会丢,但在 git log 的树形图里看会比较孤立)。

修改分支的base:onto

git rebase --onto 是 Git 中最强大的命令之一,你可以把它理解为 “精准的外科手术式移植”

普通的 git rebase master 只能处理简单的“把当前分支基底换到 master”的情况。而 --onto 允许你截取某一段特定的提交记录,嫁接到另一个地方去。

它主要用于解决 “链式分支依赖”(Branch Chaining)的问题。

核心语法

1
git rebase --onto <移植到的目标位置> <被剪切的起始位置(不包含)> <要移动的分支>

也就是:

git rebase –onto

最经典的场景:孙子分支“借尸还魂”

假设你有以下分支结构:

  1. master: 主干。
  2. feat-A: 基于 master 开发的功能 A。
  3. feat-B: 基于 feat-A 开发的功能 B(因为 B 依赖 A 的代码)。

现在,feat-A 因为某些原因被废弃了(或者已经合并进 master 了,或者你想把 feat-B 独立出来),你需要把 feat-B 单独移到 master 上,并且不要 feat-A 的那些提交

1. 现状图示

Plaintext

1
2
3
4
5
A --- B (master)
\
C --- D (feat-A)
\
E --- F (feat-B)

如果你直接在 feat-B 上执行 git rebase master,Git 默认会沿着线往回找,把 C, D, E, F 全部拿过来。这通常不是你想要的,你不想要 C 和 D。

2. 目标图示

你想达到的效果是把 EF 摘出来,直接接在 B 后面:

Plaintext

1
2
3
4
A --- B (master)
|
\
E' --- F' (feat-B)

(注意:C 和 D 依然在 feat-A 上,但 feat-B 已经和它们没关系了)

3. 使用 git rebase --onto

你需要告诉 Git 三件事:

  1. 嫁接到哪? master
  2. 从哪剪断? feat-A (意思是从 feat-A 之后开始算)
  3. 移动谁? feat-B

命令如下:

Bash

1
git rebase --onto master feat-A feat-B

翻译成人话就是:

“请把 feat-B 分支上,排除掉 feat-A 里的那些提交,剩下的部分(也就是 E 和 F),移植master 上去。”


另一个常用场景:删除中间的一段提交

虽然可以用 git rebase -i (交互式) 来做,但 --onto 也可以用来快速剔除一段提交。

假设你的提交历史是:

base — commit1 — commit2(坏的) — commit3(坏的) — commit4(好的) — HEAD

你想删掉 commit2commit3,直接让 commit4 接在 commit1 后面。

Bash

1
2
3
# 这里的 base 指的是 commit1 的哈希值
# 这里的 bad-commit-end 指的是 commit3 的哈希值
git rebase --onto <commit1_hash> <commit3_hash> current_branch

这会将 commit3 之后 的所有提交(也就是 commit4),嫁接到 commit1 上。

总结与记忆法

记住这三个参数的位置:

1
git rebase --onto 目的地 剪切点(不含) 当前分支
  • 参数 1 (Target): 地基。新的树根。
  • 参数 2 (Upstream): 以前的旧树根(你想从哪里把树枝砍断)。Git 会忽略这个点以及它之前的所有 commit。
  • 参数 3 (Branch): 树枝的顶端。

风险提示

执行这个命令后,可能会遇到**冲突 (Conflict)**。因为你跳过了中间的某些提交(比如上面例子中的 C 和 D),如果 E 和 F 依赖于 C 或 D 修改过的代码,Git 就不知道该怎么合并了,这时你需要手动解决冲突。

使用举例

之前在local开发分支,这个分支大家都在往里合代码,然后我现在的代码一直在rebase这个开发分支,但是后来计划有变,需要从开发分支的某个提交拉取一个新的开发分支,然后我的代码需要合并到这个新的开发分支上:

1
git rebase --onto origin/develop-<新开发分支> origin/develop-<老开发分支> <特性分支名>

仓库推送

标准流程就是:先提交代码入库,再执行命令发布。

为了保证公司内部引用的版本规范和安全,建议你严格按照以下 4 个步骤操作:

1. 修改版本号 (Version Bump)

在提交代码前,非常重要的一步是修改版本号。

通常是在 setup.py、pyproject.toml 或 init.py 里。

  • 比如把 1.0.1 改成 1.0.2
  • 原因:如果不改版本号,同事的 pip 可能会认为没有更新,拉不到你的新代码。

2. 提交代码 (Git Commit & Push)

把修改后的代码(包含新的版本号)推送到 Git 仓库。

1
2
3
git add .
git commit -m "fix: 修复了某某bug, 版本升级到 1.0.2"
git push origin master
  • 这一步是为了存档,确保“代码库”里有这份代码。

3. 打包构建 (Build)

通常你需要把源码打包成 Python 的安装包(.whl 或 .tar.gz)。

在项目根目录下执行(示例):

1
python setup.py sdist bdist_wheel

执行完后,你的 dist/ 目录下会生成类似 my-library-1.0.2.tar.gzmy_library-1.0.2-py3-none-any.whl 的文件。

4. 上传发布 (Rsync)

最后一步,才是用那个 rsync 命令,把刚才打好的包,传到公司的私有源服务器上。

总结

顺序不能乱:

  1. 改版本号 (否则发了也是白发)
  2. Git Push (保存源码)
  3. Build (生成安装包)
  4. Rsync (上架给别人用)