Git完全指南(上篇):基础入门与底层原理

236 阅读31分钟

目录

  1. Git简介与版本控制基础
  2. 环境准备:安装Git与配置GitHub
  3. Git底层原理深度剖析
  4. 实战:本地开发与首次推送GitHub

第一章:Git简介与版本控制基础

1.1 什么是版本控制?

想象你正在写一篇重要的文档,你可能会这样保存文件:

论文_初稿.docx
论文_修改版.docx
论文_最终版.docx
论文_最终版_真的最终版.docx
论文_最终版_导师审阅后修改.docx

这种手动管理版本的方式存在诸多问题:文件命名混乱、无法追溯修改历史、难以协作、容易丢失数据。版本控制系统就是为了解决这些问题而诞生的。

版本控制系统(Version Control System,VCS)是一种记录文件内容变化,以便将来查阅特定版本修订情况的系统。它能够:

  • 追踪每一次修改:谁在什么时间修改了什么内容
  • 回退到任意版本:发现问题可以快速恢复
  • 并行开发:多人同时工作互不干扰
  • 合并修改:智能合并不同人的修改
  • 备份与恢复:分布式存储,数据安全

1.2 版本控制系统的演进

第一代:本地版本控制系统

最简单的版本控制方式是复制整个项目目录。更进一步的是使用本地数据库记录文件的历次更新差异,如RCS(Revision Control System)。

版本1 ← 版本2 ← 版本3 ← 版本4
(完整文件) (差异) (差异) (差异)

缺点:无法协作,数据只在本地。

第二代:集中式版本控制系统(CVCS)

为了解决协作问题,集中式版本控制系统应运而生,如CVS、Subversion(SVN)、Perforce。

        中央服务器
       /    |    \
   客户端A 客户端B 客户端C

所有的版本数据都存储在中央服务器上,开发者从服务器检出(checkout)文件,修改后提交(commit)回服务器。

优点

  • 可以协作开发
  • 管理员可以精细控制权限
  • 易于管理

缺点

  • 单点故障:服务器宕机则无法协作,服务器磁盘损坏可能丢失所有历史
  • 必须联网:提交、查看历史都需要连接服务器
  • 性能瓶颈:所有操作都需要与服务器通信

第三代:分布式版本控制系统(DVCS)

Git、Mercurial、Bazaar等分布式版本控制系统从根本上改变了版本控制的方式。

    远程仓库(GitHub)
    /       |       \
本地仓库A  本地仓库B  本地仓库C
(完整历史) (完整历史) (完整历史)

在分布式系统中,客户端不只是检出最新的文件快照,而是完整地镜像整个代码仓库,包括完整的历史记录。

优点

  • 完全分布式:每个开发者都有完整的仓库副本
  • 离线工作:大部分操作都在本地完成,速度极快
  • 多重备份:每个克隆都是完整备份,数据安全
  • 灵活的工作流:支持多种协作模式

1.3 Git的诞生与发展

Git的起源:

Git由Linux内核的创始人Linus Torvalds在2005年创建。当时Linux内核开发团队使用的商业版本控制系统BitKeeper撤销了免费使用授权,Linus决定自己开发一个分布式版本控制系统。

仅仅用了十天时间,Linus就完成了Git的第一个版本!这个系统后来成为了世界上最流行的版本控制系统。

Git的设计目标:

  1. 速度:绝大多数操作都在本地完成
  2. 简单的设计:核心概念简洁明了
  3. 强力支持非线性开发:支持成千上万个并行分支
  4. 完全分布式:每个仓库都是完整的
  5. 能够高效管理大型项目:如Linux内核(数万个文件)

Git的名字:

Linus自嘲地说Git是"the stupid content tracker"(愚蠢的内容跟踪器)。Git在英式俚语中是"不愉快的人"的意思,Linus说:"I'm an egotistical bastard, and I name all my projects after myself. First Linux, now git."(我是个自负的混蛋,我所有的项目都以我自己命名。先是Linux,现在是git。)

1.4 Git vs. SVN:核心区别

特性Git(分布式)SVN(集中式)
架构分布式,每个开发者都有完整仓库集中式,只有服务器有完整历史
存储方式快照(Snapshot)差异(Delta)
分支轻量级,创建和切换极快重量级,实际是目录复制
速度绝大多数操作在本地,极快需要网络通信,较慢
离线工作完全支持不支持
数据完整性SHA-1哈希保证版本号递增
学习曲线较陡峭较平缓

快照 vs. 差异:

这是Git与其他VCS最核心的区别。

SVN的差异存储:

版本1: 完整文件A, 完整文件B, 完整文件C
版本2: 文件A的差异, 文件B的差异, 文件C不变
版本3: 文件A不变, 文件B的差异, 文件C的差异

Git的快照存储:

版本1: 文件A快照, 文件B快照, 文件C快照
版本2: 文件A快照, 文件B快照, 文件C引用(指向版本1)
版本3: 文件A引用(指向版本2), 文件B快照, 文件C快照

