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专家!

相关推荐
合作小小程序员小小店10 分钟前
web网页开发,在线考勤管理系统,基于Idea,html,css,vue,java,springboot,mysql
java·前端·vue.js·后端·intellij-idea·springboot
防火墙在线17 分钟前
前后端通信加解密(Web Crypto API )
前端·vue.js·网络协议·node.js·express
Jacky-00820 分钟前
Node + vite + React 创建项目
前端·react.js·前端框架
CoderYanger1 小时前
前端基础——CSS练习项目:百度热榜实现
开发语言·前端·css·百度·html·1024程序员节
i_am_a_div_日积月累_1 小时前
10个css更新
前端·css
倚栏听风雨2 小时前
npm命令详解
前端
用户47949283569152 小时前
为什么我的react项目启动后,dom上的类名里没有代码位置信息
前端·react.js
键盘飞行员2 小时前
Vue3+TypeScript项目中配置自动导入功能,遇到了问题需要详细的配置教程!
前端·typescript·vue
han_2 小时前
前端高频面试题之Vue(初、中级篇)
前端·vue.js·面试
一枚前端小能手2 小时前
📜 `<script>`脚本元素 - 从加载策略到安全性与性能的完整指南
前端·javascript