Git完全指南(中篇):GitHub团队协作实战


目录

  1. 分支管理的艺术
  2. 实战:开发评论系统功能
  3. [Pull Request工作流](#Pull Request工作流 "#%E7%AC%AC%E4%B8%89%E7%AB%A0pull-request%E5%B7%A5%E4%BD%9C%E6%B5%81")
  4. 冲突解决实战
  5. 团队协作工作流

第一章:分支管理的艺术

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>&copy; 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做了三件事:

  1. 移动HEAD指针到目标分支
  2. 将暂存区恢复为该分支指向的提交
  3. 将工作目录的文件恢复为该分支的快照

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>&copy; 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>&copy; 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:

  1. 在GitHub仓库页面,点击"Pull requests"标签

  2. 点击"New pull request"

  3. 选择base分支为main,compare分支为feature/dark-mode

  4. 填写PR信息:

    • Title: "Add dark mode toggle feature"

    • Description :

      markdown 复制代码
      ## 功能说明
      添加了深色模式切换功能,用户可以通过右上角的按钮切换主题。
      
      ## 主要变更
      - 添加深色模式CSS样式
      - 添加主题切换按钮
      - 使用localStorage保存用户偏好
      
      ## 测试
      - [x] 深色模式样式正常
      - [x] 切换功能正常
      - [x] 主题偏好保存正常
      
      ## 截图
      (这里可以添加截图)
  5. 点击"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>&copy; 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显示没有冲突了,可以合并。

  1. 在PR页面,点击"Merge pull request"
  2. 确认合并
  3. 删除远程分支

同步到你的本地仓库:

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:

  1. 在GitHub仓库页面,点击"Releases"
  2. 点击"Create a new release"
  3. 选择标签v1.0.0
  4. 填写Release标题和说明
  5. 可以上传附件(如编译后的文件)
  6. 点击"Publish release"

5.3 保护分支

为了防止意外推送到main分支,可以设置分支保护规则。

在GitHub上设置:

  1. 进入仓库Settings
  2. 左侧菜单选择"Branches"
  3. 点击"Add rule"
  4. 设置规则:
    • Branch name pattern: main
    • ✅ Require a pull request before merging
    • ✅ Require approvals (至少1个审批)
    • ✅ Require status checks to pass
    • ✅ Include administrators
  5. 保存规则

效果

  • 不能直接推送到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: 修复bug
  • docs: 文档更新
  • 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最佳实践:

  1. 保持PR小而专注:一个PR只做一件事
  2. 写清晰的描述:说明做了什么、为什么、如何测试
  3. 自我审查:提交前先自己review一遍
  4. 及时响应评论:快速回应审查意见
  5. 保持更新:及时合并main分支的更新

本篇小结

恭喜你掌握了Git团队协作的核心技能!让我们回顾本篇的重点内容:

核心概念:

  1. 分支的本质

    • 分支只是指向提交的可变指针
    • HEAD指针指向当前分支
    • 创建分支成本极低,速度极快
  2. 合并策略

    • 快进合并:简单移动指针
    • 三路合并:创建合并提交
    • 冲突解决:手动编辑冲突文件
  3. Pull Request工作流

    • 创建功能分支
    • 推送到远程仓库
    • 创建PR进行代码审查
    • 合并到主分支
  4. 团队协作

    • 功能分支工作流
    • 代码审查最佳实践
    • 分支保护规则
    • 版本标签管理

实战成果:

✅ 开发了深色模式和分类功能

✅ 掌握了分支创建、切换、合并

✅ 学会了解决合并冲突

✅ 完成了完整的PR流程

✅ 发布了v1.0.0版本

下一篇预告:

在《Git完全指南(下篇):Git高级技巧与问题解决》中,我们将学习:

  • 历史修改:reset、revert、rebase的高级用法
  • 时光机:reflog找回丢失的提交
  • 高级特性:Hooks、Submodules、Worktree、LFS
  • 常见问题:24个实际问题的解决方案
  • 性能优化:大仓库管理、克隆加速

我们将处理更复杂的场景,成为Git专家!

相关推荐
Tech_Lin2 小时前
前端工作实战:如何在vite中配置代理解决跨域问题
前端·后端
whysqwhw2 小时前
KuiklyUI利用Kotlin Lambda函数实现声明式UI系统的深入分析
github
XiaoSong2 小时前
React Native 主题配置终极指南,一篇文章说透
前端·react native·react.js
NicolasCage2 小时前
Eslint v9 扁平化配置学习
前端·eslint
shayudiandian2 小时前
Chrome性能优化秘籍技术
前端·chrome·性能优化
whysqwhw3 小时前
Kotlin扩展函数和带接收者的 Lambda 表达式实现DSL
github
嬉皮客3 小时前
TailwindCSS 初探
前端·css
林希_Rachel_傻希希3 小时前
Express 入门全指南:从 0 搭建你的第一个 Node Web 服务器
前端·后端·node.js