一篇文章入门Git工具

文章目录

Git中的四个区域

git checkout是老命令,功能很多,既能切分支,也能恢复文件,语义容易混淆;git switch是后来拆分出来的新命令,专门用于切换分支

Workspace:工作区

定义:工作区是当前项目目录中真实存在于磁盘上的文件,也就是开发者正在编辑、创建、删除或重命名文件的地方

特点

  • 工作区中的文件是真实存在于磁盘上的文件
  • 保存的是当前可见的工作状态
    当前仓库最近一次提交中有一个文件app.py,内容是print("Hello"),然后在编辑器中将它改成print("Hello Git"),此时print("Hello Git")就是工作区中的当前可见状态
    可以用这个过程理解:Repository 中的版本:app.py -> print("Hello"),修改文件后Workspace 中的状态:app.py -> print("Hello Git"),此时运行git status,Git会发现modified: app.py,即工作区中的文件内容已经和当前HEAD指向的提交版本不同了
  • 工作区中的状态可能包括新建文件、修改文件、删除文件、重命名文件,即对文件的增删改查

常用命令

  • git status:用于查看工作区和暂存区相对于当前HEAD的状态
  • git diff:用于比较工作区与暂存区之间的差异

Index / Stage:暂存区

定义:暂存区保存的是下一次commit将要提交的内容清单

特点

  • 暂存区不是整个工作区的自动快照,而是通过git add主动加入的内容
  • 可以被理解为"下一次提交的预备区域"
  • Git允许开发者只暂存部分修改,从而把多个修改拆分为多个不同的commit

常见命令

  • git add <file>:将工作区中的修改加入暂存区
  • git diff --cachedgit diff --staged:用于比较暂存区与当前HEAD之间的差异,也就是查看下一次commit将提交哪些内容

Repository:仓库区 / 本地仓库

定义:本地仓库保存的是项目的提交历史,也就是一系列commit对象

特点

  • 每一次commit都会生成一个不可变的历史版本
  • 本地仓库位于项目目录中的.git文件夹内
  • HEAD通常会指向当前所在的分支,而当前分支再指向最新的commit
    例如,在main分支上git switch main,此时Git的指向关系是HEAD → \to → main → \to → 最新commit,如果继续commitmain分支会自动向前移动
  • 在detached HEAD状态下,HEAD会直接指向某个具体的commit

常用命令

  • git commit:将暂存区中的内容提交到本地仓库,形成一个新的历史版本

Remote:远程仓库

定义:远程仓库是保存在远程服务器上的Git Repository,例如Github、Gitee上的仓库

作用:团队协作、云端备份、多设备同步、发布和托管代码

常用命令

  • git push:将本地仓库中的提交推送到远程仓库
  • git fetch:从远程仓库中获取最新提交,但不会自动合并到当前分支
  • git pull:从远程仓库获取最新提交,并尝试合并到当前分支

Git分支

Git分支的作用:用于让不同任务彼此独立,例如功能开发、Bug 修复、实验性代码等。通过分支,可以避免不同修改混在一起,从而降低代码管理和版本发布的复杂度

在Git仓库中,每一次commit都会形成一个历史节点

例如:A ------ B ------ C,默认情况下,Git 仓库通常会有一个主分支,例如main

text 复制代码
A ------ B ------ C(main)
          ▲
          │
         HEAD

这里的main指向 Commit C。需要注意的是,main并不是"永远指向整个仓库最新的Commit",而是指向main这条分支上的最新Commit

在Git中,分支本质上是一个可以移动的指针。它指向某个具体的commit,并且会随着新的提交不断向前移动

为什么需要分支?例如你正在开发一个在线商城,目前已经完成并上线了版本A ------ B ------ C(main)。此时,老板说需要增加"支付功能",同时测试人员又发现"登录页面有 Bug"。如果没有分支,你只能直接在当前代码上修改

text 复制代码
A ------ B ------ C(main)
            │
            ▼
         修改支付

但是,支付功能可能还没有写完,Bug 修复也可能只完成了一半。此时如果客户要求"先紧急修复登录 Bug 并上线",就会很麻烦,因为支付功能的半成品代码已经混入当前代码中,难以单独发布

使用分支后,可以把不同任务隔离开来,即支付功能开发 → feature-payment登录 Bug 修复 → fix-login,这样,功能开发和 Bug 修复可以互不影响

在分支上继续开发

