The Missing Semester of Your CS Education 学习笔记以及一些拓展知识(六)

发布于:2025-07-25 ⋅ 阅读:(17) ⋅ 点赞:(0)

The Missing Semester of Your CS Education 学习笔记以及一些拓展知识

以下是使用The Missing Semester of Your CS Education的个人学习笔记,方便之后翻阅

版本控制Git

Git的入门学习:参看这篇文章,笔记也参考了这篇文章。

笔记部分

说到版本控制,其实这是实际做一个中大型项目所必须的。在Git之前,常用的版本控制器还有SVN等,但Git一经推出就迅速占领了市场,现在已经成为版本控制绝对的主流。

Git的基本工作原理
Git 的核心工作原理:快照而非差异

要理解Git,首先要明白它和许多旧的版本控制系统(如SVN)在核心思想上的根本区别。

  • 其他系统 (如SVN): 将文件的版本历史存储为一系列的差异(Diffs)。它们记录了文件从一个版本到下一个版本的具体变化。当你需要检出某个版本时,系统会从初始文件开始,依次应用每一个差异补丁,最终得到你想要的版本。

  • Git: 将数据视为一系列的快照(Snapshots)。当你进行一次提交(commit)时,Git会获取你项目中所有文件的状态,并为该状态制作一个“快照”,然后保存一个指向该快照的引用。为了效率,如果文件没有被修改,Git不会重新存储该文件,而只是保留一个指向上一个已存储文件的链接。

Git 的三大工作区域

你的项目文件会存在于以下三个区域之一:

  • 工作目录 (Working Directory)
    这是你电脑上实际看到和编辑的文件所在的文件夹。它是从Git仓库(.git目录)中提取出来的某个版本的项目文件。你可以随心所欲地修改这里的文件。
  • 暂存区 (Staging Area / Index)
    这是一个位于.git目录中的文件,它保存了你下一次要提交的内容的信息。你可以把它想象成一个“购物车”或者“草稿箱”。
    使用 git add 命令,你可以把你工作目录中的修改“放入”暂存区,表示你希望这些修改被包含在下一次的快照中。

    在Git的术语中暂存区被称为Index,但一般我们还是叫做暂存区

  • Git 仓库 (Git Repository / .git directory)
    这是Git用来保存项目元数据和对象数据库的地方,也是Git最重要的部分。当你执行 git commit 命令时,Git会抓取暂存区里的内容,生成一个永久性的快照,并将其保存在Git仓库中。

Git的基本最基本的工作流程

  • 工作目录中修改文件。
  • 使用 git add 将想要提交的修改添加到暂存区
  • 使用 git commit 将暂存区的内容生成快照并永久存入本地仓库
  • (可选)使用 git push 将本地仓库的更新同步到远程仓库
Git的核心对象

刚刚说了Git的工作区以及在不同工作区中最最基本的工作流程,我们现在需要进一步了解Git是怎么在暂存区和Git仓库中组织我们的代码文件的。

Git的四个对象

Git的核心是一个内容可寻址(content-addressable)的文件系统。简单来说,你存储的任何内容,都会通过其内容的哈希值来索引和检索。这些存储的基本单元就是Git对象。它们都保存在Git仓库的 .git/objects/ 目录下。Git共有四种主要的对象类型:Blob、Tree、Commit 和 Tag

  1. blob (Binary Large Object,数据对象):
    Blob对象,或称为“数据块”,是用来存储文件内容的。它是Git对象中最基础、最简单的一种。
    工作原理:
    • 当使用 git add 一个文件时,Git会获取该文件的内容(注意:仅仅是内容,不包括文件名、权限或时间戳等元数据)。
    • Git会用zlib算法对文件内容进行压缩。
    • 然后,Git会在压缩后的内容前加上一个头部信息,格式为 blob 文件内容长度\0。例如,一个内容为 “hello” 的文件,其头部就是 blob 5\0。
    • 最后,Git对“头部+压缩内容”这个整体计算出一个40位的SHA-1哈希值。这个哈希值就是该Blob对象的ID。
    • 因为哈希值是根据文件内容计算的,所以只要文件内容完全相同,无论文件名是什么,在Git仓库中它们都指向同一个Blob对象。这极大地节省了存储空间。
  2. tree(树对象):
    它用来表示一个目录结构。一个 tree 对象里记录了它所包含的文件和子目录的信息,包括文件名、文件权限,以及指向对应 blob 或子 tree 对象的哈希值。它解决了Blob对象不存储文件名的问题。
    工作原理:
    • Tree对象存储的是一个列表,每一行代表一个文件或子目录。
    • 列表的每一行包含:文件模式(权限)、对象类型(blob或tree)、对象的SHA-1哈希值、文件名。
    • 它就像一个清单,将文件名和对应的Blob对象(文件内容)或另一个Tree对象(子目录)关联起来。
    • 与Blob类似,Tree对象本身的内容(即这个清单列表)也会被压缩并计算出其自身的SHA-1哈希值来作为ID。
  3. commit (提交对象):
    是Git历史记录的核心。它将某个时间点的项目状态(一个Tree对象)和一系列元数据打包在一起,形成一次历史快照
    Commit对象的内容包含以下关键信息:
    • 一个顶层Tree对象的SHA-1哈希: 指向代表了本次提交时项目根目录的Tree对象。
    • 一个或多个父Commit对象的SHA-1哈希:
    • 普通的提交只有一个父Commit。
    • 第一个提交(root commit)没有父Commit。
    • 由git merge产生的合并提交,通常有两个或更多父Commit。
    • 作者(Author): 原始代码的创作者信息和创作时间戳。
    • 提交者(Committer): 将代码提交到仓库的人的信息和提交时间戳。(在多人协作的rebase等场景下,二者可能不同)。
    • 提交信息(Commit Message): 你在 git commit -m “…” 中写的描述文字。

每次你执行 git commit,Git 实际上是在创建一个完整的项目快照。它会创建一个指向顶层 tree 对象的 commit 对象。如果某个文件在两次提交之间没有变化,新的 tree 对象会直接复用指向旧的 blob 对象的指针。这使得创建快照非常高效,并且节省空间

  1. Tag 对象
    Tag对象,或称为“标签对象”,用于给某个特定的Commit打上一个有意义的、固定的标签,通常用于版本发布(如 v1.0)。Git中有两种标签:轻量标签(lightweight)和附注标签(annotated)。只有附注标签才是真正的Tag对象。
    工作原理 (附注标签):
    • 轻量标签只是一个指向某个Commit的指针(类似分支),它本身不创建对象。
    • 附注标签会创建一个独立的Tag对象,其内容包含:
    • 一个Commit对象的SHA-1哈希: 指明这个标签打在了哪个提交上。
    • 标签名: 例如 v1.0。
    • 标签创建者信息和时间戳。
    • 一个标签信息: 类似于提交信息,可以对该版本进行详细描述。
    • (可选)GPG签名,用于验证
对象类型 存储内容 指向的对象 核心作用
Blob 文件的原始二进制内容 存储文件数据
Tree 目录清单(文件名、权限、对象哈希) Blob对象 或 其他Tree对象 描述目录结构
Commit Tree哈希、父Commit哈希、元数据 一个Tree对象 和 父Commit对象 记录历史快照和版本沿袭关系
Tag Commit哈希、标签名、元数据 一个Commit对象 为特定历史版本提供永久性命名
对象之间的关系与工作流程:

这四种对象通过哈希值链接在一起,构成了一个有向无环图(DAG)。

一次典型的 git commit 流程背后发生了什么:

  • 你修改了文件 a.txt 和 b.txt。
  • git add a.txt b.txt:
  • Git为 a.txt 的新内容创建一个Blob对象。
  • Git为 b.txt 的新内容创建一个Blob对象。
  • Git更新暂存区(Index),记录文件名与对应Blob哈希的映射。
  • git commit -m “My first commit”:
  • Git根据暂存区内容创建一个顶层的Tree对象,这个Tree对象里包含了指向 a.txt 和 b.txt 对应Blob对象的记录。如果项目有子目录,还会递归地创建子Tree对象。
  • Git创建一个Commit对象,这个Commit对象:
  • 指向刚刚创建的顶层Tree对象。
  • 指向当前分支所在的那个Commit作为父Commit。
  • 记录下你的作者/提交者信息和提交信息。
  • 最后,Git将当前分支的指针(比如main)移动到这个新创建的Commit对象的哈希上。
