目录
第一章: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的设计目标:
- 速度:绝大多数操作都在本地完成
- 简单的设计:核心概念简洁明了
- 强力支持非线性开发:支持成千上万个并行分支
- 完全分布式:每个仓库都是完整的
- 能够高效管理大型项目:如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。用于团队协作和代码备份。
基本工作流程:
- 在工作区修改文件
- 使用
git add将修改添加到暂存区 - 使用
git commit将暂存区的内容提交到本地仓库 - 使用
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上安装:
-
下载安装程序
-
运行安装程序,推荐配置:
- 选择"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
-
打开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是全球最大的代码托管平台,我们将用它来托管项目。
注册步骤:
- 访问github.com
- 点击"Sign up"注册账号
- 填写用户名、邮箱、密码
- 验证邮箱
- 完成新手引导
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密钥:
- 登录GitHub
- 点击右上角头像 → Settings
- 左侧菜单选择"SSH and GPG keys"
- 点击"New SSH key"
- 填写Title(如"My Laptop")
- 将复制的公钥粘贴到Key字段
- 点击"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有四种对象类型:
- Blob对象:存储文件内容
- Tree对象:存储目录结构和文件名
- Commit对象:存储提交信息
- 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的特性:
- 确定性:相同的输入总是产生相同的输出
- 唯一性:不同的输入几乎不可能产生相同的输出(碰撞概率极低)
- 不可逆:无法从哈希值反推原始内容
- 雪崩效应:输入的微小变化会导致输出的巨大变化
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的使用场景中,这个问题的影响有限:
- Git正在逐步迁移到SHA-256
- GitHub等平台会检测并拒绝已知的碰撞文件
- 实际攻击难度极高,需要巨大的计算资源
3.4 对象存储机制
3.4.1 松散对象(Loose Objects)
Git最初以松散对象格式存储对象。每个对象都是一个单独的文件。
存储过程:
- 计算内容的SHA-1哈希值
- 使用zlib压缩内容
- 将压缩后的内容写入
.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的优化:
- Delta压缩:只存储文件的差异而不是完整内容
- 对象排序:相似的对象放在一起以提高压缩率
- 索引文件:
.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哈希值),然后以校验和来引用。这意味着:
- 不可能在Git不知情的情况下更改任何文件内容或目录内容
- 如果文件在传输过程中损坏,Git能够发现
- 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)
理解发生了什么:
git init创建了.git目录- 创建了默认分支
main(因为我们之前配置了init.defaultBranch) - 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>© 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
理解发生了什么:
git add .将所有文件添加到暂存区- Git创建了blob对象存储文件内容
- 暂存区(
.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
理解发生了什么:
- Git创建了tree对象表示项目目录结构
- Git创建了commit对象记录提交信息
- main分支指针指向这个新的commit对象
- 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>© 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上创建仓库:
- 登录GitHub
- 点击右上角的"+",选择"New repository"
- 填写仓库信息:
- Repository name:
my-blog - Description: "My personal blog website"
- Public 或 Private: 选择Public(公开)
- 不要勾选"Initialize this repository with a README"(我们已经有了)
- Repository name:
- 点击"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'.
理解推送过程:
- Enumerating objects: Git枚举需要推送的对象
- Counting objects: 计算对象数量
- Compressing objects: 压缩对象以减少传输量
- Writing objects: 将对象写入远程仓库
- Branch 'main' set up to track: 设置本地main分支跟踪远程main分支
-u参数的作用:
-u(或--set-upstream)设置上游分支,之后可以直接使用git push和git 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:
- 在GitHub仓库页面,点击"Settings"
- 左侧菜单选择"Pages"
- 在"Source"下选择"Deploy from a branch"
- 选择"main"分支和"/ (root)"目录
- 点击"Save"
几分钟后,你的博客就会发布到https://your-username.github.io/my-blog/
访问你的博客:
打开浏览器访问https://your-username.github.io/my-blog/,你会看到你的博客已经上线了!
本篇小结
恭喜你完成了Git学习的第一步!让我们回顾一下本篇学到的内容:
核心概念:
-
版本控制系统的演进:
- 本地VCS → 集中式VCS(SVN) → 分布式VCS(Git)
- Git的核心优势:分布式、快照存储、强大的分支
-
Git的三个工作区域:
- 工作区(Working Directory)
- 暂存区(Staging Area)
- 本地仓库(Local Repository)
- 远程仓库(Remote Repository)
-
Git的底层原理:
- 四大对象:Blob、Tree、Commit、Tag
- SHA-1哈希算法保证数据完整性
- 内容寻址文件系统
- 松散对象和打包对象
-
实战技能:
- 安装和配置Git
- 配置GitHub和SSH密钥
- 初始化本地仓库
- 添加、提交、推送代码
- 查看历史和状态
- 部署到GitHub Pages
实战成果:
✅ 创建了一个完整的博客项目
✅ 掌握了Git的基本工作流程
✅ 成功推送代码到GitHub
✅ 部署了在线可访问的网站
下一篇预告:
在《Git完全指南(中篇):GitHub团队协作实战》中,我们将继续这个博客项目,学习:
- 分支管理:为新功能创建分支,理解分支的本质
- 团队协作:模拟多人协作开发场景
- Pull Request:代码审查和合并流程
- 冲突解决:处理多人修改同一文件的情况
- Git工作流:学习业界标准的协作模式
我们将邀请"队友"(模拟)一起开发博客的新功能,体验真实的团队协作流程!