一个软件开发案例:假设公司正在开发"学生管理系统",目前已经完成了A 初始化项目 ------ B 登录功能 ------ C 学生成绩查询,当前历史A ------ B ------ C(main),现在老板要求增加"导出 Excel"的功能

可以先创建一个新的分支git branch export-excel,此时mainexport-excel都指向Commit C:

text 复制代码
A ------ B ------ C(main)
          │
          └── export-excel

不过,git branch export-excel只是创建分支,并不会自动切换到该分支。如果要切换到新分支,需要执行git switch export-excel,也可以直接创建并切换到新分支git switch -c export-excel,切换之后,可以理解为

bash 复制代码
A ------ B ------ C(main)
            │
      export-excel
            │
          HEAD

也就是说,当前位于export-excel分支,以后在这个分支上产生的新提交,都会让export-excel分支向前移动,而不会影响main分支

export-excel分支上开发完成后,可以执行

bash 复制代码
git add .
git commit -m "新增 Excel 导出功能"

此时会产生新的 Commit D

bash 复制代码
A ------ B ------ C(main)
          \
           D(export-excel)

如果继续提交,又会产生 Commit E

bash 复制代码
A ------ B ------ C(main)
          \
           D ------ E(export-excel)

因此,分支可以理解为:从某一个 Commit 开始,继续向前发展的一条新的时间线

在 export-excel 分支开发的同时,main 分支也可以继续进行其他修改。例如,main分支上产生了 Commit F

bash 复制代码
           D ------ E(export-excel)
          /
A ------ B ------ C
          \
           F(main)

此时,mainexport-excel已经产生了不同的提交历史

合并分支

export-excel功能开发完成后,如果希望把它合并回main,可以先切换回main

bash 复制代码
git switch main

然后执行合并

bash 复制代码
git merge export-excel

合并之后,可能会生成一个新的 Merge Commit,例如 G

bash 复制代码
           D ------ E(export-excel)
          /       \
A ------ B ------ C ------ F ------ G(main)

此时,main分支就包含了export-excel分支中的修改

为什么Git合并不会复制整个项目?因为 Git 采用的是Commit + 指针的机制 ⇒ \Rightarrow ⇒Git 管理的核心不是"复制两个完整文件夹",而是管理一系列 Commit 以及指向这些 Commit 的分支指针

bash 复制代码
A ------ B ------ C    main
      \
       D ------ E  feature

其中,A、B、C、D、E 都是 Commit。每个 Commit 都记录了当前项目的文件快照,同时也记录了自己的父 Commit。Git 在内部会对对象进行压缩和复用,因此并不是每次都简单复制整个项目文件夹

Git 合并的不是两个文件夹,而是两条 Commit 历史

假设有如下历史

bash 复制代码
A ------ B ------ C      main
      \
       D ------ E    feature

这里,mainfeature的共同祖先是 Commit B。Git 在合并时会先找到共同祖先,也叫 Merge Base,即Merge Base:B,然后 Git 会比较B 到 main 发生了什么修改B 到 feature 发生了什么修改,最后,Git 会尝试把两边的修改合并起来

Git Merge 的几种常见情况

  1. Fast-forward:快进合并
    特点:只移动分支指针,不产生新的 Merge Commit
    如果main分支在创建feature之后没有新的提交,那么合并feature时,Git 不需要创建新的 Merge Commit,只需要把main指针向前移动到 E 即可
bash 复制代码
当前历史:
A ------ B ------ C    main
          \
           D ------ E   feature

执行:
git switch main
git merge feature

合并后:
A ------ B ------ C ------ D ------ E
                    │
                   main
                    │
                 feature
  1. Three-way Merge:三方合并
    如果两个分支都各自向前发展了,就不能简单快进合并
bash 复制代码
       D ------ E feature
      /
A ------ B
      \
       C ------ F main

Git 需要比较三方内容:共同祖先 B;main 分支的修改;feature 分支的修改

然后生成一个新的 Merge Commit:
       D ------ E
      /      \
A ------ B        M
      \      /
       C ------ F
这里的 M 就是 Merge Commit
Merge Commit 有两个父节点,可以同时保留两条分支的开发历史
  1. Already up to date
    如果当前分支已经包含了要合并分支的全部内容,再执行合并命令时,Git 会提示Already up to date.
bash 复制代码
A ------ B ------ C ------ D(main)
          \
           D(feature)