对象的引用

如果Git只有40位的SHA-1哈希值,那它对人类来说几乎是无法使用的。我们不可能记住 e475e5a20d326f20478a179c32cfe33a52144579 这样的字符串。因此,Git提供了一套非常友好的机制,允许我们使用易于记忆的名称来作为指向Git对象的指针。这些“命名的指针”在Git中被统称为引用(References,简称 refs)

工作原理:
从根本上说,一个引用就是一个位于 .git/refs/ 目录下的普通文本文件。这个文件里面只包含一行内容:一个40位的SHA-1哈希值,或者指向另一个引用的路径。

Git中最常见、最重要的引用有三种:分支(Branches)、轻量标签(Lightweight Tags),以及一些特殊的引用,如 HEAD。

  1. 分支 (Branches)
    本质: 分支就是一个可以移动的、指向某个Commit对象的指针。它的核心设计理念就是“变化”。
    工作原理: 当你在一个分支上工作并创建一个新的Commit时,这个分支的指针会自动向前移动,指向这个最新的Commit。
    物理存储: 它们存储在 .git/refs/heads/ 目录下。

  2. 轻量标签(Lightweight Tags)
    本质: 轻量标签是一个通常固定不变的、指向某个特定Commit的指针。它主要用于标记项目历史中重要的里程碑,比如版本发布(v1.0, v2.1.3等)。
    工作原理:

    • 这是一种“纯粹”的引用。它就是一个简单的文件,直接存储了一个Commit的SHA-1哈希值。
    • 它仅仅是给某个Commit起了一个别名,不包含任何额外信息。

    物理存储: 它们存储在 .git/refs/tags/ 目录下。

  3. 特殊引用(其中最重要的就是HEAD)
    本质: HEAD 是一个特殊的指针,它指向你当前所在的位置。
    工作原理: 大多数情况下,HEAD 是一个符号引用 (Symbolic Reference, or symref)。它本身不是直接指向一个Commit哈希,而是指向另一个引用,通常是一个分支。
    物理存储: HEAD 是一个位于 .git 根目录下的文件,即 .git/HEAD。

注意:

  • 引用是别名: 引用系统是Git为了方便人类使用,给底层复杂的SHA-1哈希对象起别名的一套机制。
  • 分支是为“变化”而设计的动态指针,而标签(特别是附注标签)是为“稳定”而设计的静态标记。
Git的安装和基础配置

对于一般的Linux系统其实都会预装Git,当然也可以使用对应的包管理器下载:

sudo apt install git 
git --version #检查是否安装成功。

Git在安装好之后一定要先进行下面的配置:

git config --global user.name "你的名字"  #设置您的用户名。
git config --global user.email "你的邮箱" #设置您的邮箱。
git config --global core.editor vim # 设置编辑器
git config --list --show-origin # 检查一下配置信息

如果使用了 --global 选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事情, Git 都会使用那些信息。 当你想针对特定项目使用不同的用户名称与邮件地址时,可以在那个项目目录下运行没有 --global 选项的命令来配置。(来自:ProGit中文版)

前两项配置至关重要,因为每一次提交都会记录作者信息

下面就是很多Git命令了,如果使用时发现问题,我们可以通过以下方式查看手册:

git help <verb>
git <verb> --help
man git-<verb>
Git的本地操作(个人的版本控制)
Git项目的创建
1. git init

git init 命令用于将一个普通的目录转变为一个 Git 仓库,让 Git 可以开始对这个目录下的文件进行版本控制。

基本使用方法:

1. 在现有项目目录中初始化
# 进入你的项目目录
cd my-project
# 执行初始化命令
git init

2. 初始化并创建一个新目录
# git init <新目录名>
git init new-awesome-project

在一个已经初始化的仓库中再次运行 git init 是安全的。它不会覆盖你已有的配置和历史,只会重新初始化模板等

常用参数:

  • -b <分支名> 或 --initial-branch=<分支名>
    (非常推荐使用) 这个参数用于指定初始分支的名称。在 Git 的较新版本中,默认的初始分支名已经从 master 趋向于 main。使用此参数可以明确设置你想要的初始分支名,避免后续重命名的麻烦。
# 初始化一个新仓库,并将其主分支命名为 main
git init -b main
  • –bare
    创建一个“裸仓库”(Bare Repository)。裸仓库没有工作目录,也就是说你看不到项目文件的实际样子,整个目录的内容就相当于 .git 目录里的内容。
    用途:裸仓库主要用作团队协作的服务器。开发者不会直接在裸仓库上进行编辑和提交,而是将各自的本地仓库的更改“推送”(push)到这个中央裸仓库中,并从中“拉取”(pull)别人的更改。
# 通常裸仓库的目录名以 .git 结尾,以示区别
git init --bare my-central-repo.git
  • -q 或 --quiet
    静默模式,只打印关键的错误和警告信息,不输出“Initialized empty Git repository…”这样的提示信息。在自动化脚本中比较常用。
2. git clone

git clone 命令用于从一个已经存在的远程 Git 仓库(例如在 GitHub, GitLab, Gitee 或你自己的服务器上)下载一份完整的副本到你的本地机器

主要作用:

  • 在本地创建一个与远程仓库同名的目录(除非你指定了新目录名)。
  • 将远程仓库的整个 .git 目录(包含所有历史记录、分支和标签)完整地复制下来。
  • 自动创建一个指向原始仓库地址的“远程连接”,默认名为 origin。这使得你将来可以方便地使用 git pull 和 git push 与远程仓库同步。
  • 自动检出(checkout)远程仓库的默认分支(通常是 main 或 master)的最新版本到你的工作目录,让你立刻可以开始工作。

基本用法:

git clone <仓库URL> [<本地新目录名>]

# 使用 HTTPS 协议克隆 (公开仓库或需要输入用户名密码的私有仓库)
git clone https://github.com/torvalds/linux.git
# 使用 SSH 协议克隆 (需要配置 SSH Key 的私有仓库,更安全便捷)
git clone git@github.com:torvalds/linux.git
# 克隆到指定的目录
git clone https://github.com/torvalds/linux.git my-linux-kernel

常用参数:

  • -b <分支名> 或 --branch <分支名>

如果你只关心某个特定的分支,而不是默认主分支,可以用这个参数直接克隆并检出该分支。

# 克隆仓库并直接切换到 develop 分支
git clone -b develop https://example.com/my-project.git
  • –depth <深度>

执行“浅克隆”(Shallow Clone)。它只会下载最近的 <深度> 次提交历史,而不是全部历史。–depth 1 表示只下载最新的那一次提交。
用途:对于历史非常庞大、文件非常多的项目,浅克隆可以极大地节省下载时间和磁盘空间。这在 CI/CD(持续集成/持续部署)环境中尤其有用,因为通常只需要最新的代码来构建和测试。

# 只克隆最新一次提交,不包含任何历史记录
git clone --depth 1 https://github.com/large/repository.git
  • –bare

和 git init --bare 类似,这会克隆一个裸仓库。你得到的是一个不包含工作目录的 .git 目录的副本。
用途:用于创建远程仓库的镜像,或者备份远程仓库。

git clone --bare https://github.com/my/project.git project.git.backup

–progress

在克隆过程中显示详细的进度条。在交互式终端中这通常是默认开启的,但在脚本中可能需要显式指定。

Git项目管理的核心操作
1. git status

这是一个经常使用的操作,它告诉你当前工作目录和暂存区的状态。这是你在进行任何操作前后都应该习惯性使用的命令,以确保你清楚地知道发生了什么。

使用方法:

git status

解读输出:

  • On branch [branch-name]: 显示你当前所在的分支。
  • Changes to be committed: 这部分列出了已经使用 git add 添加到暂存区的文件。这些是你下次 git commit 时会提交的内容。它们是“已暂存”状态。
  • Changes not staged for commit: 这部分列出了已修改但没有添加到暂存区的文件。Git 知道它们被改动了,但如果你现在提交,这些改动不会被包含进去。
  • Untracked files: 这部分列出了新创建的、Git 从未进行过版本控制的文件。Git 只是发现了它们,但完全不关心它们的内容,除非你使用 git add 来跟踪它们。