Git将数据看作是小型文件系统的一组快照。每次提交时,Git实际上是对当前所有文件制作一个快照并保存这个快照的索引。如果文件没有修改,Git只保留一个指向之前存储的文件的链接。

1.5 Git的三个工作区域

理解Git的三个工作区域是掌握Git的关键。

工作区              暂存区              本地仓库           远程仓库
(Working Directory) (Staging Area)     (Local Repository) (Remote Repository)
                    (Index)            (.git directory)   (GitHub)

  文件修改     →    git add    →    git commit   →   git push
               ←  git restore  ←   git checkout  ←   git pull

1. 工作区(Working Directory)

就是你在电脑里能看到的项目目录,是你实际编辑文件的地方。

2. 暂存区(Staging Area / Index)

暂存区是一个文件(.git/index),保存了下次将要提交的文件列表信息。可以理解为一个"购物车",你把修改的文件放进购物车,最后一起"结账"(提交)。

3. 本地仓库(Local Repository)

Git用来保存项目元数据和对象数据库的地方(.git目录)。这是Git最重要的部分,克隆仓库时复制的就是这里的数据。

4. 远程仓库(Remote Repository)

托管在网络上的项目仓库,如GitHub、GitLab、Bitbucket。用于团队协作和代码备份。

基本工作流程:

  1. 在工作区修改文件
  2. 使用git add将修改添加到暂存区
  3. 使用git commit将暂存区的内容提交到本地仓库
  4. 使用git push将本地仓库的提交推送到远程仓库

1.6 Git的三种文件状态

Git中的文件有三种状态:

未跟踪(Untracked) ──git add──> 已暂存(Staged) ──git commit──> 已提交(Committed)
                                      ↑                              ↓
                                      └────── 修改后 ←────── 已修改(Modified)

1. 已提交(Committed)

数据已经安全地保存在本地仓库中。

2. 已修改(Modified)

修改了文件,但还没有提交到本地仓库。

3. 已暂存(Staged)

对已修改文件的当前版本做了标记,使之包含在下次提交的快照中。

4. 未跟踪(Untracked)

新创建的文件,Git还不知道它的存在。


第二章:环境准备:安装Git与配置GitHub

2.1 安装Git

在Linux(Ubuntu/Debian)上安装:

# 更新包索引
sudo apt update

# 安装Git
sudo apt install git

# 验证安装
git --version
# 输出:git version 2.34.1

在macOS上安装:

# 方法一:使用Homebrew(推荐)
brew install git

# 方法二:安装Xcode Command Line Tools
xcode-select --install

# 验证安装
git --version

在Windows上安装:

  1. 访问git-scm.com/download/wi…

  2. 下载安装程序

  3. 运行安装程序,推荐配置:

    • 选择"Git from the command line and also from 3rd-party software"
    • 选择"Use the OpenSSL library"
    • 选择"Checkout Windows-style, commit Unix-style line endings"
    • 选择"Use MinTTY"
    • 启用文件系统缓存和Git Credential Manager
  4. 打开Git Bash,验证安装:

git --version

2.2 初始配置

安装完Git后,需要进行一些基本配置。Git的配置分为三个级别:

级别配置文件位置作用范围命令参数
系统级/etc/gitconfig所有用户、所有仓库--system
用户级~/.gitconfig~/.config/git/config当前用户的所有仓库--global
仓库级.git/config当前仓库--local(默认)

优先级:仓库级 > 用户级 > 系统级

配置用户信息:

# 配置用户名(会显示在每次提交中)
git config --global user.name "Your Name"

# 配置邮箱(会显示在每次提交中,建议使用GitHub邮箱)
git config --global user.email "your.email@example.com"

配置默认分支名称:

# 将默认分支名从master改为main(GitHub的新标准)
git config --global init.defaultBranch main

配置文本编辑器:

# 使用VS Code
git config --global core.editor "code --wait"

# 使用Vim
git config --global core.editor vim

# 使用Nano
git config --global core.editor nano

配置行尾符处理:

# Windows用户
git config --global core.autocrlf true

# macOS/Linux用户
git config --global core.autocrlf input

配置颜色输出:

# 启用彩色输出
git config --global color.ui auto

查看配置:

# 查看所有配置
git config --list

# 查看特定配置
git config user.name

# 查看配置及其来源
git config --list --show-origin

2.3 注册GitHub账号

GitHub是全球最大的代码托管平台,我们将用它来托管项目。

注册步骤:

  1. 访问github.com
  2. 点击"Sign up"注册账号
  3. 填写用户名、邮箱、密码
  4. 验证邮箱
  5. 完成新手引导

GitHub账号设置建议:

  • 用户名:选择专业的用户名,它会出现在你的项目URL中
  • 邮箱:使用常用邮箱,确保能收到通知
  • 头像:上传清晰的头像,提升专业形象
  • 个人简介:填写简短的自我介绍