或者更一般地说,只要 feature 的提交已经被 main 包含,那么在 main 上执行git merge feature,Git 就不会产生新的 Commit,因为没有新的内容需要合并
  1. 自动合并成功
    如果两个分支修改的是不同文件,或者修改的是同一个文件中的不同位置,Git 通常可以自动合并成功
bash 复制代码
main 修改了 login.py
feature 修改了 export.py

这种情况下,Git 一般可以直接完成合并

main 修改了 app.py 的第 10 行
feature 修改了 app.py 的第 100 行

如果两处修改没有冲突,Git 也通常可以自动合并
  1. 合并冲突
    如果两个分支修改了同一个文件中的同一位置,Git 可能无法判断应该保留哪一方的内容,于是产生冲突
    例如,两个分支的共同祖先中有a=10main分支修改为a=20feature分支修改为a=30,此时 Git 不知道应该保留 20 还是 30,于是会产生冲突标记
bash 复制代码
<<<<<<< HEAD
a = 20
=======
a = 30
>>>>>>> feature

其中,<<<<<<< HEAD表示当前分支的内容,=======表示分隔线,>>>>>>> feature表示另一个分支的内容

如果最终希望改成a=25可以手动编辑文件,删除冲突标记,并保留最终内容a=25,然后执行git add <file>git commit就可以完成合并。如果不想继续这次合并,可以执行git merge --abort,该命令会尽量恢复到合并之前的状态

Git Merge的常用选项

  • --no-ff:默认情况下,如果可以进行 Fast-forward 合并,Git 会直接移动分支指针,不产生新的 Merge Commit。如果希望即使可以 Fast-forward,也强制创建 Merge Commit,可以使用git merge --no-ff feature,这种方式可以保留"曾经开过一个feature分支"的历史痕迹
  • --ff-only:如果只允许快进合并,可以使用git merge --ff-only feature,只有在能够快进合并的情况下才允许合并。如果当前情况不能 Fast-forward,Git 会直接失败,不会生成 Merge Commit。这通常用于希望保持线性提交历史的场景

bash命令

  1. 查看所有分支
    git branch:查看本地分支
    git branch -r:查看远程跟踪分支 → \to →git branch -r看到的是本地保存的远程跟踪分支,例如origin/main,并不是实时访问远程服务器后的结果。如果想要更新远程跟踪分支信息,可以先执行git fetch
    git branch -a:查看本地分支和远程跟踪分支
bash 复制代码
git branch
* main
  develop
  feature-login
其中,* 表示当前所在的分支

git branch -r
origin/main
origin/dev

git branch -a
main
dev
remotes/origin/main
remotes/origin/dev
  1. git branch feature-payment:创建分支,该命令会在当前HEAD所在的Commit上创建一个新分支feature-payment
bash 复制代码
例如,当前位于 main 分支,并且 main 指向 Commit C
A ------ B ------ C(main)
             │
           HEAD

执行git branch feature-payment
之后,main 和 feature-payment 都指向 Commit C,但当前仍然停留在 main 分支
A ------ B ------ C(main)
            │
      feature-payment

HEAD → main

需要注意:git branch feature-payment只创建分支,不会自动切换分支

  1. git switch feature-payment:切换分支,该命令会将当前工作分支切换到feature-payment
bash 复制代码
切换之后,可以理解为
A ------ B ------ C(main)
            │
     feature-payment
            │
           HEAD

更准确地说:HEAD → feature-payment → C
  1. git switch -c feature-payment:创建并切换分支,该命令等价于git branch feature-paymentgit switch feature-payment,也就是说,它会先创建feature-payment分支,然后立即切换到该分支
  2. git branch -d feature-payment:该命令用于删除已经合并过的本地分支,如果分支还没有被合并,Git 通常会拒绝删除,以避免丢失提交
    如果确认要强制删除,可以使用git branch -D feature-payment,其中-d表示安全删除、-D表示强制删除
  3. git branch -m old-name new-name:重命名分支,该命令会将本地分支old-name重命名为new-name
    如果要重命名当前所在分支,也可以写成git branch -m new-name
  4. git merge:合并分支
    git merge feature-payment:将feature-payment分支合并到当前所在分支
bash 复制代码
git switch main
git merge feature-payment
把 feature-payment 中的修改合并回 main