常用参数:

  • -s 或 --short:以更紧凑的格式显示状态,非常适合快速概览。
  • -b 或 --branch:在输出的顶部额外显示分支的详细信息,包括与远程分支的同步状态。
2. git add

git add 是连接工作目录和 Git 仓库的桥梁。它将你工作目录中的更改(新文件或修改过的文件)添加到暂存区,为下一次提交做准备。

核心作用:

  • 开始跟踪一个新文件。
  • 将已修改文件的当前内容快照放入暂存区。
  • 将一个被删除的文件标记为“已删除”,并放入暂存区。

基本使用方法

# 添加一个文件
git add file1.txt
# 添加多个文件
git add file1.txt file2.js
# 递归添加文件夹和文件夹下的所有文件
git add src/
# 添加当前目录下所有更改
git add .

常用参数:

  • -p 或 --patch
    (非常强大和推荐) 进入交互式的“补丁”模式。Git 会逐一展示文件中每一处修改(hunk),然后询问你是否要暂存这一块修改。你可以输入 y (yes), n (no), s (split, 将大块修改拆分成更小的块), q (quit) 等。
    用途:当一个文件里包含了多个不相关的修改时,你可以用 -p 参数只暂存其中一部分,从而实现更原子化、更清晰的提交。

  • -u 或 --update
    只暂存那些已经被 Git 跟踪的文件的修改和删除,不会暂存新创建的文件(untracked files)。

  • -A 或 --all
    暂存所有更改,包括新文件、被修改的文件和被删除的文件。在 Git 较新的版本中,git add . 的行为和 git add -A 基本一致。

3. git commit

它将暂存区中的所有内容创建成一个永久的快照,并保存在 Git 的历史记录中。每一次提交都是你项目历史中的一个节点。创建一个新的提交对象,包含一个唯一的 SHA-1 哈希值、作者信息、时间戳和提交信息。将暂存区的内容保存到本地仓库

使用方法:

git commit 
# 执行这条命令后,Git 会打开你配置的默认文本编辑器(通常是 Vim 或 Nano),让你输入详细的提交信息。提交信息的第一行是摘要(subject),空一行后可以写更详细的正文(body)。保存并关闭编辑器后,提交就完成了。

# git commit -m "你的提交信息"
git commit -m "Feat: Add user login functionality"

常用参数:

  • -m
    如上所述,直接在命令行提供提交信息。

  • -a 或 --all
    一个快捷方式,它会自动把所有已经跟踪过的文件的修改暂存起来,然后进行提交。相当于 git add -u 和 git commit 的合并。

# 对于已跟踪文件的修改,这条命令等同于:
# git add .
# git commit -m "message"
git commit -a -m "Fix: Correct a typo in the documentation"

注意:-a 参数不会添加未被跟踪的新文件(untracked files)。你必须先用 git add 手动添加新文件。

  • –amend
    (非常有用) 修改上一次的提交。这并不会真的“修改”历史(Git 的历史是不可变的),而是用一个新的提交来替换掉上一次的提交。
    用途:
    • 修改上一次的提交信息:比如你提交后发现信息里有错别字。
      git commit --amend # 这会打开编辑器让你重新编辑上一次的提交信息 
      
    • 将当前暂存区的更改合并到上一次提交中:比如你刚提交完就发现漏掉了一个文件或一处修改。
      # 修复/添加文件 
      git add forgotten-file.txt 
      # 使用 --amend 合并到上一次提交 
      git commit --amend --no-edit 
      # --no-edit 表示不修改提交信息,直接使用上一次的 
      

如果你的上一次提交已经被推送(push)到了远程仓库,绝对不要使用 --amend,因为它会修改你本地的历史,导致与远程历史不一致,给团队协作带来大麻烦。只对未推送的本地提交使用 --amend。

4. git rm

用于从 Git 的跟踪列表(暂存区)和工作目录中删除文件。它不仅仅是简单地删除文件,还会将“删除”这个操作记录到暂存区。

核心作用:

  • 将文件从工作目录中删除。
  • 将这次删除操作添加到暂存区,以便在下次提交时将文件从 Git 仓库中彻底移除。

使用方法:

# 删除 a.txt 文件
git rm a.txt

效果等同于你手动执行 rm <file> 然后再 git add <file>。

常用参数:

  • –cached
    只从暂存区(版本库)中删除,保留工作目录中的文件。
    git rm --cached my_config.json
    #执行后,my_config.json 仍然存在于你的工作目录中,但 Git 不再跟踪它的任何变化。
    #git status 会显示它是一个 “untracked file”。这个操作通常会配合 .gitignore 文件一起使用,在取消跟踪后,将文件名添加到 .gitignore 中,以防未来被意外 add。
    
  • -f 或 --force
    强制删除。如果一个文件在工作目录中有修改,并且这些修改还没有被提交,git rm 会拒绝删除它以防数据丢失。使用 -f 可以强制执行删除。

如果你已经手动用 rm 命令删除了一个文件,git status 会提示 deleted: <file>。此时,你只需要执行 git add <file> 或 git rm <file> 就能将这个删除操作暂存。

5. git mv

git mv是在 Git 的跟踪下,安全地重命名一个文件/目录,或将文件/目录移动到新的位置,并自动将这个操作暂存起来。其实git mv也可以用三个命名合起来:

  • 用 mv 命令移动或重命名文件。
  • 执行 git rm 将旧路径的文件从暂存区移除。
  • 执行 git add 将新路径的文件添加到暂存区。

使用方法:

# 1.重命名文件
# 语法: git mv <旧文件名> <新文件名>
git mv old-file.txt new-file.txt

# 2. 移动文件
# 语法: git mv <文件路径> <目标目录>
git mv README.md docs/

常用参数

  • -f 或 --force
    强制执行。如果目标路径已经存在一个文件,git mv 默认会失败以防止覆盖。使用 -f 可以强制覆盖目标文件。

  • -n 或 --dry-run
    “演习”模式。它只会显示将要执行的操作,但不会真的移动文件。这在你进行复杂操作前,想预览一下结果时非常有用。

  • -k
    在遇到错误时,跳过该错误并继续处理其他文件。

6. git restore

git restore 是一个相对较新(在 Git 2.23 版本中引入)的命令,它的出现是为了将 git checkout 中恢复文件的功能分离出来,使其职责更单一、语义更清晰。restore 的核心任务就是撤销工作目录或暂存区中的更改。

核心作用

  • 将工作目录中的文件恢复到暂存区中的状态,或 HEAD(最新一次提交)的状态。
  • 将暂存区中的文件“撤销”,使其返回到 HEAD 的状态(即 “unstage”)。0

使用方法:

# 1. 撤销工作目录中的修改
# 如果你修改了一个文件,但想放弃这些修改,让它回到最近一次提交(或暂存)后的样子。
# 假设你修改了 README.md,但想撤销这些修改
git restore README.md

# 2. 将文件从暂存区中移除(Unstage)
# 如果你用 git add 将一个文件添加到了暂存区,但后来决定这次提交不应包含它。
# 假设你已经执行了 git add README.md
# 现在想把它从暂存区拿出来
git restore --staged README.md

常用参数:

  • –staged
    指定操作目标是暂存区,用于“撤销暂存”(Unstage)。
  • –worktree
    (这是默认行为)指定操作目标是工作目录,用于“撤销修改”。
  • –source <commit>
    指定从某次特定的提交来恢复文件,而不仅仅是 HEAD。
# 将 README.md 文件恢复到上上次提交(HEAD~2)时的状态
git restore --source=HEAD~2 README.md
7. git reset

git reset 是一个用来操控提交历史的命令,它的核心动作是移动当前分支的 HEAD 指针.将当前分支的 HEAD 指针重置到指定的提交,并根据模式选择性地更新暂存区和工作目录。