2.4 配置SSH密钥

使用SSH密钥可以在不输入密码的情况下安全地连接GitHub。

检查现有SSH密钥:

# 查看是否已有SSH密钥
ls -al ~/.ssh

# 如果看到id_rsa.pub或id_ed25519.pub,说明已有密钥

生成新的SSH密钥:

# 使用你的GitHub邮箱生成SSH密钥
ssh-keygen -t ed25519 -C "your.email@example.com"

# 如果系统不支持ed25519,使用RSA
ssh-keygen -t rsa -b 4096 -C "your.email@example.com"

# 提示输入文件位置时,直接按Enter使用默认位置
# Enter file in which to save the key (/home/you/.ssh/id_ed25519): [按Enter]

# 提示输入密码时,可以设置密码或直接按Enter跳过
# Enter passphrase (empty for no passphrase): [输入密码或按Enter]

启动SSH代理并添加密钥:

# 启动SSH代理
eval "$(ssh-agent -s)"

# 添加SSH私钥到代理
ssh-add ~/.ssh/id_ed25519

将SSH公钥添加到GitHub:

# 复制SSH公钥到剪贴板
# Linux
cat ~/.ssh/id_ed25519.pub
# 手动复制输出内容

# macOS
pbcopy < ~/.ssh/id_ed25519.pub

# Windows (Git Bash)
clip < ~/.ssh/id_ed25519.pub

然后在GitHub上添加SSH密钥:

  1. 登录GitHub
  2. 点击右上角头像 → Settings
  3. 左侧菜单选择"SSH and GPG keys"
  4. 点击"New SSH key"
  5. 填写Title(如"My Laptop")
  6. 将复制的公钥粘贴到Key字段
  7. 点击"Add SSH key"

测试SSH连接:

# 测试SSH连接
ssh -T git@github.com

# 首次连接会提示确认指纹,输入yes
# The authenticity of host 'github.com (140.82.113.4)' can't be established.
# ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
# Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

# 成功后会看到:
# Hi username! You've successfully authenticated, but GitHub does not provide shell access.

2.5 配置Git凭证管理

为了避免每次推送都输入用户名和密码(如果使用HTTPS),可以配置凭证管理器。

Linux:

# 使用缓存模式(默认15分钟)
git config --global credential.helper cache

# 设置缓存时间(如1小时)
git config --global credential.helper 'cache --timeout=3600'

# 或使用存储模式(永久保存,不安全)
git config --global credential.helper store

macOS:

# 使用macOS钥匙串
git config --global credential.helper osxkeychain

Windows:

# 使用Git Credential Manager(安装Git时已包含)
git config --global credential.helper manager-core

2.6 配置.gitignore全局忽略

创建全局.gitignore文件,忽略系统文件和编辑器临时文件。

# 创建全局gitignore文件
cat > ~/.gitignore_global << 'EOF'
# macOS
.DS_Store
.AppleDouble
.LSOverride

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# Linux
*~
.directory

# 编辑器
.vscode/
.idea/
*.swp
*.swo
*~

# 日志文件
*.log
EOF

# 配置Git使用全局gitignore
git config --global core.excludesfile ~/.gitignore_global

第三章:Git底层原理深度剖析

理解Git的底层原理是从Git用户成长为Git专家的关键。本章将深入探索Git的内部机制。

3.1 .git目录结构

当你执行git init时,Git会创建一个.git目录,这是Git存储所有数据的地方。

# 创建测试目录
mkdir git-internals-demo
cd git-internals-demo

# 初始化Git仓库
git init

# 查看.git目录结构
tree .git -L 1

.git目录结构:

.git/
├── HEAD              # 指向当前分支的指针
├── config            # 仓库配置文件
├── description       # 仓库描述(仅供GitWeb使用)
├── hooks/            # 钩子脚本目录
├── info/             # 全局排除文件
│   └── exclude       # 不想用.gitignore管理的忽略模式
├── objects/          # 所有数据内容(核心!)
│   ├── info/
│   └── pack/
└── refs/             # 指向数据(分支)的提交对象的指针
    ├── heads/        # 本地分支
    ├── tags/         # 标签
    └── remotes/      # 远程分支

核心文件和目录:

路径说明
objects/存储所有数据对象(blob、tree、commit、tag)
refs/存储指向数据对象的指针(分支、标签)
HEAD指向当前分支
index暂存区信息(执行git add后才会出现)
config仓库级配置
hooks/客户端或服务端钩子脚本

3.2 Git对象模型

Git的核心是一个内容寻址文件系统(content-addressable filesystem)。这意味着Git的核心是一个简单的键值对数据库(key-value data store)。你可以向Git仓库中插入任意类型的内容,它会返回一个唯一的键,通过该键可以在任意时刻再次取回该内容。