如果希望远程仓库中的main分支也包含本地合并结果,需要执行git push origin main,完整的流程通常是git switch maingit merge featuregit push origin main

  1. git init:初始化当前目录为 Git 仓库
    它不会创建新的项目目录,而是在当前目录下创建一个.git目录
    .git目录中保存了 Git 的所有版本历史、分支、配置和对象信息,是整个本地仓库的核心
  2. git init project-name:初始化一个新的项目目录,创建一个名为project-name的目录,并在该目录中初始化Git仓库,等价于mkdir project-name → \to →cd project-name → \to →git init
  3. git clone <url>:通过远程仓库地址url克隆一个已有仓库到本地
    git clone https://github.com/user/project.git,克隆后,本地会自动拥有工作区、.git本地仓库、远程仓库别名origin、远程跟踪分支origin/main
  4. git remote add origin <url>:关联远程仓库,如果本地仓库是通过git init创建的,可以通过git remote add origin <url>关联远程仓库,其中origin是远程仓库的默认别名、url是远程仓库的真实地址,例如git remote add origin https://github.com/user/project.git
  5. git config --list:查看所有配置
bash 复制代码
user.name=Tom
user.email=tom@gmail.com
  1. git config --global --edit:打开并编辑全局配置文件
    在 macOS 和 Linux 中,全局配置文件通常是~/.gitconfig
bash 复制代码
[user]
	name = Tom
	email = tom@gmail.com
  1. git config --global user.name "Tom":设置用户名,设置后以后提交时的默认会显示Author: Tom
  2. git config --global user.email "tom@gmail.com":设置邮箱,设置后以后提交时默认会使用该邮箱作为提交者邮箱
    需要注意,user.nameuser.email只是 Git 提交记录中的作者信息,不等同于GitHub、或Gitee的登录账号
  3. git add <file>:添加文件到暂存区
  4. git mv <old> <new>:重命名文件,并将重命名操作记录到暂存区中
  5. git commit -m "message":将暂存区中的内容提交到本地仓库,形成一个新的 Commit
    需要注意,git commit只会提交已经通过git add加入暂存区的内容。没有加入暂存区的修改不会被提交
  6. git push:推送本地分支到远程仓库
    git push <远程仓库名> <本地分支名>:将本地分支推送到指定远程仓库,例如本地分支为feature-login、远程仓库名为origin,则git push origin feature-login。如果远程仓库中还没有feature-login分支,Git 会自动在远程仓库中创建一个同名分支,并把本地提交推送过去
  7. git push -u <远程仓库名> <本地分支名>:推送并建立追踪关系
    git push -u origin feature-login:将本地feature-login分支推送到远程origin仓库,同时建立本地feature-login与远程origin/feature-login的追踪关系,建立追踪关系之后,在feature-login分支上通常可以直接执行git pushgit pull,而不需要每次都写完整的远程仓库名和分支名
  8. git push <远程仓库名> <本地分支名>:<远程分支名>:将本地分支推送到一个不同名的远程分支
    git push origin hotfix:hotfix-v2:将本地的hotfix分支推送到远程的hotfix-v2分支

git clone、git fetch、git pull、git restore对比

bash 命令 是否访问远程仓库 是否修改本地仓库 是否修改暂存区 是否修改工作区 是否自动合并
git clone 是,创建新的本地仓库 是,初始化暂存区 是,检出默认分支 不涉及
git fetch 是, 更新远程跟踪分支
git pull 是,下载并合并或变基 通常会 通常会
git restore 否,不创建 Commit 可能会 可能会

检出默认分支:"检出"可以被理解为将某个分支或某个默认的 Commit 对应的文件内容放到本地的工作区中,"默认分支"则是远程仓库指定的主要分支

  1. git clone <url>:从远程服务器url复制一个 Git 仓库到本地
    执行git clone后,Git 通常会完成以下工作:从远程仓库下载提交历史 → \to →下载分支和标签信息 → \to →创建本地项目目录 → \to →创建.git本地仓库 → \to →建立远程仓库别名origin → \to →建立远程跟踪分支,例如origin/main → \to →创建并检出默认本地分支,例如main → \to →将默认分支内容写入工作区 → \to →最终得到project/.git/src/README.md
    需要注意,git clone默认通常只创建一个本地分支,也就是远程仓库的默认分支,例如main
    其他远程分支通常不会立即变成本地分支,而是以远程跟踪分支的形式存在,例如origin/devorigin/feature-login
    如果要基于远程分支创建本地分支,可以执行git switch dev,如果本地还没有dev分支,而远程存在origin/dev,Git 通常会自动创建本地dev分支,并与origin/dev建立追踪关系
  2. git fetch:从远程仓库中下载最新提交、分支和标签信息,但不修改当前工作区,也不自动合并 ⇒ \Rightarrow ⇒git fetch只更新本地保存的远程跟踪分支
    可以通过git diff main origin/main查看本地main分支和远程跟踪分支origin/main之间的文件差异