三种主要模式
reset 命令的威力与危险并存,关键在于理解它的三种主要模式:–soft、–mixed(默认)和 --hard。

  1. git reset --soft :温柔重置
    • 动作:只移动 HEAD 指针到 。
    • 影响:
      • 仓库历史:HEAD 移动了。从旧 HEAD 到新 HEAD 之间的所有提交被“撤销”。
      • 暂存区:不变。所有被“撤销”的提交所包含的更改,现在全部处于已暂存状态。
      • 工作目录:不变。你的代码文件没有任何变化。
    • 典型场景:合并多个零碎的提交。你可以回退几个版本,然后将所有暂存的更改一次性地进行一个干净的提交。
       # 撤销最近两次提交,并将所有更改放入暂存区
       git reset --soft HEAD~2
      
  2. git reset --mixed :混合重置(默认模式)
    • 动作:移动 HEAD 指针,并且重置暂存区。
    • 影响:
      • 仓库历史:HEAD 移动了。
      • 暂存区:被清空,并更新为 时的状态。
      • 工作目录:不变。所有被“撤销”的提交所包含的更改,现在全部处于未暂存状态(在工作目录中)。
    • 典型场景:你想撤销几次提交,并且想重新组织这些更改(重新 add 并 commit)。
    # 撤销最近一次提交,并将更改保留在工作目录中
    git reset --mixed HEAD~1
    # 因为是默认模式,所以可以简写为:
    git reset HEAD~1
    
  3. git reset --hard :硬核重置(危险!)
    • 动作:移动 HEAD 指针,同时重置暂存区和工作目录。
    • 影响:
      • 仓库历史:HEAD 移动了。
      • 暂存区:被重置。
      • 工作目录:被重置!所有被“撤销”的提交所包含的更改,以及你在工作目录中所有未提交的更改,都将被永久删除。
    • 典型场景:你发现最近几次提交完全是错误的,想彻底、干净地回到某个历史版本,丢弃这期间的所有工作。
     # 彻底丢弃最近一次提交以及所有本地的未提交更改
     git reset --hard HEAD~1
    

reset 命令也可以作用于单个文件(git reset <file>),其效果等同于 git restore --staged <file>,即只用来撤销暂存。这是它在 restore 命令出现之前的一个历史用法。

8. git stash

git stash 是一个非常有用的工作流工具,它允许你临时保存未提交的更改(包括已暂存和未暂存的),以便将工作目录恢复到一个干净的状态。

例如:当你正在一个分支上开发某个功能,但突然需要切换到另一个分支去修复一个紧急 Bug 时,你的工作目录可能还很“脏”(有未完成的修改)。此时你不想为了这个未完成的功能创建一个临时的 commit。stash 就是为此而生。

使用方法:

  1. 储藏更改
# 将所有已跟踪文件的修改(包括暂存和未暂存的)保存起来
git stash
# 推荐的做法是加上说明信息,方便以后识别
git stash push -m "Refactoring user login form"
  1. 查看储藏列表
    Stash 是一个栈(Stack,后进先出),你可以储藏多次。
git stash list

#输出会是这样:
stash@{0}: On main: Refactoring user login form
stash@{1}: On main: WIP on feature/new-feature
stash@{0} 是最近一次储藏。
  1. 应用储藏
    有两种方式可以恢复储藏的更改:
  • git stash apply [stash名]:应用储藏,但不从储藏列表中删除它。
# 应用最近一次储藏
git stash apply
# 应用指定的储藏
git stash apply stash@{1}
  • git stash pop [stash名]:应用储藏,并自动从储藏列表中删除它。这是最常用的方式。
git stash pop
  1. 查看储藏内容
    在应用之前,你可能想看看某个储藏里到底改了什么。
# 以补丁(diff)的形式显示最近一次储藏的内容
git stash show -p
# 显示指定储藏的内容
git stash show -p stash@{1}
  1. 删除储藏
  • 如果你用 apply 应用了储藏,或者某个储藏你不再需要了,可以手动删除它。
git stash drop stash@{1}
  • 清空所有储藏(危险操作!)。
git stash clear

常用参数

  • push -m “message”:储藏并附带说明。
  • list: 查看列表。
  • pop: 应用并删除。
  • apply: 应用但不删除。
  • drop: 删除。
  • clear: 清空。
  • -u 或 --include-untracked:在储藏时,一并储藏未被跟踪的新文件。默认情况下 stash 只处理已跟踪的文件。
  • -a 或 --all:储藏所有文件,包括被 .gitignore 忽略的文件。

注意事项:

  • 储藏是本地的:Stash 存储在你的本地仓库的 .git 目录中,它不会随着 git push 被推送到远程仓库。它纯粹是你个人的本地工具。
  • 添加说明是个好习惯:当储藏列表变长时,没有说明信息的 WIP on … 会让你很难记起每个储藏是做什么的。坚持使用 git stash push -m “…”。
  • 可能会有冲突:如果在储藏后,你的分支又有了新的提交,那么在 apply 或 pop 储藏时,可能会发生合并冲突。解决方法和普通的合并冲突一样。
  • 不是长期存储方案:stash 设计初衷是用于临时、短期的状态保存。如果一项工作需要搁置较长时间,更规范的做法是为它创建一个分支并提交(即使是临时的 commit)。分支比 stash 更稳健、更清晰。
  • 定期清理:养成定期清理不再需要的 stash 的习惯,保持 git stash list 的整洁。
Git的分支与合并

关于Git的分支:
在 Git 中,一个分支本质上只是一个指向某个特定提交(commit)的、轻量级的、可移动的指针。当你创建一个新分支时,Git 只是创建了一个新的指针,它指向你当前所在的提交。这使得创建和切换分支的操作快如闪电。HEAD 是另一个特殊的指针,它指向你当前工作的本地分支。

1. git branch

branch操作是Git中分支操作的核心。

基本使用方法:

#1.列出分支
# 列出所有本地分支
git branch
#输出结果中,当前所在的分支会以 * 标记,并通常会高亮显示。

#2.创建一个新分支
# 语法: git branch <新分支名>
git branch feature/payment-gateway
#重要:这条命令只创建新分支,不会自动切换到该分支。你仍然停留在当前分支。

#3.删除一个分支
# 语法: git branch -d <要删除的分支名>
git branch -d feature/user-auth
# -d 是 --delete 的缩写,它会进行安全检查。如果要删除的分支上的工作还没有被合并到当前分支,Git 会阻止删除并给出提示,防止你意外丢失工作成果。

#4.重命名分支
# 语法: git branch -m <旧分支名> <新分支名>
git branch -m feature/payment-gateway feature/payment-integration

常用参数:

  • -a 或 --all
    列出所有分支,包括本地分支和远程跟踪分支(如 remotes/origin/main)
  • -d / --delete
    安全删除。只有当分支的工作被完全合并后才能删除。
  • -D
    强制删除。git branch -D 会忽略检查,直接删除分支。当你确定要丢弃某个功能分支上的所有工作时使用。
  • -m / --move
    重命名分支。
  • -M
    强制重命名,即使新分支名已经存在。
  • -v 或 -vv / --verbose
    显示更详细的信息。-v 会显示每个分支最后一次提交的哈希值和提交信息。-vv (非常有用) 还会显示与上游远程分支的跟踪关系(领先或落后多少次提交)。
  • –merged / --no-merged
    非常有用的过滤器,用于分支清理。
# 列出所有已经合并到当前分支的分支(这些通常是可以安全删除的)
git branch --merged
# 列出所有尚未合并到当前分支的分支
git branch --no-merged

分支的约定命名规范:
为了保持项目清晰,建议采用一致的分支命名规范。例如:

  • 新功能:feature/user-login、feature/shopping-cart
  • Bug修复:bugfix/issue-123、bugfix/null-pointer-exception
  • 发布:release/v1.2.0
  • 紧急修复:hotfix/security-patch

注意:

  • 请务必区分 git branch 和 git switch (或旧的 git checkout )。前者只创建指针,后者才是切换 HEAD 指针到目标分支上,让你开始在新分支上工作。git switch -c (或 git checkout -b ) 是一个快捷方式,可以一步完成创建和切换。
  • 远程分支管理:git branch 主要管理的是你的本地分支。删除一个本地分支 (git branch -d) 并不会影响到远程仓库中对应的分支。要删除远程分支,你需要使用 git push.
2. git checkout

git checkout 是一个功能非常强大的“多面手”命令,它的核心功能有两个:分支操作和恢复文件

checkout的分支操作正在被switch取代,恢复文件操作被restore命令取代

使用方法