Git有四种对象类型:

  1. Blob对象:存储文件内容
  2. Tree对象:存储目录结构和文件名
  3. Commit对象:存储提交信息
  4. Tag对象:存储标签信息

3.2.1 Blob对象(二进制大对象)

Blob(Binary Large Object)对象用于存储文件内容,不包含文件名、权限等元数据

创建Blob对象:

# 创建一个文件
echo "Hello, Git!" > hello.txt

# 使用底层命令将文件内容存储为blob对象
git hash-object -w hello.txt
# 输出:8d0e41234f24b6da002d962a26c2495ea16a425f

# 这个40位的十六进制字符串就是该内容的SHA-1哈希值

查看对象内容:

# 使用cat-file命令查看对象
git cat-file -p 8d0e41234f24b6da002d962a26c2495ea16a425f
# 输出:Hello, Git!

# 查看对象类型
git cat-file -t 8d0e41234f24b6da002d962a26c2495ea16a425f
# 输出:blob

# 查看对象大小
git cat-file -s 8d0e41234f24b6da002d962a26c2495ea16a425f
# 输出:12

Blob对象存储位置:

# 对象存储在.git/objects目录下
# 使用SHA-1的前2位作为目录名,后38位作为文件名
ls .git/objects/8d/
# 0e41234f24b6da002d962a26c2495ea16a425f

重要特性:

  • 相同内容的文件只存储一次(内容寻址)
  • 不存储文件名和目录结构
  • 不存储文件权限(除了可执行位)

3.2.2 Tree对象(目录树)

Tree对象用于存储目录结构,它包含一个或多个条目,每个条目是一个blob对象或子tree对象的SHA-1指针,以及相应的模式、类型和文件名。

Tree对象的结构:

tree对象
├── 100644 blob 8d0e412... hello.txt
├── 100644 blob 5c1b14e... README.md
└── 040000 tree 3c4e9cd... src/

查看Tree对象:

# 先创建一次提交
git add hello.txt
git commit -m "Initial commit"

# 查看最新提交的tree对象
git cat-file -p HEAD^{tree}

# 输出示例:
# 100644 blob 8d0e41234f24b6da002d962a26c2495ea16a425f    hello.txt

文件模式说明:

模式说明
100644普通文件
100755可执行文件
120000符号链接
040000目录(tree对象)

3.2.3 Commit对象(提交)

Commit对象包含以下信息:

  • 指向项目根目录tree对象的指针
  • 父提交的指针(第一次提交没有父提交,合并提交有多个父提交)
  • 作者信息(姓名、邮箱、时间戳)
  • 提交者信息(姓名、邮箱、时间戳)
  • 提交说明

查看Commit对象:

# 查看最新提交
git cat-file -p HEAD

# 输出示例:
# tree 3c4e9cd789f88c4e9cd789f88c4e9cd789f88c4e
# author Your Name <your.email@example.com> 1634567890 +0800
# committer Your Name <your.email@example.com> 1634567890 +0800
#
# Initial commit

提交链:

C3 (HEAD -> main)
↓ (parent)
C2
↓ (parent)
C1
↓ (parent)
C0 (初始提交,无parent)

每个提交对象都指向其父提交,形成一条提交链。

3.2.4 Tag对象(标签)

Tag对象用于给特定的提交打上标签,通常用于标记版本发布点。

轻量标签 vs. 附注标签:

# 轻量标签(只是一个指向提交的引用)
git tag v1.0

# 附注标签(完整的对象,包含标签信息)
git tag -a v1.0 -m "Release version 1.0"

查看Tag对象:

# 查看附注标签
git cat-file -p v1.0

# 输出示例:
# object 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t
# type commit
# tag v1.0
# tagger Your Name <your.email@example.com> 1634567890 +0800
#
# Release version 1.0

3.3 SHA-1哈希算法

Git使用SHA-1(Secure Hash Algorithm 1)哈希算法来为每个对象生成唯一的标识符。

SHA-1的特性:

  1. 确定性:相同的输入总是产生相同的输出
  2. 唯一性:不同的输入几乎不可能产生相同的输出(碰撞概率极低)
  3. 不可逆:无法从哈希值反推原始内容
  4. 雪崩效应:输入的微小变化会导致输出的巨大变化

Git如何计算SHA-1:

# Git对象的SHA-1计算方式:
# SHA-1(对象类型 + 空格 + 内容长度 + null字节 + 内容)

# 手动计算"Hello, Git!"的blob对象哈希
echo -n "blob 12\0Hello, Git!" | sha1sum
# 输出:8d0e41234f24b6da002d962a26c2495ea16a425f

# 与git hash-object的结果一致
echo "Hello, Git!" | git hash-object --stdin
# 输出:8d0e41234f24b6da002d962a26c2495ea16a425f

SHA-1碰撞问题:

2017年,Google宣布成功制造了SHA-1碰撞(两个不同的PDF文件产生相同的SHA-1哈希)。但在Git的使用场景中,这个问题的影响有限:

  1. Git正在逐步迁移到SHA-256
  2. GitHub等平台会检测并拒绝已知的碰撞文件
  3. 实际攻击难度极高,需要巨大的计算资源

3.4 对象存储机制

3.4.1 松散对象(Loose Objects)

Git最初以松散对象格式存储对象。每个对象都是一个单独的文件。

存储过程:

  1. 计算内容的SHA-1哈希值
  2. 使用zlib压缩内容
  3. 将压缩后的内容写入.git/objects/xx/yy...文件
# 查看松散对象
find .git/objects -type f

# 输出示例:
# .git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f
# .git/objects/3c/4e9cd789f88c4e9cd789f88c4e9cd789f88c4e

查看对象的原始内容:

# 使用Python解压查看
python3 << 'EOF'
import zlib
with open('.git/objects/8d/0e41234f24b6da002d962a26c2495ea16a425f', 'rb') as f:
    content = zlib.decompress(f.read())
    print(content)
EOF

# 输出:b'blob 12\x00Hello, Git!'

3.4.2 打包对象(Packed Objects)

随着仓库的增长,松散对象会占用大量空间。Git会定期将松散对象打包成packfile以节省空间。

触发打包:

# 手动触发垃圾回收和打包
git gc

# 查看pack文件
ls .git/objects/pack/
# pack-1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t.idx
# pack-1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t.pack

Packfile的优化:

  1. Delta压缩:只存储文件的差异而不是完整内容
  2. 对象排序:相似的对象放在一起以提高压缩率
  3. 索引文件.idx文件用于快速查找pack中的对象

查看pack文件内容:

# 查看pack文件的对象列表
git verify-pack -v .git/objects/pack/pack-*.idx

# 输出示例:
# 8d0e4123 blob   12 20 12
# 3c4e9cd7 tree   36 46 32
# 1a2b3c4d commit 234 155 78
# ...

3.5 引用(References)

引用是指向提交对象的指针,存储在.git/refs目录下。

引用的类型:

.git/refs/
├── heads/          # 本地分支
│   ├── main
│   └── feature
├── tags/           # 标签
│   └── v1.0
└── remotes/        # 远程分支
    └── origin/
        ├── main
        └── feature

引用的内容:

# 查看main分支指向的提交
cat .git/refs/heads/main
# 输出:1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t

# 这就是一个40位的SHA-1哈希值

HEAD引用:

HEAD是一个特殊的引用,指向当前所在的分支。

# 查看HEAD
cat .git/HEAD
# 输出:ref: refs/heads/main

# HEAD指向main分支,main分支指向具体的提交

符号引用:

HEAD是一个符号引用(symbolic reference),它不直接指向提交,而是指向另一个引用。

# 查看HEAD指向的提交
git rev-parse HEAD
# 输出:1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t

# 等同于
cat .git/refs/heads/main

3.6 数据完整性

Git的所有数据在存储前都会计算校验和(SHA-1哈希值),然后以校验和来引用。这意味着:

  1. 不可能在Git不知情的情况下更改任何文件内容或目录内容
  2. 如果文件在传输过程中损坏,Git能够发现
  3. Git数据库中的所有内容都是通过哈希值引用的,而不是文件名

验证数据完整性:

# 检查仓库的完整性
git fsck

# 输出示例(如果一切正常):
# Checking object directories: 100% (256/256), done.
# Checking objects: 100% (12/12), done.

第四章:实战:本地开发与首次推送GitHub

现在让我们将前面学到的知识应用到实战中,创建一个真实的项目并推送到GitHub。

4.1 实战项目:创建个人博客网站

我们将创建一个简单的个人博客网站,包含HTML、CSS和JavaScript文件。

项目结构:

my-blog/
├── index.html
├── css/
│   └── style.css
├── js/
│   └── main.js
├── images/
│   └── avatar.jpg
└── README.md

4.2 初始化本地仓库

# 创建项目目录
mkdir my-blog
cd my-blog

# 初始化Git仓库
git init

# 输出:Initialized empty Git repository in /path/to/my-blog/.git/

# 查看仓库状态
git status

# 输出:
# On branch main
#
# No commits yet
#
# nothing to commit (create/copy files and use "git add" to track)

