目录
- 分支管理的艺术
- 实战:开发评论系统功能
- [Pull Request工作流](#Pull Request工作流 "#%E7%AC%AC%E4%B8%89%E7%AB%A0pull-request%E5%B7%A5%E4%BD%9C%E6%B5%81")
- 冲突解决实战
- 团队协作工作流
第一章:分支管理的艺术
1.1 分支的本质
在Git中,分支本质上仅仅是指向提交对象的可变指针。这是Git最重要的概念之一。
当前的提交历史:
bash
# 查看当前提交历史
cd my-blog
git log --oneline --graph
# 输出:
# * 3c4d5e6 (HEAD -> main, origin/main) Improve blog styling
# * 2b3c4d5 Add second blog post
# * 1a2b3c4 Initial commit
分支的底层实现:
bash
# 查看main分支的内容
cat .git/refs/heads/main
# 输出:3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v
# 这就是main分支:一个包含提交SHA-1哈希的文件
HEAD指针:
bash
# 查看HEAD指向
cat .git/HEAD
# 输出:ref: refs/heads/main
# HEAD指向main分支,main分支指向最新提交
可视化理解:
scss
提交对象链:
1a2b3c4 ← 2b3c4d5 ← 3c4d5e6
↑
main (分支指针)
↑
HEAD (当前位置)
1.2 创建和切换分支
让我们为新功能创建分支。
场景:添加评论系统
bash
# 创建新分支(但不切换)
git branch feature/comments
# 查看所有分支
git branch
# 输出:
# feature/comments
# * main # *表示当前分支
# 查看分支详情
git branch -v
# 输出:
# feature/comments 3c4d5e6 Improve blog styling
# * main 3c4d5e6 Improve blog styling
# 两个分支都指向同一个提交
切换分支:
bash
# 方法一:使用checkout(传统方式)
git checkout feature/comments
# 方法二:使用switch(Git 2.23+,更直观)
git switch feature/comments
# 输出:
# Switched to branch 'feature/comments'
# 查看当前分支
git branch
# 输出:
# * feature/comments
# main
一步创建并切换:
bash
# 使用checkout
git checkout -b feature/dark-mode
# 使用switch(推荐)
git switch -c feature/dark-mode
# 切回feature/comments分支
git switch feature/comments
底层发生了什么:
bash
# 切换分支后,HEAD指向新分支
cat .git/HEAD
# 输出:ref: refs/heads/feature/comments
# 工作目录的文件没有变化(因为两个分支指向同一提交)
1.3 在分支上开发
现在让我们在feature/comments
分支上开发评论系统。
创建评论系统的HTML结构:
bash
# 修改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>
<!-- 评论系统 -->
<div class="comments-section">
<h3>评论 (2)</h3>
<div class="comment">
<div class="comment-author">张三</div>
<div class="comment-date">2025年10月18日 15:30</div>
<div class="comment-content">写得不错!期待更多文章。</div>
</div>
<div class="comment">
<div class="comment-author">李四</div>
<div class="comment-date">2025年10月18日 16:45</div>
<div class="comment-content">Git确实很强大,我也在学习中。</div>
</div>
<div class="comment-form">
<h4>发表评论</h4>
<input type="text" id="author-name" placeholder="你的名字" required>
<textarea id="comment-text" placeholder="写下你的评论..." required></textarea>
<button id="submit-comment">提交评论</button>
</div>
</div>
</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
添加评论系统的样式:
bash
# 在style.css末尾添加评论样式
cat >> css/style.css << 'EOF'
/* 评论系统样式 */
.comments-section {
margin-top: 30px;
padding-top: 20px;
border-top: 2px solid #eee;
}
.comments-section h3 {
color: #667eea;
margin-bottom: 20px;
}
.comment {
background: #f9f9f9;
padding: 15px;
margin-bottom: 15px;
border-radius: 5px;
border-left: 3px solid #667eea;
}
.comment-author {
font-weight: bold;
color: #333;
margin-bottom: 5px;
}
.comment-date {
font-size: 0.85em;
color: #999;
margin-bottom: 10px;
}
.comment-content {
color: #555;
line-height: 1.6;
}
.comment-form {
margin-top: 25px;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
}
.comment-form h4 {
color: #667eea;
margin-bottom: 15px;
}
.comment-form input,
.comment-form textarea {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-family: inherit;
font-size: 1em;
}
.comment-form textarea {
min-height: 100px;
resize: vertical;
}
.comment-form button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 12px 30px;
border: none;
border-radius: 5px;
font-size: 1em;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.comment-form button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
}
.comment-form button:active {
transform: translateY(0);
}
EOF
添加评论功能的JavaScript:
bash
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'
});
}
});
});
// 评论提交功能
const submitButton = document.getElementById('submit-comment');
if (submitButton) {
submitButton.addEventListener('click', function() {
const authorName = document.getElementById('author-name').value.trim();
const commentText = document.getElementById('comment-text').value.trim();
if (!authorName || !commentText) {
alert('请填写姓名和评论内容!');
return;
}
// 创建新评论元素
const commentsSection = document.querySelector('.comments-section');
const commentForm = document.querySelector('.comment-form');
const newComment = document.createElement('div');
newComment.className = 'comment';
newComment.innerHTML = `
<div class="comment-author">${escapeHtml(authorName)}</div>
<div class="comment-date">${new Date().toLocaleString('zh-CN')}</div>
<div class="comment-content">${escapeHtml(commentText)}</div>
`;
// 插入新评论
commentsSection.insertBefore(newComment, commentForm);
// 更新评论数量
const commentCount = commentsSection.querySelectorAll('.comment').length;
commentsSection.querySelector('h3').textContent = `评论 (${commentCount})`;
// 清空表单
document.getElementById('author-name').value = '';
document.getElementById('comment-text').value = '';
// 显示成功消息
alert('评论发表成功!');
});
}
});
// HTML转义函数,防止XSS攻击
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
EOF
提交更改:
bash
# 查看修改
git status
# 输出:
# On branch feature/comments
# Changes not staged for commit:
# modified: css/style.css
# modified: index.html
# modified: js/main.js
# 查看具体修改
git diff
# 添加所有修改
git add .
# 提交
git commit -m "Add comment system with form and styling"
# 查看提交历史
git log --oneline --graph --all
# 输出:
# * 4d5e6f7 (HEAD -> feature/comments) Add comment system
# * 3c4d5e6 (origin/main, main) Improve blog styling
# * 2b3c4d5 Add second blog post
# * 1a2b3c4 Initial commit
可视化分支状态:
bash
1a2b3c4 ← 2b3c4d5 ← 3c4d5e6 ← 4d5e6f7
↑ ↑
main feature/comments (HEAD)
1.4 切换分支查看差异
bash
# 切换回main分支
git switch main
# 打开index.html,你会发现评论系统不见了!
# 因为main分支还是旧的版本
# 查看当前提交
git log --oneline -1
# 输出:3c4d5e6 (HEAD -> main, origin/main) Improve blog styling
# 切换回feature/comments分支
git switch feature/comments
# 评论系统又回来了!
git log --oneline -1
# 输出:4d5e6f7 (HEAD -> feature/comments) Add comment system
理解发生了什么:
切换分支时,Git做了三件事:
- 移动HEAD指针到目标分支
- 将暂存区恢复为该分支指向的提交
- 将工作目录的文件恢复为该分支的快照
1.5 合并分支
现在让我们将评论系统合并到main分支。
快进合并(Fast-forward):
bash
# 切换到main分支
git switch main
# 合并feature/comments分支
git merge feature/comments
# 输出:
# Updating 3c4d5e6..4d5e6f7
# Fast-forward
# css/style.css | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++
# index.html | 28 ++++++++++++++++++++++
# js/main.js | 45 ++++++++++++++++++++++++++++++++++
# 3 files changed, 138 insertions(+)
# 查看提交历史
git log --oneline --graph
# 输出:
# * 4d5e6f7 (HEAD -> main, feature/comments) Add comment system
# * 3c4d5e6 (origin/main) Improve blog styling
# * 2b3c4d5 Add second blog post
# * 1a2b3c4 Initial commit
什么是快进合并?
当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,Git会简单地将指针向前移动。这叫做"快进"(fast-forward)。
bash
合并前:
1a2b3c4 ← 2b3c4d5 ← 3c4d5e6 ← 4d5e6f7
↑ ↑
main feature/comments
合并后(快进):
1a2b3c4 ← 2b3c4d5 ← 3c4d5e6 ← 4d5e6f7
↑
main, feature/comments
删除已合并的分支:
bash
# 删除feature/comments分支
git branch -d feature/comments
# 输出:
# Deleted branch feature/comments (was 4d5e6f7).
# 查看分支列表
git branch
# 输出:
# * main
推送到GitHub:
bash
# 推送main分支
git push
# 输出:
# Enumerating objects: 11, done.
# ...
# To github.com:your-username/my-blog.git
# 3c4d5e6..4d5e6f7 main -> main
第二章:实战:开发评论系统功能
2.1 模拟团队协作场景
现在让我们模拟一个更复杂的场景:你和队友同时开发不同的功能。
场景设置:
- 你:开发"深色模式"功能
- 队友:开发"文章分类"功能
我们将使用两个本地目录模拟两个开发者。
2.2 克隆仓库(模拟队友)
bash
# 回到上级目录
cd ..
# 克隆仓库到新目录(模拟队友的工作环境)
git clone git@github.com:your-username/my-blog.git my-blog-teammate
# 进入队友的目录
cd my-blog-teammate
# 查看远程仓库
git remote -v
# 输出:
# origin git@github.com:your-username/my-blog.git (fetch)
# origin git@github.com:your-username/my-blog.git (push)
# 查看提交历史
git log --oneline
# 输出:
# 4d5e6f7 (HEAD -> main, origin/main, origin/HEAD) Add comment system
# 3c4d5e6 Improve blog styling
# 2b3c4d5 Add second blog post
# 1a2b3c4 Initial commit
2.3 你:开发深色模式功能
bash
# 回到你的工作目录
cd ../my-blog
# 创建深色模式分支
git switch -c feature/dark-mode
# 修改CSS,添加深色模式
cat >> css/style.css << 'EOF'
/* 深色模式 */
body.dark-mode {
background: linear-gradient(to bottom, #1a1a1a, #2d2d2d);
color: #e0e0e0;
}
body.dark-mode header {
background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
}
body.dark-mode article {
background: #2d2d2d;
color: #e0e0e0;
border-left-color: #4a5568;
}
body.dark-mode article h2 {
color: #90cdf4;
}
body.dark-mode .comment {
background: #3a3a3a;
border-left-color: #4a5568;
}
body.dark-mode .comment-form {
background: #3a3a3a;
}
body.dark-mode .comment-form input,
body.dark-mode .comment-form textarea {
background: #2d2d2d;
border-color: #4a5568;
color: #e0e0e0;
}
/* 深色模式切换按钮 */
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 20px;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: all 0.3s;
z-index: 1000;
}
.theme-toggle:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
body.dark-mode .theme-toggle {
background: #4a5568;
}
EOF
添加深色模式切换按钮和功能:
bash
# 在index.html的body开头添加切换按钮
# 这里我们重新生成完整的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>
<button class="theme-toggle" id="theme-toggle">🌙 深色模式</button>
<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>
<!-- 评论系统 -->
<div class="comments-section">
<h3>评论 (2)</h3>
<div class="comment">
<div class="comment-author">张三</div>
<div class="comment-date">2025年10月18日 15:30</div>
<div class="comment-content">写得不错!期待更多文章。</div>
</div>
<div class="comment">
<div class="comment-author">李四</div>
<div class="comment-date">2025年10月18日 16:45</div>
<div class="comment-content">Git确实很强大,我也在学习中。</div>
</div>
<div class="comment-form">
<h4>发表评论</h4>
<input type="text" id="author-name" placeholder="你的名字" required>
<textarea id="comment-text" placeholder="写下你的评论..." required></textarea>
<button id="submit-comment">提交评论</button>
</div>
</div>
</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
添加深色模式切换逻辑:
bash
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'
});
}
});
});
// 深色模式切换
const themeToggle = document.getElementById('theme-toggle');
const body = document.body;
// 从localStorage读取主题设置
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
body.classList.add('dark-mode');
themeToggle.textContent = '☀️ 浅色模式';
}
themeToggle.addEventListener('click', function() {
body.classList.toggle('dark-mode');
if (body.classList.contains('dark-mode')) {
themeToggle.textContent = '☀️ 浅色模式';
localStorage.setItem('theme', 'dark');
} else {
themeToggle.textContent = '🌙 深色模式';
localStorage.setItem('theme', 'light');
}
});
// 评论提交功能
const submitButton = document.getElementById('submit-comment');
if (submitButton) {
submitButton.addEventListener('click', function() {
const authorName = document.getElementById('author-name').value.trim();
const commentText = document.getElementById('comment-text').value.trim();
if (!authorName || !commentText) {
alert('请填写姓名和评论内容!');
return;
}
// 创建新评论元素
const commentsSection = document.querySelector('.comments-section');
const commentForm = document.querySelector('.comment-form');
const newComment = document.createElement('div');
newComment.className = 'comment';
newComment.innerHTML = `
<div class="comment-author">${escapeHtml(authorName)}</div>
<div class="comment-date">${new Date().toLocaleString('zh-CN')}</div>
<div class="comment-content">${escapeHtml(commentText)}</div>
`;
// 插入新评论
commentsSection.insertBefore(newComment, commentForm);
// 更新评论数量
const commentCount = commentsSection.querySelectorAll('.comment').length;
commentsSection.querySelector('h3').textContent = `评论 (${commentCount})`;
// 清空表单
document.getElementById('author-name').value = '';
document.getElementById('comment-text').value = '';
// 显示成功消息
alert('评论发表成功!');
});
}
});
// HTML转义函数,防止XSS攻击
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
EOF
提交深色模式功能:
bash
# 查看修改
git status
# 添加并提交
git add .
git commit -m "Add dark mode toggle feature"
# 查看提交历史
git log --oneline --graph
# 输出:
# * 5e6f7g8 (HEAD -> feature/dark-mode) Add dark mode toggle feature
# * 4d5e6f7 (origin/main, main) Add comment system
# * 3c4d5e6 Improve blog styling
# * 2b3c4d5 Add second blog post
# * 1a2b3c4 Initial commit
2.4 队友:开发文章分类功能
bash
# 切换到队友的工作目录
cd ../my-blog-teammate
# 创建分类功能分支
git switch -c feature/categories
# 修改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>
<!-- 分类筛选 -->
<div class="categories">
<button class="category-btn active" data-category="all">全部</button>
<button class="category-btn" data-category="tech">技术</button>
<button class="category-btn" data-category="life">生活</button>
<button class="category-btn" data-category="learning">学习</button>
</div>
</header>
<main>
<article data-category="tech learning">
<div class="article-tags">
<span class="tag">技术</span>
<span class="tag">学习</span>
</div>
<h2>第一篇博客文章</h2>
<p class="date">2025年10月18日</p>
<p>这是我的第一篇博客文章。今天我学习了Git和GitHub,并成功创建了这个博客网站!</p>
<!-- 评论系统 -->
<div class="comments-section">
<h3>评论 (2)</h3>
<div class="comment">
<div class="comment-author">张三</div>
<div class="comment-date">2025年10月18日 15:30</div>
<div class="comment-content">写得不错!期待更多文章。</div>
</div>
<div class="comment">
<div class="comment-author">李四</div>
<div class="comment-date">2025年10月18日 16:45</div>
<div class="comment-content">Git确实很强大,我也在学习中。</div>
</div>
<div class="comment-form">
<h4>发表评论</h4>
<input type="text" id="author-name" placeholder="你的名字" required>
<textarea id="comment-text" placeholder="写下你的评论..." required></textarea>
<button id="submit-comment">提交评论</button>
</div>
</div>
</article>
<article data-category="tech learning">
<div class="article-tags">
<span class="tag">技术</span>
<span class="tag">学习</span>
</div>
<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
添加分类样式:
bash
cat >> css/style.css << 'EOF'
/* 分类筛选 */
.categories {
margin-top: 20px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.category-btn {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
font-size: 0.9em;
}
.category-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.category-btn.active {
background: white;
color: #667eea;
border-color: white;
}
/* 文章标签 */
.article-tags {
margin-bottom: 15px;
}
.tag {
display: inline-block;
background: #e0e7ff;
color: #667eea;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.85em;
margin-right: 8px;
}
/* 隐藏不匹配分类的文章 */
article.hidden {
display: none;
}
EOF
添加分类筛选功能:
bash
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'
});
}
});
});
// 分类筛选功能
const categoryButtons = document.querySelectorAll('.category-btn');
const articles = document.querySelectorAll('article');
categoryButtons.forEach(button => {
button.addEventListener('click', function() {
// 移除所有按钮的active类
categoryButtons.forEach(btn => btn.classList.remove('active'));
// 添加当前按钮的active类
this.classList.add('active');
const selectedCategory = this.dataset.category;
articles.forEach(article => {
if (selectedCategory === 'all') {
article.classList.remove('hidden');
} else {
const articleCategories = article.dataset.category.split(' ');
if (articleCategories.includes(selectedCategory)) {
article.classList.remove('hidden');
} else {
article.classList.add('hidden');
}
}
});
});
});
// 评论提交功能
const submitButton = document.getElementById('submit-comment');
if (submitButton) {
submitButton.addEventListener('click', function() {
const authorName = document.getElementById('author-name').value.trim();
const commentText = document.getElementById('comment-text').value.trim();
if (!authorName || !commentText) {
alert('请填写姓名和评论内容!');
return;
}
// 创建新评论元素
const commentsSection = document.querySelector('.comments-section');
const commentForm = document.querySelector('.comment-form');
const newComment = document.createElement('div');
newComment.className = 'comment';
newComment.innerHTML = `
<div class="comment-author">${escapeHtml(authorName)}</div>
<div class="comment-date">${new Date().toLocaleString('zh-CN')}</div>
<div class="comment-content">${escapeHtml(commentText)}</div>
`;
// 插入新评论
commentsSection.insertBefore(newComment, commentForm);
// 更新评论数量
const commentCount = commentsSection.querySelectorAll('.comment').length;
commentsSection.querySelector('h3').textContent = `评论 (${commentCount})`;
// 清空表单
document.getElementById('author-name').value = '';
document.getElementById('comment-text').value = '';
// 显示成功消息
alert('评论发表成功!');
});
}
});
// HTML转义函数,防止XSS攻击
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
EOF
提交分类功能:
bash
# 添加并提交
git add .
git commit -m "Add article categories and filtering"
# 查看提交历史
git log --oneline --graph
# 输出:
# * 6f7g8h9 (HEAD -> feature/categories) Add article categories and filtering
# * 4d5e6f7 (origin/main, origin/HEAD, main) Add comment system
# * 3c4d5e6 Improve blog styling
# * 2b3c4d5 Add second blog post
# * 1a2b3c4 Initial commit
第三章:Pull Request工作流
3.1 推送功能分支到GitHub
你:推送深色模式分支
bash
# 回到你的工作目录
cd ../my-blog
# 推送feature/dark-mode分支到GitHub
git push -u origin feature/dark-mode
# 输出:
# Enumerating objects: 11, done.
# ...
# To github.com:your-username/my-blog.git
# * [new branch] feature/dark-mode -> feature/dark-mode
# Branch 'feature/dark-mode' set up to track remote branch 'feature/dark-mode' from 'origin'.
队友:推送分类分支
bash
# 在队友的工作目录
cd ../my-blog-teammate
# 推送feature/categories分支
git push -u origin feature/categories
# 输出类似
查看GitHub上的分支:
在GitHub仓库页面,点击"branches",你会看到三个分支:
main
(默认分支)feature/dark-mode
feature/categories
3.2 创建Pull Request
创建深色模式的PR:
-
在GitHub仓库页面,点击"Pull requests"标签
-
点击"New pull request"
-
选择base分支为
main
,compare分支为feature/dark-mode
-
填写PR信息:
-
Title: "Add dark mode toggle feature"
-
Description :
markdown## 功能说明 添加了深色模式切换功能,用户可以通过右上角的按钮切换主题。 ## 主要变更 - 添加深色模式CSS样式 - 添加主题切换按钮 - 使用localStorage保存用户偏好 ## 测试 - [x] 深色模式样式正常 - [x] 切换功能正常 - [x] 主题偏好保存正常 ## 截图 (这里可以添加截图)
-
-
点击"Create pull request"
创建分类功能的PR:
同样的步骤,为feature/categories
分支创建PR。
3.3 代码审查
在PR页面,你可以:
查看文件变更:
- 点击"Files changed"标签
- 查看每个文件的具体修改
- 绿色行是新增内容,红色行是删除内容
添加评论:
- 在代码行旁边点击"+"图标
- 添加评论或建议
- 可以建议具体的代码修改
示例评论:
csharp
在第45行,建议添加错误处理:
```suggestion
try {
localStorage.setItem('theme', 'dark');
} catch (e) {
console.error('无法保存主题设置:', e);
}
bash
**请求修改或批准:**
- 点击"Review changes"
- 选择"Comment"(仅评论)、"Approve"(批准)或"Request changes"(请求修改)
- 添加总体评论
- 点击"Submit review"
### 3.4 合并Pull Request
**合并策略:**
GitHub提供三种合并策略:
| 策略 | 说明 | 提交历史 |
|:---|:---|:---|
| **Merge commit** | 创建合并提交 | 保留完整的分支历史 |
| **Squash and merge** | 压缩所有提交为一个 | 简洁的线性历史 |
| **Rebase and merge** | 变基后合并 | 线性历史,保留单独的提交 |
**合并深色模式PR:**
1. 在PR页面,点击"Merge pull request"
2. 选择"Create a merge commit"(默认)
3. 确认合并
4. 删除远程分支(GitHub会提示)
**本地同步:**
```bash
# 回到你的工作目录
cd ../my-blog
# 切换到main分支
git switch main
# 拉取最新代码
git pull
# 输出:
# remote: Enumerating objects: 1, done.
# ...
# Updating 4d5e6f7..7g8h9i0
# Fast-forward
# css/style.css | 45 +++++++++++++++++++++++++++++++++++++++++++++
# index.html | 2 ++
# js/main.js | 20 ++++++++++++++++++++
# 3 files changed, 67 insertions(+)
# 删除本地的feature/dark-mode分支
git branch -d feature/dark-mode
第四章:冲突解决实战
4.1 制造冲突
现在让我们合并分类功能,这会产生冲突,因为两个分支都修改了同样的文件。
队友:尝试合并分类PR
在GitHub上尝试合并feature/categories
的PR,会看到冲突提示:
This branch has conflicts that must be resolved
查看冲突文件:
index.html
:两个分支都修改了这个文件css/style.css
:两个分支都添加了新内容js/main.js
:两个分支都修改了这个文件
4.2 在本地解决冲突
bash
# 在队友的工作目录
cd ../my-blog-teammate
# 切换到feature/categories分支
git switch feature/categories
# 拉取main分支的最新代码
git fetch origin
# 合并origin/main到当前分支
git merge origin/main
# 输出:
# Auto-merging js/main.js
# Auto-merging index.html
# CONFLICT (content): Merge conflict in index.html
# Auto-merging css/style.css
# Automatic merge failed; fix conflicts and then commit the result.
查看冲突状态:
bash
git status
# 输出:
# On branch feature/categories
# You have unmerged paths.
# (fix conflicts and run "git commit")
# (use "git merge --abort" to abort the merge)
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
# both modified: index.html
#
# no changes added to commit (use "git add" and/or "git commit -a")
4.3 理解冲突标记
打开index.html
,你会看到冲突标记:
html
<<<<<<< HEAD (feature/categories的内容)
<header>
<h1>欢迎来到我的博客</h1>
<nav>
<a href="#home">首页</a>
<a href="#about">关于</a>
<a href="#contact">联系</a>
</nav>
<!-- 分类筛选 -->
<div class="categories">
<button class="category-btn active" data-category="all">全部</button>
<button class="category-btn" data-category="tech">技术</button>
<button class="category-btn" data-category="life">生活</button>
<button class="category-btn" data-category="learning">学习</button>
</div>
</header>
=======
<body>
<button class="theme-toggle" id="theme-toggle">🌙 深色模式</button>
<header>
<h1>欢迎来到我的博客</h1>
<nav>
<a href="#home">首页</a>
<a href="#about">关于</a>
<a href="#contact">联系</a>
</nav>
</header>
>>>>>>> origin/main (main分支的内容)
冲突标记说明:
<<<<<<< HEAD
:当前分支的内容开始=======
:分隔符>>>>>>> origin/main
:被合并分支的内容结束
4.4 手动解决冲突
我们需要保留两个功能,手动编辑文件:
bash
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>
<!-- 深色模式切换按钮 -->
<button class="theme-toggle" id="theme-toggle">🌙 深色模式</button>
<header>
<h1>欢迎来到我的博客</h1>
<nav>
<a href="#home">首页</a>
<a href="#about">关于</a>
<a href="#contact">联系</a>
</nav>
<!-- 分类筛选 -->
<div class="categories">
<button class="category-btn active" data-category="all">全部</button>
<button class="category-btn" data-category="tech">技术</button>
<button class="category-btn" data-category="life">生活</button>
<button class="category-btn" data-category="learning">学习</button>
</div>
</header>
<main>
<article data-category="tech learning">
<div class="article-tags">
<span class="tag">技术</span>
<span class="tag">学习</span>
</div>
<h2>第一篇博客文章</h2>
<p class="date">2025年10月18日</p>
<p>这是我的第一篇博客文章。今天我学习了Git和GitHub,并成功创建了这个博客网站!</p>
<!-- 评论系统 -->
<div class="comments-section">
<h3>评论 (2)</h3>
<div class="comment">
<div class="comment-author">张三</div>
<div class="comment-date">2025年10月18日 15:30</div>
<div class="comment-content">写得不错!期待更多文章。</div>
</div>
<div class="comment">
<div class="comment-author">李四</div>
<div class="comment-date">2025年10月18日 16:45</div>
<div class="comment-content">Git确实很强大,我也在学习中。</div>
</div>
<div class="comment-form">
<h4>发表评论</h4>
<input type="text" id="author-name" placeholder="你的名字" required>
<textarea id="comment-text" placeholder="写下你的评论..." required></textarea>
<button id="submit-comment">提交评论</button>
</div>
</div>
</article>
<article data-category="tech learning">
<div class="article-tags">
<span class="tag">技术</span>
<span class="tag">学习</span>
</div>
<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
合并JavaScript文件:
两个分支都修改了js/main.js
,我们需要合并两个功能:
bash
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'
});
}
});
});
// 深色模式切换
const themeToggle = document.getElementById('theme-toggle');
const body = document.body;
// 从localStorage读取主题设置
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
body.classList.add('dark-mode');
themeToggle.textContent = '☀️ 浅色模式';
}
themeToggle.addEventListener('click', function() {
body.classList.toggle('dark-mode');
if (body.classList.contains('dark-mode')) {
themeToggle.textContent = '☀️ 浅色模式';
localStorage.setItem('theme', 'dark');
} else {
themeToggle.textContent = '🌙 深色模式';
localStorage.setItem('theme', 'light');
}
});
// 分类筛选功能
const categoryButtons = document.querySelectorAll('.category-btn');
const articles = document.querySelectorAll('article');
categoryButtons.forEach(button => {
button.addEventListener('click', function() {
// 移除所有按钮的active类
categoryButtons.forEach(btn => btn.classList.remove('active'));
// 添加当前按钮的active类
this.classList.add('active');
const selectedCategory = this.dataset.category;
articles.forEach(article => {
if (selectedCategory === 'all') {
article.classList.remove('hidden');
} else {
const articleCategories = article.dataset.category.split(' ');
if (articleCategories.includes(selectedCategory)) {
article.classList.remove('hidden');
} else {
article.classList.add('hidden');
}
}
});
});
});
// 评论提交功能
const submitButton = document.getElementById('submit-comment');
if (submitButton) {
submitButton.addEventListener('click', function() {
const authorName = document.getElementById('author-name').value.trim();
const commentText = document.getElementById('comment-text').value.trim();
if (!authorName || !commentText) {
alert('请填写姓名和评论内容!');
return;
}
// 创建新评论元素
const commentsSection = document.querySelector('.comments-section');
const commentForm = document.querySelector('.comment-form');
const newComment = document.createElement('div');
newComment.className = 'comment';
newComment.innerHTML = `
<div class="comment-author">${escapeHtml(authorName)}</div>
<div class="comment-date">${new Date().toLocaleString('zh-CN')}</div>
<div class="comment-content">${escapeHtml(commentText)}</div>
`;
// 插入新评论
commentsSection.insertBefore(newComment, commentForm);
// 更新评论数量
const commentCount = commentsSection.querySelectorAll('.comment').length;
commentsSection.querySelector('h3').textContent = `评论 (${commentCount})`;
// 清空表单
document.getElementById('author-name').value = '';
document.getElementById('comment-text').value = '';
// 显示成功消息
alert('评论发表成功!');
});
}
});
// HTML转义函数,防止XSS攻击
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
EOF
CSS文件通常不会冲突(两个分支都是追加内容),Git会自动合并。
4.5 标记冲突已解决并提交
bash
# 标记冲突已解决
git add index.html js/main.js
# 查看状态
git status
# 输出:
# On branch feature/categories
# All conflicts fixed but you are still merging.
# (use "git commit" to conclude merge)
#
# Changes to be committed:
# modified: css/style.css
# modified: index.html
# modified: js/main.js
# 完成合并提交
git commit -m "Merge origin/main into feature/categories
Resolved conflicts by combining dark mode and categories features."
# 推送到GitHub
git push
4.6 完成PR合并
现在在GitHub上,feature/categories
的PR显示没有冲突了,可以合并。
- 在PR页面,点击"Merge pull request"
- 确认合并
- 删除远程分支
同步到你的本地仓库:
bash
# 回到你的工作目录
cd ../my-blog
# 拉取最新代码
git pull
# 查看提交历史
git log --oneline --graph
# 输出会显示合并的历史
第五章:团队协作工作流
5.1 Git工作流模式
1. 集中式工作流
最简单的工作流,类似SVN,所有人都在main分支上工作。
css
main: C1 ← C2 ← C3 ← C4 ← C5
适用场景:小团队、简单项目
2. 功能分支工作流
我们刚才使用的就是这种工作流。每个功能在独立分支上开发,通过PR合并到main。
makefile
main: C1 ← C2 ←─────── C5 ←────── C8
↓ ↑ ↑
feature-a: C3 ← C4 ───┘ │
│
feature-b: C6 ← C7 ──────────────┘
适用场景:大多数团队和项目
3. Gitflow工作流
严格的分支模型,适合有明确发布周期的项目。
makefile
main: v1.0 ←──────────── v1.1
↑ ↑
develop: C1 ← C2 ← C3 ← C4 ← C5
↓ ↓
feature-a: F1 ← F2─┘
feature-b: F3 ← F4──┘
分支类型:
main
:生产环境代码develop
:开发分支feature/*
:功能分支release/*
:发布分支hotfix/*
:紧急修复分支
4. Forking工作流
用于开源项目,每个开发者有自己的服务器端仓库。
scss
官方仓库 (upstream)
↓ fork
你的仓库 (origin)
↓ clone
本地仓库 (local)
5.2 实战:使用标签管理版本
现在我们的博客已经有了完整的功能,让我们发布第一个版本。
bash
# 确保在main分支且代码最新
git switch main
git pull
# 创建标签
git tag -a v1.0.0 -m "Release version 1.0.0
Features:
- Blog post display
- Comment system
- Dark mode toggle
- Article categories and filtering"
# 查看标签
git tag
# 输出:
# v1.0.0
# 查看标签详情
git show v1.0.0
# 推送标签到GitHub
git push origin v1.0.0
# 或推送所有标签
git push --tags
在GitHub上创建Release:
- 在GitHub仓库页面,点击"Releases"
- 点击"Create a new release"
- 选择标签
v1.0.0
- 填写Release标题和说明
- 可以上传附件(如编译后的文件)
- 点击"Publish release"
5.3 保护分支
为了防止意外推送到main分支,可以设置分支保护规则。
在GitHub上设置:
- 进入仓库Settings
- 左侧菜单选择"Branches"
- 点击"Add rule"
- 设置规则:
- Branch name pattern:
main
- ✅ Require a pull request before merging
- ✅ Require approvals (至少1个审批)
- ✅ Require status checks to pass
- ✅ Include administrators
- Branch name pattern:
- 保存规则
效果:
- 不能直接推送到main分支
- 必须通过PR合并
- PR必须经过代码审查
- 可以要求CI测试通过
5.4 最佳实践总结
提交规范:
bash
# 好的提交信息
git commit -m "Add dark mode toggle feature"
git commit -m "Fix comment form validation bug"
git commit -m "Update README with installation instructions"
# 不好的提交信息
git commit -m "update"
git commit -m "fix bug"
git commit -m "changes"
提交信息格式(约定式提交):
xml
<type>(<scope>): <subject>
<body>
<footer>
类型(type):
feat
: 新功能fix
: 修复bugdocs
: 文档更新style
: 代码格式(不影响功能)refactor
: 重构test
: 测试chore
: 构建过程或辅助工具的变动
示例:
bash
git commit -m "feat(comments): add comment submission functionality
- Add comment form HTML structure
- Implement comment submission logic
- Add XSS protection with HTML escaping
Closes #123"
分支命名规范:
bash
feature/功能名称 # 新功能
bugfix/问题描述 # bug修复
hotfix/紧急修复 # 紧急修复
release/版本号 # 发布分支
PR最佳实践:
- 保持PR小而专注:一个PR只做一件事
- 写清晰的描述:说明做了什么、为什么、如何测试
- 自我审查:提交前先自己review一遍
- 及时响应评论:快速回应审查意见
- 保持更新:及时合并main分支的更新
本篇小结
恭喜你掌握了Git团队协作的核心技能!让我们回顾本篇的重点内容:
核心概念:
-
分支的本质:
- 分支只是指向提交的可变指针
- HEAD指针指向当前分支
- 创建分支成本极低,速度极快
-
合并策略:
- 快进合并:简单移动指针
- 三路合并:创建合并提交
- 冲突解决:手动编辑冲突文件
-
Pull Request工作流:
- 创建功能分支
- 推送到远程仓库
- 创建PR进行代码审查
- 合并到主分支
-
团队协作:
- 功能分支工作流
- 代码审查最佳实践
- 分支保护规则
- 版本标签管理
实战成果:
✅ 开发了深色模式和分类功能
✅ 掌握了分支创建、切换、合并
✅ 学会了解决合并冲突
✅ 完成了完整的PR流程
✅ 发布了v1.0.0版本
下一篇预告:
在《Git完全指南(下篇):Git高级技巧与问题解决》中,我们将学习:
- 历史修改:reset、revert、rebase的高级用法
- 时光机:reflog找回丢失的提交
- 高级特性:Hooks、Submodules、Worktree、LFS
- 常见问题:24个实际问题的解决方案
- 性能优化:大仓库管理、克隆加速
我们将处理更复杂的场景,成为Git专家!