# 1.分支操作
# 切换到一个已经存在的分支
git checkout develop
# 创建一个新分支并立即切换过去(等同于 git branch <name> + git checkout <name>)
git checkout -b new-feature-branch

# 2.恢复工作目录中的文件
# 丢弃工作目录中对 a.txt 的修改,用暂存区或 HEAD 的版本覆盖它
git checkout -- a.txt

注意命令中的 --,它是一个好习惯,用于分隔分支名和文件名,避免当文件名与分支名相同时产生歧义.

常用参数:

  • -b <new-branch>
    创建一个新分支,并切换到该分支。
  • -B <new-branch>
    如果 <new-branch> 不存在,则创建并切换(同 -b);如果已存在,则重置该分支到当前 commit,并切换过去。这是一个有风险的操作,慎用。
  • –track
    当本地没有某个分支,而远程 origin 有时,可以使用它来快捷地创建并跟踪远程分支。
# 本地没有 feature 分支,但远程 origin/feature 存在
# 这条命令会创建一个本地的 feature 分支,并使其跟踪 origin/feature
git checkout --track origin/feature
3. git switch

git switch 是在 Git 2.23 版本中引入的新命令,旨在将 git checkout 的功能进行拆分,使其职责更单一。switch 专门负责分支的切换和创建,让命令的意图更加清晰。

核心作用

  • 切换到另一个已存在的分支。
  • 创建一个新分支并立即切换过去

使用方法

1.切换到一个已存在的分支
# 切换到名为 develop 的分支
git switch develop

2.创建并切换到新分支
# 这是 git checkout -b 的现代等价命令。
# 创建一个名为 feature/new-login 的新分支,并立即切换到该分支
git switch -c feature/new-login

3.切换回上一个分支
#一个非常方便的快捷方式,可以在两个分支之间快速来回切换。
git switch -

常用参数

  • -c 或 --create
    创建一个新分支并切换过去。
  • -C 或 --force-create
    强制创建。如果同名分支已存在,它将被重置到当前 HEAD 的位置。这是一个有风险的操作。
  • –detach
    进入“分离 HEAD”状态。此时 HEAD 直接指向一个具体的提交(commit),而不是一个分支。这允许你在不创建新分支的情况下进行实验性提交。
# 直接切换到某个 commit,进入分离 HEAD 状态
git switch --detach a1b2c3d
4. git merge

git merge 是用于将一个分支的更改集成(合并)到另一个分支的命令。这是团队协作和功能开发的核心操作。

标准的合并流程如下:

  1. 首先,切换到你想要接纳更改的目标分支(例如 main)。
git switch main
  1. 确保目标分支是最新的。
git pull origin main
  1. 执行 merge 命令,将源分支(例如 feature/new-login)合并进来。
git merge feature/new-login

合并的两种类型

  • 快进合并 (Fast-Forward)
    如果你的目标分支 (main) 在源分支 (feature/new-login) 创建之后没有任何新的提交,那么合并时 Git 只会简单地将 main 分支的指针向前移动到 feature/new-login 的最新位置。这个过程不会产生新的“合并提交”,历史记录保持线性。
  • 三方合并 (Three-Way Merge)
    如果目标分支和源分支在分叉后各自都有了新的提交,Git 就无法进行快进合并。此时,它会执行“三方合并”:
    a. 找到两个分支的共同祖先。
    b. 将两个分支的更改与共同祖先进行比较。
    c. 创建一个新的合并提交 (Merge Commit),这个提交有两个父提交,分别指向原来的两个分支头。

常用参数:

  • –no-ff
    (非常推荐) 禁止快进式合并。即使可以快进,也强制创建一个新的合并提交。
    为什么? 这可以保留分支的开发历史。在 git log --graph 中,你能清晰地看到一个功能是从哪个分支合并过来的,这使得项目历史更具可读性。
git merge --no-ff feature/new-login
  • –squash
    “压扁”合并。它会将来自分支的所有提交的更改内容都应用到当前分支的工作目录和暂存区,但不会自动创建合并提交。你需要手动执行 git commit 来创建一个全新的、单一的提交。
    用途:当一个功能分支上有很多零碎的开发过程提交时,使用 --squash 可以将它们合并成一个干净的、有意义的提交,从而保持主分支的历史整洁。
  • –abort
    (救命稻草) 中止合并。如果合并过程中出现了很多你不想处理的冲突,或者你觉得合并操作有误,可以执行 git merge --abort。它会让你安全地回到执行 merge 命令之前的状态。
  • –ff-only
    只在可以快进的情况下才进行合并,否则就中止。

注意事项(合并冲突)

  • 合并方向:务必搞清楚合并的方向。git merge B 是将 B 分支合并到你当前所在的分支。
  • 合并冲突 (Merge Conflicts):如果两个分支在同一个文件的同一个地方有不同的修改,Git 无法自动判断该保留哪个。这时就会发生合并冲突。Git 会暂停合并,在冲突文件中用特殊标记(<<<<<<<, =======, >>>>>>>)标出冲突区域,等待你手动解决。
  • 解决冲突后:手动解决完所有冲突文件后,你需要使用 git add <已解决的文件> 将它们标记为已解决,然后执行 git commit 来完成这次合并提交。
5. git mergetool

当手动解决文本标记的合并冲突感到困难或效率低下时,git mergetool 可以启动一个外部的可视化合并工具来帮助你.

mergetool本身的使用需要进行配置,而且并不是Git的核心内容,这里先了略过

6. git tag

git tag 的核心作用是为 Git 仓库历史中的某一个特定的提交(commit)打上一个永久性的、有意义的标记。这个标记通常用于指代一个重要的时间点,最常见的场景就是项目发布。

常用方法

# 1. 列出标签
# 列出所有本地标签
git tag
# 使用通配符筛选标签
git tag -l "v1.8.*"

# 2. 创建标签
# 创建附注标签(推荐)
# 语法: git tag -a <标签名> -m "附注信息"
git tag -a v1.0 -m "Release version 1.0"
# 创建轻量标签
# 语法: git tag <标签名>
git tag v1.0-light

# 3. 为过去的提交打标签
# 如果你忘记了打标签,可以先用 git log 找到目标提交的哈希值,然后在创建标签时指定它。
# 假设从 git log 得知目标 commit 哈希是 7f5d8b2
git tag -a v0.9 -m "Retroactively tagging version 0.9" 7f5d8b2

# 4. 查看标签信息
# 使用 git show 命令查看标签的详细信息
git show v1.0

# 5. 删除标签
# 删除一个本地标签
git tag -d v1.0-light

# 6. 检出标签
# 你可以像检出分支一样检出某个标签,以查看当时的代码状态。
git checkout v1.0

注意最后一个检出标签的操作:会使你的仓库进入“分离 HEAD (Detached HEAD)”状态。这意味着你现在不在任何分支上。你可以在这个状态下查看代码、进行编译,但如果你在此基础上做了新的提交,这些提交不属于任何分支,一旦你切换到其他分支,就可能丢失。如果想要在这个标签基础上开发,正确的做法是基于该标签创建一个新分支:

git checkout -b hotfix-for-v1.0 v1.0

这条命令会创建一个名为 hotfix-for-v1.0 的新分支,其起点就是 v1.0 标签所在的位置,然后将你切换到这个新分支上。

Git的历史查看和修改
1. git log

log命令让你能够查看项目从诞生至今的每一次提交记录

使用方法:
最基础的用法就是直接输入命令:

git log 
# 或者
git log <content> # 查找与content有关的log 

你会看到一个详细的列表,每个条目包含:

  • Commit Hash:一个唯一的 SHA-1 哈希值,是这次提交的身份证。
  • Author:提交者的姓名和邮箱。
  • Date:提交的日期和时间。
  • Commit Message:提交时附带的说明信息。