理解发生了什么:

  1. git init创建了.git目录
  2. 创建了默认分支main(因为我们之前配置了init.defaultBranch
  3. HEAD指向refs/heads/main,但该分支还不存在(因为还没有提交)

4.3 创建项目文件

创建README.md:

cat > README.md << 'EOF'
# My Personal Blog

A simple personal blog website built with HTML, CSS, and JavaScript.

## Features

- Responsive design
- Clean and modern UI
- Easy to customize

## Author

Your Name

## License

MIT License
EOF

创建index.html:

cat > index.html << 'EOF'
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的个人博客</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <h1>欢迎来到我的博客</h1>
        <nav>
            <a href="#home">首页</a>
            <a href="#about">关于</a>
            <a href="#contact">联系</a>
        </nav>
    </header>
    
    <main>
        <article>
            <h2>第一篇博客文章</h2>
            <p class="date">2025年10月18日</p>
            <p>这是我的第一篇博客文章。今天我学习了Git和GitHub,并成功创建了这个博客网站!</p>
        </article>
    </main>
    
    <footer>
        <p>&copy; 2025 我的博客. All rights reserved.</p>
    </footer>
    
    <script src="js/main.js"></script>
</body>
</html>
EOF

创建CSS文件:

mkdir css
cat > css/style.css << 'EOF'
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}

header {
    background: #4CAF50;
    color: white;
    padding: 20px;
    border-radius: 5px;
    margin-bottom: 20px;
}

header h1 {
    margin-bottom: 10px;
}

nav a {
    color: white;
    text-decoration: none;
    margin-right: 15px;
    padding: 5px 10px;
    border-radius: 3px;
    transition: background 0.3s;
}

nav a:hover {
    background: rgba(255, 255, 255, 0.2);
}

article {
    background: #f9f9f9;
    padding: 20px;
    margin-bottom: 20px;
    border-radius: 5px;
    border-left: 4px solid #4CAF50;
}

article h2 {
    color: #4CAF50;
    margin-bottom: 10px;
}

.date {
    color: #666;
    font-size: 0.9em;
    margin-bottom: 10px;
}

footer {
    text-align: center;
    padding: 20px;
    color: #666;
    border-top: 1px solid #ddd;
    margin-top: 20px;
}
EOF

创建JavaScript文件:

mkdir js
cat > js/main.js << 'EOF'
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
    console.log('博客页面加载完成!');
    
    // 添加平滑滚动
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function (e) {
            e.preventDefault();
            const target = document.querySelector(this.getAttribute('href'));
            if (target) {
                target.scrollIntoView({
                    behavior: 'smooth'
                });
            }
        });
    });
});
EOF

4.4 查看文件状态

# 查看仓库状态
git status

# 输出:
# On branch main
#
# No commits yet
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#         README.md
#         css/
#         index.html
#         js/
#
# nothing added to commit but untracked files present (use "git add" to track)

理解输出:

  • On branch main:当前在main分支上
  • No commits yet:还没有任何提交
  • Untracked files:这些文件还没有被Git跟踪

4.5 配置.gitignore

在添加文件前,先创建.gitignore文件,忽略不需要版本控制的文件。

cat > .gitignore << 'EOF'
# 操作系统文件
.DS_Store
Thumbs.db

# 编辑器文件
.vscode/
.idea/
*.swp

# 日志文件
*.log

# 临时文件
*.tmp
*~

# 依赖目录(如果以后添加)
node_modules/
vendor/

# 构建输出(如果以后添加)
dist/
build/
EOF

4.6 添加文件到暂存区

# 添加所有文件到暂存区
git add .

# 查看状态
git status

# 输出:
# On branch main
#
# No commits yet
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#         new file:   .gitignore
#         new file:   README.md
#         new file:   css/style.css
#         new file:   index.html
#         new file:   js/main.js

理解发生了什么:

  1. git add .将所有文件添加到暂存区
  2. Git创建了blob对象存储文件内容
  3. 暂存区(.git/index)记录了这些文件

查看暂存区:

# 查看暂存区的内容
git ls-files -s

# 输出示例:
# 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       .gitignore
# 100644 8d0e41234f24b6da002d962a26c2495ea16a425f 0       README.md
# 100644 3c4e9cd789f88c4e9cd789f88c4e9cd789f88c4e 0       css/style.css
# ...

4.7 创建第一次提交

# 提交到本地仓库
git commit -m "Initial commit: Add blog structure and basic styling"

# 输出:
# [main (root-commit) 1a2b3c4] Initial commit: Add blog structure and basic styling
#  5 files changed, 120 insertions(+)
#  create mode 100644 .gitignore
#  create mode 100644 README.md
#  create mode 100644 css/style.css
#  create mode 100644 index.html
#  create mode 100644 js/main.js

理解发生了什么:

  1. Git创建了tree对象表示项目目录结构
  2. Git创建了commit对象记录提交信息
  3. main分支指针指向这个新的commit对象
  4. HEAD指向main分支

查看提交历史:

# 查看提交历史
git log

# 输出:
# commit 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t (HEAD -> main)
# Author: Your Name <your.email@example.com>
# Date:   Mon Oct 18 10:30:00 2025 +0800
#
#     Initial commit: Add blog structure and basic styling

# 查看简洁的提交历史
git log --oneline

# 输出:
# 1a2b3c4 (HEAD -> main) Initial commit: Add blog structure and basic styling

4.8 继续开发:添加新功能

让我们添加一个新的博客文章。