bash 复制代码
远程仓库
A ------ B ------ C ------ D(origin/main)

本地当前分支
A ------ B ------ C(main)

在本地执行 git fetch 之后,本地的远程跟踪分支 origin/main 会前进到 D
A ------ B ------ C ------ D(origin/main)
A ------ B ------ C(main)
此时 origin/main 前进了;main 没有动;工作区也没有动
  1. git pull:拉取远程仓库的提交,并将其合并到当前所在的本地分支
    默认情况下:git pull = git fetch + git mergegit fetch负责下载远程更新,即远程仓库 → 本地仓库中的远程跟踪分支;而git merge负责把远程跟踪分支合并到当前本地分支。因此,执行git pull origin main可以理解为git fetch origin main → \to →git merge origin/main
    如果启用了 rebase 模式:git pull = git fetch + git rebase

Merge:保留分叉历史和合并轨迹

bash 复制代码
假设当前历史如下:
A ------ B ------ C main
     \
      D ------ E feature

在 main 分支执行:git merge feature
合并后:
A ------ B ------ C ------ M main
     \        /
      D ------ E feature
其中,M 是新的 Merge Commit。

Rebase:整理成线性历史 ⇒ \Rightarrow ⇒Rebase 的本质不是简单移动原来的提交,而是基于原来的修改内容创建新的提交

bash 复制代码
假设当前历史如下:
A ------ B ------ C main
     \
      D ------ E feature

在 feature 分支执行:git rebase main
变基后:
A ------ B ------ C main
           \
            D' ------ E' feature
需要注意,D' 和 E' 是新的提交
它们和原来的 D、E 修改内容可能相同,但提交哈希值不同

在 Git 中,Commit Hash 是提交的唯一标识,可以理解为提交的"身份证"。在 Rebase 中,D 和 D' 的修改内容可能一样,但由于父提交变了,所以哈希值也会变

  1. git restore:恢复文件或撤销文件修改
    git restore是 Git 后来引入的命令,主要是为了拆分旧命令git checkout的复杂功能
    丢弃工作区的未暂存修改:如果修改了文件,但还没有执行git add,想放弃这些修改,让文件恢复到最近一次提交的状态,可以执行git restore <file>。例如,丢弃对 index.html 的修改git restore index.html;如果想恢复当前目录下所有被修改的文件,可以执行git restore
    取消暂存:如果已经使用git add将文件加入暂存区,但想把它撤回到"已修改但未暂存"的状态,可以执行git restore --staged <file>。例如,git restore --staged index.html ⇒ \Rightarrow ⇒作用是把文件从暂存区撤回,工作区中的实际修改仍然保留,即git restore --staged不会删除对文件的修改,只是取消暂存
    同时恢复暂存区和工作区:如果想让文件彻底回到HEAD状态,可以执行git restore --staged <file>git restore <file>,第一步取消暂存,第二步丢弃工作区修改

参考文献

  1. 常用 Git 命令清单
  2. ChatGPT Plus
相关推荐
Irissgwe2 小时前
第 6 篇:Git 分支进阶:分支策略、stash、bugfix 和 feature 开发
git
wok1572 小时前
Git Bash 执行中文命令报错 127:MSYS 参数编码 bug 排查与修复
git·bug·bash
霸道流氓气质17 小时前
CodeBuddy中使用 MCP 工具将 XMind 思维导图转换为 Markdown —— 实操流程汇总
git·myeclipse·xmind
OsDepK1 天前
mimo code安装教程
git·电脑·ai编程
NexTunnel1 天前
公司老项目还在 SVN,远程维护怎么做更稳?
git·svn·gitlab·远程工作·nextunnel
满天星83035772 天前
【Git】原理及使用(七) (多人协作 [下])
git
郡杰2 天前
Git基础与开发平台搭建
git·后端
钟智强2 天前
我警告了 329 天
git·php·wordpress·webshell
乐观的冷风2 天前
我们需要将文件添加到暂存区才能提交,而移除文件后是无法添加到暂存区的,那么怎么移除一个文件让GIT不再将其纳入版本控制呢?上图中GIT已经给出了说明:
git