常用参数:

  • –oneline
    将每次提交压缩到一行显示,只包含 commit 短哈希和提交信息摘要。非常适合快速概览。
  • –graph
    以 ASCII 字符绘制出分支与合并的拓扑图。通常和 –oneline、–decorate(显示分支和标签名)一起使用,效果极佳。
  • –pretty=format:“<格式字符串>”
    终极自定义格式。你可以指定任何你想要的输出格式。
      # 示例:短哈希 - 作者 - 相对时间 : 提交信息
      git log --pretty=format:"%h - %an, %ar : %s"
    
  • -p 或 --patch
    显示每次提交所引入的具体代码差异(补丁)。
  • -n <数量> 或 -<数量>
    只显示最近的 <数量> 次提交。例如 git log -3。
  • –author=“<作者名>”
    只显示指定作者的提交。
  • –grep=“<关键词>”
    在提交信息中搜索包含 <关键词> 的提交。
  • –since=<日期> 和 --until=<日期>
    按时间范围筛选。例如 git log --since=“2 weeks ago”。
  • <分支1>…<分支2>
    显示在 分支2 中存在、但在 分支1 中不存在的提交。非常适合查看一个功能分支相比主分支多了哪些提交。
    git log main..feature/new-login
    
2. git diff

git diff 用于比较 Git 仓库中任意两个“状态”之间的差异。这些“状态”可以是提交、分支、标签,甚至是你的工作目录。

常用方法:

1.比较工作目录与暂存区
git diff
# 显示所有已修改但还未 git add 到暂存区的更改。这是你即将要 add 的内容。

2.比较暂存区与最新提交
git diff --staged  # 或者 --cached
# 显示已经 git add 到暂存区、但还未 git commit 的更改。这是你即将要 commit 的内容。

3. 比较工作目录与最新提交
git diff HEAD
# 显示你工作目录中所有未提交的更改(包括已暂存和未暂存的)。

4.比较两次提交
# 语法: git diff <提交1> <提交2>
git diff a1b2c3d e4f5g6h

5.比较两个分支
# 显示 feature 分支相比于 main 分支有哪些不同
git diff main..feature

Diff的输出格式

  • — a/file.txt 和 +++ b/file.txt 分别代表变更前的“a”版本和变更后的“b”版本。
  • @@ -l,s +l,s @@ 称为“hunk”头,表示差异所在的行号范围。
  • 以 - 开头的行表示从“a”版本中删除。
  • 以 + 开头的行表示在“b”版本中添加。
    常用参数
  • –stat
    不显示完整的代码差异,而是显示一个统计摘要,包含哪些文件被修改了,以及增删了多少行。
  • –name-only
    最精简的模式,只列出有差异的文件名。
  • –color-words
    以单词为单位高亮显示差异,而不是以行为单位。对于修改文档或文章非常有用。
3. git show

git show 的核心作用是以一种对人类友好的方式,显示各种类型的 Git 对象(commit、tag、blob、tree)的详细信息。它最常见的用途是查看某一次提交(commit)的完整详情。
可以把 git show 理解为 git log 和 git diff 的一个便捷组合:

  • 它像 git log 一样,能显示某次提交的元数据(作者、日期、提交信息)。
  • 它又像 git diff 一样,能显示该次提交所引入的具体代码更改(补丁/patch)。

使用方法

  1. 查看单个提交(最常用)
  • 查看最近一次提交 (HEAD)
    如果你不提供任何参数,git show 默认显示当前 HEAD 指针指向的提交。
git show
  • 查看指定的提交
    你可以使用任何可以指向一个 commit 的引用来查看,比如:

    • Commit 哈希:git show a1b2c3d
    • 分支名:git show main (显示 main 分支顶端的最新提交)
    • 标签名:git show v1.0
    • 相对引用:git show HEAD~2 (显示当前提交往前数第2次的提交)
  • 解读输出内容
    git show 的输出通常分为两部分:

    • 提交元数据:
      commit a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 (HEAD -> main, origin/main)
      Author: Your Name you@example.com
      Date: Thu Jul 25 06:20:00 2024 -0700
      Feat: Add user authentication feature

    • 代码差异 (Diff/Patch):
      这部分显示了这次提交相比于其父提交所做的具体更改。
      diff --git a/src/login.js b/src/login.js
      index e69de29…d00491f 100644
      — a/src/login.js
      +++ b/src/login.js
      @@ -0,0 +1,5 @@
      +function authenticate(user, pass) {
      + // TODO: Implement real authentication
      + return true;
      +}

  1. 查看标签 (Tag)
    git show 也可以用来查看标签对象的信息。
git show v1.2.0
  • 如果 v1.2.0 是一个附注标签 (annotated tag),git show 会首先显示标签自身的信息(打标者、日期、附注信息),然后显示该标签指向的提交的详细信息。
  • 如果它是一个轻量标签 (lightweight tag),git show 的输出将和直接 show 那个提交完全一样。
  1. 查看某次提交中的文件内容
    你可以用 git show 直接查看某个历史版本中某个文件的全部内容,而不是它的变更。
# 语法: git show <commit>:<文件路径>
# 查看上一次提交中 src/main.js 文件的完整内容
git show HEAD~1:src/main.js
# 查看 v1.0 版本时 pom.xml 文件的内容
git show v1.0:pom.xml

常用参数
git show 的很多参数都是用来控制其 diff 部分的输出格式的,与 git diff 的参数类似。

  • 格式化输出
    –pretty=<格式> 或 --format=<格式>:与 git log 的用法完全一样,可以自定义提交元数据的显示格式,而不显示 diff。
    –stat:不显示完整的代码差异,只显示一个统计摘要,包含哪些文件被修改了以及增删的行数。
    –name-only:最精简的模式,只列出在该次提交中被更改的文件名。

  • 控制 Diff 输出
    –color-words:以单词为单位高亮显示差异,而不是以行为单位。对修改文档或文章非常有用。
    -w 或 --ignore-all-space:在比较时忽略所有空白字符的差异。

  • 处理合并提交
    对于合并提交(merge commit),它有两个父提交,git show 默认会显示一个合并后的“组合差异”。这有时会比较混乱。

-m 或 --first-parent:当遇到合并提交时,只显示该提交与第一个父提交的差异。这通常是你合并进来的那个分支的差异。

4. git blame

git blame 是一个行级的代码考古工具。它能逐行显示一个文件,并清晰地标明每一行代码最后是由谁、在哪一次提交中修改的。

使用方法:
最基础的用法是指定一个文件名:

git blame <文件名>

每一行的输出内容包括:

  • Commit 短哈希:这次修改所在的提交ID。^ 开头的哈希表示这是该文件的初始引入提交。
  • 作者:最后修改该行的作者名。
  • 时间戳:提交的时间。
  • 行号:文件中的原始行号。
  • 代码内容:该行的实际代码。

常用参数:

  • -L <起始行>,<结束行>:只显示指定行号范围内的 blame 信息。当你只关心文件中一小部分代码的历史时非常有用。
# 只查看 main.js 文件第 4 到第 6 行的历史
git blame -L 4,6 src/main.js
  • -e 或 --show-email:在作者名旁边显示完整的电子邮件地址。
  • -w:在追溯历史时,忽略单纯的空白(空格、Tab)修改。如果某次提交只是重新格式化了代码,使用此参数可以追溯到上一次真正修改代码逻辑的提交。
  • -C:更进一步,检测代码是否是从同一提交中的其他文件移动或复制过来的。这对于追踪重构操作很有帮助
4. git rebase

git rebase 是 Git 中很危险的命令之一。它的核心思想是重写历史,将一系列提交“变基”到另一个基础之上,从而创造一个更线性的、更整洁的提交历史。

rebase的使用比较危险,这里就先不做笔记了

Git的远程操作(团队的版本协作)

Git的远程操作一般有两种方式,一种是自己建立一个私人的Git服务器,第二种是使用Github(或其他)这种Git服务平台。

管理远程仓库
1. git clone

用于复制项目,之前已经说过。

2. git remote

用于管理你本地仓库配置的远程仓库“别名”及其对应的 URL。它本身不进行数据传输(如 push 或 pull),只负责管理连接信息。
核心作用:

  • 查看已配置的远程仓库。
  • 添加新的远程仓库连接。
  • 重命名或删除已有的远程仓库连接。
  • 修改远程仓库的 URL。

常见用法

1.查看远程连接
# 只列出远程连接的名称 (如 origin)
git remote
# 列出名称及其对应的 URL,非常常用
git remote -v
# 利用remote show命令查看更多信息
git remote show origin