# 修改index.html,添加第二篇文章
cat >> index.html << 'EOF'
    <article>
        <h2>学习Git的心得</h2>
        <p class="date">2025年10月19日</p>
        <p>今天深入学习了Git的底层原理,了解了blob、tree、commit对象的工作方式。Git真是一个精妙的设计!</p>
    </article>
EOF

注意:上面的命令会在文件末尾添加内容,实际应该插入到</main>标签之前。让我们正确修改:

# 使用编辑器修改文件(这里用sed演示)
# 实际操作中应该用文本编辑器

# 或者重新创建完整的index.html
cat > index.html << 'EOF'
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的个人博客</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <h1>欢迎来到我的博客</h1>
        <nav>
            <a href="#home">首页</a>
            <a href="#about">关于</a>
            <a href="#contact">联系</a>
        </nav>
    </header>
    
    <main>
        <article>
            <h2>第一篇博客文章</h2>
            <p class="date">2025年10月18日</p>
            <p>这是我的第一篇博客文章。今天我学习了Git和GitHub,并成功创建了这个博客网站!</p>
        </article>
        
        <article>
            <h2>学习Git的心得</h2>
            <p class="date">2025年10月19日</p>
            <p>今天深入学习了Git的底层原理,了解了blob、tree、commit对象的工作方式。Git真是一个精妙的设计!</p>
        </article>
    </main>
    
    <footer>
        <p>&copy; 2025 我的博客. All rights reserved.</p>
    </footer>
    
    <script src="js/main.js"></script>
</body>
</html>
EOF

查看修改:

# 查看工作区状态
git status

# 输出:
# On branch main
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git restore <file>..." to discard changes in working directory)
#         modified:   index.html

# 查看具体修改内容
git diff

# 输出会显示添加的新文章部分(以+开头的绿色行)

提交修改:

# 添加到暂存区
git add index.html

# 查看暂存区的修改
git diff --staged

# 提交
git commit -m "Add second blog post about Git internals"

# 查看提交历史
git log --oneline

# 输出:
# 2b3c4d5 (HEAD -> main) Add second blog post about Git internals
# 1a2b3c4 Initial commit: Add blog structure and basic styling

4.9 在GitHub上创建远程仓库

现在我们要将本地仓库推送到GitHub。

在GitHub上创建仓库:

  1. 登录GitHub
  2. 点击右上角的"+",选择"New repository"
  3. 填写仓库信息:
    • Repository name: my-blog
    • Description: "My personal blog website"
    • PublicPrivate: 选择Public(公开)
    • 不要勾选"Initialize this repository with a README"(我们已经有了)
  4. 点击"Create repository"

GitHub会显示快速设置指南,我们选择"push an existing repository from the command line"部分的命令。

4.10 连接远程仓库

# 添加远程仓库(使用SSH URL)
git remote add origin git@github.com:your-username/my-blog.git

# 验证远程仓库
git remote -v

# 输出:
# origin  git@github.com:your-username/my-blog.git (fetch)
# origin  git@github.com:your-username/my-blog.git (push)

理解远程仓库:

  • origin是远程仓库的默认名称(可以自定义)
  • git@github.com:your-username/my-blog.git是SSH URL
  • 也可以使用HTTPS URL:https://github.com/your-username/my-blog.git

4.11 首次推送到GitHub

# 推送main分支到远程仓库
git push -u origin main

# 输出:
# Enumerating objects: 10, done.
# Counting objects: 100% (10/10), done.
# Delta compression using up to 8 threads
# Compressing objects: 100% (8/8), done.
# Writing objects: 100% (10/10), 2.15 KiB | 2.15 MiB/s, done.
# Total 10 (delta 1), reused 0 (delta 0), pack-reused 0
# To github.com:your-username/my-blog.git
#  * [new branch]      main -> main
# Branch 'main' set up to track remote branch 'main' from 'origin'.

理解推送过程:

  1. Enumerating objects: Git枚举需要推送的对象
  2. Counting objects: 计算对象数量
  3. Compressing objects: 压缩对象以减少传输量
  4. Writing objects: 将对象写入远程仓库
  5. Branch 'main' set up to track: 设置本地main分支跟踪远程main分支

-u参数的作用:

-u(或--set-upstream)设置上游分支,之后可以直接使用git pushgit pull而不需要指定远程仓库和分支名。

# 第一次推送
git push -u origin main

# 之后的推送
git push  # 自动推送到origin/main

4.12 在GitHub上查看仓库

打开浏览器,访问https://github.com/your-username/my-blog,你会看到:

  • 所有文件都已上传
  • README.md的内容显示在仓库首页
  • 提交历史可以查看
  • 可以在线浏览代码

GitHub仓库页面的主要功能:

  • Code: 浏览代码和文件
  • Issues: 问题跟踪
  • Pull requests: 代码审查和合并请求
  • Actions: CI/CD自动化
  • Projects: 项目管理
  • Wiki: 文档
  • Settings: 仓库设置