2.添加远程连接
一个非常经典的场景是,你 fork 了一个开源项目。你 clone 的是你自己的 fork(origin),但你还想跟踪原始项目的更新。这时就可以将原始项目添加为一个新的远程连接,通常命名为 upstream。
# 语法: git remote add <名称> <URL>
git remote add upstream https://github.com/original-author/original-project.git
# 现在,git remote -v 就会显示 origin 和 upstream 两个远程连接。你可以从 upstream 拉取更新,然后推送到你自己的 origin。

3.修改远程连接的 URL
当你需要将项目迁移到新的服务器,或者想从 HTTPS 切换到 SSH 时,这个命令非常有用。
# 语法: git remote set-url <名称> <新URL>
git remote set-url origin git@github.com:my-user/my-fork.git

4.重命名远程连接
# 语法: git remote rename <旧名称> <新名称>
git remote rename origin upstream

5.删除远程连接
# 语法: git remote remove <名称>
git remote remove upstream

常用参数

  • git remote 的主要“参数”实际上是它的子命令,如 add, remove, set-url 等。
  • -v 或 --verbose: 在列出远程连接时,显示详细的 URL 信息。

注意:

  • 本地操作:git remote 的所有操作都只修改你本地仓库的 .git/config 文件。它不会以任何方式联系远程服务器或影响远程仓库本身。它只是在管理你本地的“书签”
  • 远程跟踪分支:你配置的每一个远程连接,都对应着一组“远程跟踪分支”(如 origin/main, upstream/develop)。当你执行 git fetch <远程名> 时,Git 就是根据这个远程连接的 URL 去获取最新数据,并更新这些远程跟踪分支。
同步操作
1. git fetch

fetch 的作用非常单纯:它只负责从远程仓库下载最新的数据(新的提交、分支、标签)到你的本地仓库

  • 它会更新你的“远程跟踪分支”(如 origin/main)。这些分支就像是远程仓库在你本地的只读镜像或书签,让你知道远程仓库的状态。
  • 它完全不会修改你自己的本地工作分支(如 main),也不会影响你的工作目录或你正在编辑的代码。。

使用方法:

# 从名为 origin 的远程仓库获取所有更新
git fetch origin

# 获取所有已配置的远程仓库的更新
git fetch --all

# 获取 origin 仓库的数据,并清理本地不存在的远程跟踪分支
git fetch --prune origin

常用参数

  • –all:从所有配置的远程仓库中获取更新。
  • –prune 或 -p:在获取前,清理掉本地那些在远程仓库中已被删除的、陈旧的远程跟踪分支。这是一个保持仓库整洁的好习惯。
2. git pull

pull 是一个复合命令,它试图简化工作流程,但也可能带来意外。pull 是两个命令的快捷方式:git pull = git fetch + git merge。

  • 它首先会执行 git fetch,从远程下载最新的数据。
  • 然后,它会立刻尝试将远程跟踪分支(如 origin/main)合并到你当前所在的本地工作分支(如 main)。

使用方法

# 从 origin 拉取更新并合并到当前分支
# 假设当前在 main 分支,这条命令大致等同于:
# git fetch origin
# git merge origin/main
git pull origin

如果你本地分支设置了上游跟踪关系(通常 clone 或 push -u 后会自动设置),可以直接简化为:git pull

常用参数

  • –rebase:一个非常重要的参数。使用 git pull --rebase 时,pull 会以 git fetch + git rebase 的方式工作,而不是 merge。这可以避免产生不必要的合并提交,保持提交历史的线性整洁。这是许多团队推荐的做法。
  • –ff-only:只在可以“快进式”(Fast-forward)合并时才执行,否则就停止。这可以防止 pull 操作自动创建一个非预期的合并提交。

注意:

  • 因为 pull 会自动尝试合并,如果你本地有与远程不一致的提交,pull 操作可能会立刻导致合并冲突。
3. git push

当你完成了本地的提交,就需要用 push 命令将你的工作分享给团队,更新到远程仓库。

使用方法

1.基本推送
# 语法: git push <远程仓库名> <本地分支名>[:<远程分支名>]
git push origin main
# 这条命令将你本地的 main 分支推送到 origin 远程仓库对应的 main 分支。
#如果远程分支名与本地分支名相同,可以省略冒号后的部分。

2.首次推送新分支
#当你第一次推送一个本地新创建的分支时,建议使用 -u 参数来设置“上游跟踪关系”。
# -u 参数会自动设置跟踪,以后该分支可以直接使用 git push
git push -u origin feature/new-login
设置后,Git 就知道你本地的 feature/new-login 分支对应的是远程的 origin/feature/new-login 分支。

3.删除远程分支
git push origin --delete <分支名>

4.推送标签
#标签默认不会被 git push 推送,需要显式操作。
# 推送所有本地标签
git push --tags
# 推送单个标签
git push origin <标签名>

常用参数

  • -u 或 --set-upstream:设置上游跟踪关系。
  • –force:(危险!) 强制推送。它会用你本地的分支状态强行覆盖远程分支。这会销毁远程仓库上别人可能已经提交的更改。绝对不要在共享分支(如 main, develop)上使用此命令,除非你百分之百确定你在做什么,并且已经和团队沟通过。
  • –force-with-lease:一个更安全的强制推送。在推送前,它会检查远程分支是否在你上次 fetch 之后又有新的提交。如果有,推送就会失败。这可以防止你无意中覆盖掉团队成员在你不知情的情况下推送的工作。如果必须强制推送,请优先使用此命令。

注意事项

  • 先 pull(或 fetch+merge)再 push:在推送你的更改之前,一定要先从远程拉取最新的版本并与你的本地工作合并。这可以确保你的工作是基于最新版本进行的,并在本地解决完所有可能的冲突。这是一个至关重要的协作习惯。
  • 被拒绝的推送 (Rejected Push):如果你 push 时看到 rejected 的错误,通常意味着远程分支上有了你本地没有的新提交。这正是需要你先 pull 或 fetch 的信号。切勿立即使用 --force!
建立Github仓库

占个位

建立私人的Git服务器

占个位

Git的配置
git config 命令

Git配置的三个层级:

  1. System (系统级)
    • 作用范围:对操作系统上的所有用户和他们的所有仓库都生效。
    • 配置文件位置:通常在 /etc/gitconfig (Linux)。
    • 使用场景:由系统管理员设置,为服务器上的所有用户提供统一的默认配置。个人用户很少需要修改它。
    • 命令参数:–system
  2. Global (全局级/用户级)
    • 作用范围:对当前登录的单个用户的所有仓库生效。
    • 配置文件位置:通常在 ~/.gitconfig 或 ~/.config/git/config。
    • 使用场景:这是最常用的配置级别,用于设置你个人的信息,如用户名、邮箱,以及你希望在所有项目中通用的别名、编辑器等。
    • 命令参数:–global
  3. Local (本地级/仓库级)
    • 作用范围:只对当前所在的单个仓库生效。
    • 配置文件位置:在仓库的 .git/config 文件中。
    • 使用场景:用于设置特定项目的配置。例如,你在公司的项目需要使用公司邮箱,而在个人的开源项目中使用个人邮箱。这时就可以在公司仓库内部使用 --local 配置来覆盖全局设置。
    • 命令参数:–local (在一个仓库内部时,此为默认级别,可省略)

当同一个配置项在多个层级中都存在时,Git 会采用“就近原则”:Local (仓库级) > Global (用户级) > System (系统级)

config命令的常用方法:

  1. 查看配置
  • 列出所有配置
    这个命令会列出所有层级的配置,并显示它们各自的来源文件,非常适合调试。
git config -l --show-origin
查看单个配置项
  • 查看单个配置

Git 会按照 Local > Global > System 的顺序查找并返回第一个找到的值。

git config user.name
git config user.email
  1. 设置配置
  • 首次使用 Git 必须做的配置
    每个提交都包含作者信息,所以这是使用 Git 前必须完成的配置。通常我们把它设置在 --global 级别。
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
  • 设置特定仓库的配置
    假设你在一个工作项目中,需要使用工作邮箱。
# 首先进入你的项目仓库
cd /path/to/work-project
# 设置本地配置 (可以省略 --local)
git config --local user.email "your.name@work-company.com"