4.13 继续开发:本地修改并推送

让我们继续改进博客。

修改CSS样式:

# 改进样式
cat > css/style.css << 'EOF'
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
    background: linear-gradient(to bottom, #f5f5f5, #ffffff);
}

header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 30px;
    border-radius: 10px;
    margin-bottom: 30px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

header h1 {
    margin-bottom: 15px;
    font-size: 2.5em;
}

nav a {
    color: white;
    text-decoration: none;
    margin-right: 20px;
    padding: 8px 15px;
    border-radius: 5px;
    transition: all 0.3s;
    display: inline-block;
}

nav a:hover {
    background: rgba(255, 255, 255, 0.2);
    transform: translateY(-2px);
}

article {
    background: white;
    padding: 25px;
    margin-bottom: 25px;
    border-radius: 10px;
    border-left: 5px solid #667eea;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    transition: transform 0.3s, box-shadow 0.3s;
}

article:hover {
    transform: translateY(-5px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}

article h2 {
    color: #667eea;
    margin-bottom: 10px;
    font-size: 1.8em;
}

.date {
    color: #999;
    font-size: 0.9em;
    margin-bottom: 15px;
    font-style: italic;
}

footer {
    text-align: center;
    padding: 20px;
    color: #666;
    border-top: 2px solid #eee;
    margin-top: 30px;
}
EOF

提交并推送:

# 查看修改
git status
git diff css/style.css

# 添加并提交
git add css/style.css
git commit -m "Improve blog styling with gradients and animations"

# 推送到GitHub
git push

# 输出:
# Enumerating objects: 7, done.
# Counting objects: 100% (7/7), done.
# Delta compression using up to 8 threads
# Compressing objects: 100% (4/4), done.
# Writing objects: 100% (4/4), 1.23 KiB | 1.23 MiB/s, done.
# Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
# To github.com:your-username/my-blog.git
#    2b3c4d5..3c4d5e6  main -> main

4.14 查看提交历史

# 查看详细的提交历史
git log

# 查看简洁的单行历史
git log --oneline

# 查看图形化的分支历史
git log --oneline --graph --all

# 输出:
# * 3c4d5e6 (HEAD -> main, origin/main) Improve blog styling with gradients and animations
# * 2b3c4d5 Add second blog post about Git internals
# * 1a2b3c4 Initial commit: Add blog structure and basic styling

# 查看每次提交的文件变化
git log --stat

# 查看每次提交的详细差异
git log -p

# 查看最近2次提交
git log -2

# 按作者过滤
git log --author="Your Name"

# 按时间过滤
git log --since="2 days ago"
git log --after="2025-10-01" --before="2025-10-31"

# 按提交信息搜索
git log --grep="styling"

4.15 使用GitHub Pages部署博客

GitHub Pages可以免费托管静态网站,让我们部署这个博客。

启用GitHub Pages:

  1. 在GitHub仓库页面,点击"Settings"
  2. 左侧菜单选择"Pages"
  3. 在"Source"下选择"Deploy from a branch"
  4. 选择"main"分支和"/ (root)"目录
  5. 点击"Save"

几分钟后,你的博客就会发布到https://your-username.github.io/my-blog/

访问你的博客:

打开浏览器访问https://your-username.github.io/my-blog/,你会看到你的博客已经上线了!


本篇小结

恭喜你完成了Git学习的第一步!让我们回顾一下本篇学到的内容:

核心概念:

  1. 版本控制系统的演进

    • 本地VCS → 集中式VCS(SVN) → 分布式VCS(Git)
    • Git的核心优势:分布式、快照存储、强大的分支
  2. Git的三个工作区域

    • 工作区(Working Directory)
    • 暂存区(Staging Area)
    • 本地仓库(Local Repository)
    • 远程仓库(Remote Repository)
  3. Git的底层原理

    • 四大对象:Blob、Tree、Commit、Tag
    • SHA-1哈希算法保证数据完整性
    • 内容寻址文件系统
    • 松散对象和打包对象
  4. 实战技能

    • 安装和配置Git
    • 配置GitHub和SSH密钥
    • 初始化本地仓库
    • 添加、提交、推送代码
    • 查看历史和状态
    • 部署到GitHub Pages

实战成果:

✅ 创建了一个完整的博客项目
✅ 掌握了Git的基本工作流程
✅ 成功推送代码到GitHub
✅ 部署了在线可访问的网站

下一篇预告:

在《Git完全指南(中篇):GitHub团队协作实战》中,我们将继续这个博客项目,学习:

  • 分支管理:为新功能创建分支,理解分支的本质
  • 团队协作:模拟多人协作开发场景
  • Pull Request:代码审查和合并流程
  • 冲突解决:处理多人修改同一文件的情况
  • Git工作流:学习业界标准的协作模式

我们将邀请"队友"(模拟)一起开发博客的新功能,体验真实的团队协作流程!