现在,在这个仓库里的所有提交都会使用你的工作邮箱,而其他仓库则继续使用全局配置的个人邮箱。

  1. 直接编辑配置文件
    你可以直接用编辑器打开配置文件进行更复杂的操作,比如设置别名。
# 编辑全局配置文件
git config --global --edit
# 编辑本地仓库配置文件
git config --local --edit
  1. 删除配置项
# 语法: git config --[scope] --unset <配置项>
git config --global --unset user.name

一些有用的配置推荐:

除了基本的 user.name 和 user.email,以下是一些能极大提升效率的推荐配置。

  • 设置默认编辑器
git config --global core.editor "vim"  # 或者 "code --wait", "nano" 等
  • 设置新仓库的默认分支名
git config --global init.defaultBranch main
  • 开启彩色显示
    让 git status, git diff 等命令的输出更易读。
git config --global color.ui auto
  • 设置命令别名 (alias)
# 为常用命令设置短别名
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit

# 创建一个自定义的、格式优美的 log 命令
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

设置后,你只需输入 git st 就等同于 git status,输入 git lg 就能看到漂亮的提交历史。

  • 配置 pull 的默认行为
    避免产生不必要的合并提交,保持历史线性。
git config --global pull.rebase true

注意:许多开发者会将他们的 ~/.gitconfig 文件(以及其他点文件,如 .bashrc, .vimrc)放在一个专门的 Git 仓库(称为 “dotfiles” 仓库)里进行版本控制,这样就可以轻松地在多台机器之间同步他们的个性化配置

gitignore 文件

.gitignore 文件是一个纯文本文件,它的核心作用是充当一个“忽略清单”或“黑名单”。你可以在这个文件中列出一些你希望 Git 完全忽略的文件或目录的匹配模式。

我们需要忽略的文件通常包括:

  • 编译产生的文件:如 Java 的 .class、C/C++ 的 .o、.a、.so 以及可执行文件。这些文件可以由源代码重新生成,体积大且没有版本控制的必要。
  • 依赖包和库文件:如 Node.js 项目的 node_modules 目录、Python 的虚拟环境目录 venv、Java 的 Maven/Gradle 依赖包。这些依赖可以通过包管理工具(如 npm, pip, mvn)根据配置文件(如 package.json, requirements.txt, pom.xml)自动下载,提交它们会使仓库变得异常臃肿。
  • 日志和临时文件:如*.log、*.tmp、*.swp (Vim交换文件) 等。
  • 操作系统和IDE/编辑器的配置文件:如 macOS 的 .DS_Store、Windows 的 Thumbs.db、JetBrains IDE 的 .idea/ 目录、VS Code 的 .vscode/ 目录(除非团队需要共享特定配置)。
  • 敏感信息文件:(极其重要) 包含密码、API密钥、数据库连接字符串、私钥等敏感数据的文件,如 .env、credentials.json、settings.local.py。将这些文件提交到公共仓库会导致严重的安全漏洞。

使用方法

  1. 创建文件
    在你的项目根目录下,创建一个名为 .gitignore 的文本文件。注意,它以点 . 开头,在 Linux/macOS 下是一个隐藏文件。

  2. 添加规则
    在 .gitignore 文件中,每行写一个匹配模式。Git 会根据这些模式来判断是否要忽略某个文件或目录。

基本规则:

  • 空行或以 # 开头的行会被忽略,可以作为注释使用。
  • 可以直接写文件名,如 debug.log,会忽略所有目录下的 debug.log 文件。
  • 可以使用标准的 glob 模式(类似 shell 的通配符):
    • *:匹配零个或多个字符。例如,*.log 会忽略所有以 .log 结尾的文件。
    • ?:匹配一个任意字符。例如,file?.txt 会忽略 file1.txt、fileA.txt 等。
    • []:匹配方括号中的任意一个字符。例如,[ab].log 会匹配 a.log 和 b.log。
  • 目录:在模式后面加上斜杠 / 表示这是一个目录。例如,node_modules/ 会忽略整个 node_modules 目录。即使不加 /,Git 通常也能正确识别目录,但加上 / 是更明确、更推荐的做法。
  • 否定模式:在模式前加上感叹号 ! 表示不要忽略。这可以用来对之前的忽略规则设置例外。
# 例如,你想忽略所有 .log 文件,但保留 important.log:
*.log
!important.log
  • 路径分隔符:
    • 如果模式不包含斜杠 /,它会匹配任何路径下的同名文件/目录。例如,tmp 会匹配 ./tmp、src/tmp 等。
    • 如果模式以斜杠 / 开头,它只匹配相对于项目根目录的路径。例如,/debug.log 只会忽略项目根目录下的 debug.log,而不会忽略 src/debug.log。
    • 如果模式中间包含斜杠 /,它也会被看作是相对于项目根目录的路径。例如,logs/debug.log。
  • 双星号 :可以匹配任意多层目录。例如,/logs 会匹配项目下任何深度的 logs 目录。

注意事项:

  • .gitignore 文件本身应该被提交:将 .gitignore 文件提交到仓库中,这样团队中的每个成员都能共享同一套忽略规则,保证了协作的一致性。
  • 如果文件已经被跟踪了怎么办?:这是一个非常常见的问题。如果你不小心把一个本应忽略的文件(如 config.local.js)提交到了仓库,此时再把它加入 .gitignore 是无效的,因为 Git 已经开始跟踪它了。
    解决方法:你需要先从 Git 的跟踪列表(暂存区)中移除它,然后再提交。
# 1. 从 Git 的跟踪列表中移除文件,但保留本地的物理文件
git rm --cached config.local.js

# 2. 将 config.local.js 添加到 .gitignore 文件中
echo "config.local.js" >> .gitignore

# 3. 提交这次更改
git commit -m "Stop tracking config.local.js"

使用模板
为每一种语言或框架从头编写 .gitignore 文件是很繁琐的。强烈推荐使用现成的模板。

  • GitHub 的模板库:github/gitignore 是一个非常全面的官方模板集合,包含了几乎所有主流语言和框架的推荐配置。
  • 在线生成工具:网站 toptal.com/developers/gitignore (原 gitignore.io) 可以让你选择你的技术栈(如 Node, Python, VSCode),然后自动为你生成一个非常完善的 .gitignore 文件。

习题部分

题目一:

# 克隆课程的github仓库
git clone https://github.com/missing-semester-cn/missing-semester-cn.github.io.git

# 查看最近一条README更改,这里检测的是commit的说明内容
git log -n 1 README

# 查看最近一条_config.yml更改,这里检测的是commit的说明内容(这个命令不合题意)
git log -n 1 _config.yml

# 查找collection行的修改,这里检测的是内容
git blame _config.yml | grep collections

# 输出:
a88b4eac (Anish Athalye  2020-01-17 15:26:30 -0500 18) collections:

# 显示一下commit的说明内容(这里的--pretty是看了答案)
git show --pretty=format:"%s" a88b4eac | head -1

题目二:
这一题还挺有意义的,需要你学会如何删除已经提交了的敏感数据,主要涉及git-filter-repo工具的使用。(读题干给的链接

第一种情况:本地上的commit还没有上传到github

# 建立一个秘密文件
echo "don't open" >> secret.txt

# 上传
git add secret.txt 
git commit -m "add secret files"

# 撤回(这次撤回将文件放回了缓冲区)
git reset --soft HEAD~1

# 撤回缓冲区的内容 
git restore --staged secret.txt

第二种情况:已经上传到github了

git filter-repo --path secret.txt --invert-paths

答案上给的是这个(是filter-repo的前身filter-branch):

 git filter-branch --force --index-filter\
 'git rm --cached --ignore-unmatch ./my_password' \
 --prune-empty --tag-name-filter cat -- --all

这是一个强大且危险的 Git 历史重写操作,其目的是从 Git 仓库的所有分支和标签的全部历史记录中,彻底删除一个名为 my_password 的文件。

题目三:
关于slash之前已经说了

题目四:

git config --global alias.graph 'log --all --graph --decorate --oneline'

题目五:

git config --global core.excludesfile ~/.gitignore
echo ".DS_Store" >> ~/.gitignore

放在最后:其实Git里的命令非常非常多,常用的还有cat-file、bisect等等,一篇笔记是肯定写不完,之后有机会再补充。


网站公告

今日签到

点亮在社区的每一天
去签到