From 5818e9129af3deb20e01acf8a7b4c549b5819813 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 02:02:32 +0000 Subject: [PATCH] Add Git bundle solution for incremental code synchronization Co-authored-by: ramwin --- README_bundle_solution.md | 188 +++++++++++++++++++++++++++ apply_incremental_bundle.sh | 238 +++++++++++++++++++++++++++++++++++ bundle_solution.md | 111 ++++++++++++++++ create_incremental_bundle.sh | 190 ++++++++++++++++++++++++++++ 4 files changed, 727 insertions(+) create mode 100644 README_bundle_solution.md create mode 100755 apply_incremental_bundle.sh create mode 100644 bundle_solution.md create mode 100755 create_incremental_bundle.sh diff --git a/README_bundle_solution.md b/README_bundle_solution.md new file mode 100644 index 0000000..913d8a5 --- /dev/null +++ b/README_bundle_solution.md @@ -0,0 +1,188 @@ +# Git Bundle 增量同步完整解决方案 + +## 问题背景 + +你遇到的场景: +1. 分支 `inner` 基于 master 的 A0 节点,开发了 A1, A2, A3, A4 +2. 已经将 A1-A3 打包成 `bundle_A123` 给同事C +3. 同事B开发了B1并合并到master +4. 你将A4 rebase到B1后变成:A0 → B1 → A1' → A2' → A3' → A4' +5. 现在需要给同事C发送A4',但要避免重新传输A1', A2', A3' + +## 解决方案概述 + +**核心思路**:创建增量bundle,只包含A4'这个新提交,充分利用同事C已有的bundle_A123。 + +## 具体操作步骤 + +### 第一步:你的操作(创建增量bundle) + +```bash +# 1. 确保你在inner分支上 +git checkout inner + +# 2. 找到A3'的commit hash +git log --oneline -5 +# 假设A3'的hash是 abc123 + +# 3. 使用脚本创建增量bundle +./create_incremental_bundle.sh -b abc123 -t inner -c + +# 或者手动创建 +git bundle create bundle_A4_only.bundle abc123..inner +``` + +### 第二步:发送给同事C + +将生成的 `bundle_A4_only.bundle`(或压缩后的 `.gz` 文件)发送给同事C。 + +### 第三步:同事C的操作(应用增量bundle) + +```bash +# 方法1:使用提供的脚本(推荐) +./apply_incremental_bundle.sh bundle_A4_only.bundle + +# 方法2:手动操作 +# 2.1 首先确保已经应用了之前的bundle_A123 +git fetch bundle_A123.bundle refs/heads/inner:inner_base + +# 2.2 应用新的增量bundle +git fetch bundle_A4_only.bundle refs/heads/inner:inner_updated + +# 2.3 切换到更新后的分支 +git checkout inner_updated +``` + +## 提供的工具脚本 + +### 1. `create_incremental_bundle.sh` - 创建增量bundle + +**功能特点**: +- 自动验证提交点存在性 +- 显示将要打包的提交列表 +- 支持压缩输出 +- 提供详细的操作指导 + +**使用示例**: +```bash +# 基本用法 +./create_incremental_bundle.sh -b HEAD~1 + +# 指定目标分支和输出文件 +./create_incremental_bundle.sh -b abc123 -t inner -o my_bundle.bundle + +# 创建压缩的bundle +./create_incremental_bundle.sh -b abc123 -c +``` + +### 2. `apply_incremental_bundle.sh` - 应用增量bundle + +**功能特点**: +- 自动验证bundle完整性 +- 支持预览模式查看内容 +- 可创建新分支或更新现有分支 +- 自动处理压缩文件 + +**使用示例**: +```bash +# 基本用法 +./apply_incremental_bundle.sh bundle_A4_only.bundle + +# 预览bundle内容 +./apply_incremental_bundle.sh --preview bundle_A4_only.bundle + +# 创建新分支而不是更新现有分支 +./apply_incremental_bundle.sh -n bundle_A4_only.bundle +``` + +## 高级技巧 + +### 1. 使用Git标签标记关键点 + +```bash +# 在创建bundle_A123时打标签 +git tag -a sync_point_A123 A3_commit_hash -m "Sync point for bundle_A123" + +# 基于标签创建增量bundle +./create_incremental_bundle.sh -b sync_point_A123 +``` + +### 2. 验证bundle大小对比 + +```bash +# 创建完整bundle(对比用) +git bundle create bundle_full.bundle master..inner + +# 创建增量bundle +./create_incremental_bundle.sh -b A3_hash + +# 对比大小 +ls -lh *.bundle +``` + +### 3. 处理复杂的rebase情况 + +如果rebase后的A1', A2', A3'与原来的A1, A2, A3差异很大: + +```bash +# 方案1:基于共同祖先创建bundle +COMMON_ANCESTOR=$(git merge-base A3_original_hash A3_new_hash) +git bundle create bundle_from_common.bundle $COMMON_ANCESTOR..inner + +# 方案2:使用cherry-pick方式 +# 让同事C基于bundle_A123创建分支,然后应用A4' +git bundle create bundle_A4_cherry.bundle A4_hash^..A4_hash +``` + +## 故障排除 + +### 问题1:同事C应用bundle后出现冲突 +```bash +# 检查当前状态 +git status + +# 查看冲突的文件 +git diff + +# 解决冲突后继续 +git add . +git commit -m "Resolve conflicts from incremental bundle" +``` + +### 问题2:bundle验证失败 +```bash +# 重新创建bundle +git bundle create new_bundle.bundle base_commit..target_branch + +# 验证bundle +git bundle verify new_bundle.bundle +``` + +### 问题3:找不到基础提交点 +```bash +# 查看详细的提交历史 +git log --oneline --graph -20 + +# 或者使用reflog查找 +git reflog show inner +``` + +## 最佳实践建议 + +1. **记录同步点**:每次创建bundle时记录base commit hash +2. **使用描述性文件名**:包含日期、分支名和版本信息 +3. **验证操作**:在本地模拟同事的操作流程 +4. **备份重要分支**:在应用bundle前创建备份分支 +5. **压缩传输**:对于大文件使用压缩选项 +6. **文档化流程**:团队内部建立标准的bundle同步流程 + +## 总结 + +这个解决方案通过创建增量bundle,有效解决了rebase后避免重复传输的问题。关键优势: + +- ✅ **大幅减少传输大小**:只传输新的提交A4' +- ✅ **保持完整历史**:同事C可以完整重建分支历史 +- ✅ **操作简单**:提供自动化脚本,减少人为错误 +- ✅ **灵活性高**:支持多种应用方式和验证选项 + +通过这种方式,你可以高效地与内网同事同步代码,避免重复传输大量已同步的内容。 \ No newline at end of file diff --git a/apply_incremental_bundle.sh b/apply_incremental_bundle.sh new file mode 100755 index 0000000..b3cc83d --- /dev/null +++ b/apply_incremental_bundle.sh @@ -0,0 +1,238 @@ +#!/bin/bash + +# Git Bundle 增量应用脚本 +# 用于应用增量bundle到本地仓库 + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 打印带颜色的消息 +print_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +print_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +print_error() { + echo -e "${RED}❌ $1${NC}" +} + +# 显示帮助信息 +show_help() { + echo "Git Bundle 增量应用脚本" + echo "" + echo "用法:" + echo " $0 [选项] BUNDLE_FILE" + echo "" + echo "选项:" + echo " -b, --branch BRANCH 目标分支名 (默认: 从bundle中推断)" + echo " -n, --new-branch 创建新分支而不是更新现有分支" + echo " -p, --preview 预览模式,只显示将要应用的提交" + echo " -h, --help 显示此帮助信息" + echo "" + echo "示例:" + echo " $0 bundle_inner_20241201_143022.bundle" + echo " $0 -b inner -n bundle_A4_only.bundle" + echo " $0 --preview bundle_incremental.bundle" + echo "" + echo "注意:" + echo " 在应用bundle之前,请确保你已经应用了之前的bundle_A123" +} + +# 默认值 +BUNDLE_FILE="" +TARGET_BRANCH="" +CREATE_NEW_BRANCH=false +PREVIEW_MODE=false + +# 解析命令行参数 +while [[ $# -gt 0 ]]; do + case $1 in + -b|--branch) + TARGET_BRANCH="$2" + shift 2 + ;; + -n|--new-branch) + CREATE_NEW_BRANCH=true + shift + ;; + -p|--preview) + PREVIEW_MODE=true + shift + ;; + -h|--help) + show_help + exit 0 + ;; + -*) + print_error "未知参数: $1" + show_help + exit 1 + ;; + *) + if [[ -z "$BUNDLE_FILE" ]]; then + BUNDLE_FILE="$1" + else + print_error "只能指定一个bundle文件" + exit 1 + fi + shift + ;; + esac +done + +# 检查必需参数 +if [[ -z "$BUNDLE_FILE" ]]; then + print_error "必须指定bundle文件" + show_help + exit 1 +fi + +# 检查bundle文件是否存在 +if [[ ! -f "$BUNDLE_FILE" ]]; then + print_error "Bundle文件不存在: $BUNDLE_FILE" + exit 1 +fi + +# 解压bundle文件(如果是压缩的) +ORIGINAL_BUNDLE="$BUNDLE_FILE" +if [[ "$BUNDLE_FILE" == *.gz ]]; then + print_info "检测到压缩的bundle文件,正在解压..." + UNCOMPRESSED_FILE="${BUNDLE_FILE%.gz}" + if [[ ! -f "$UNCOMPRESSED_FILE" ]]; then + gunzip -k "$BUNDLE_FILE" + fi + BUNDLE_FILE="$UNCOMPRESSED_FILE" + print_success "解压完成: $BUNDLE_FILE" +fi + +print_info "开始处理bundle: $BUNDLE_FILE" + +# 验证bundle +print_info "正在验证bundle..." +if ! git bundle verify "$BUNDLE_FILE"; then + print_error "Bundle验证失败" + exit 1 +fi +print_success "Bundle验证通过" + +# 获取bundle中的分支信息 +print_info "Bundle中包含的分支:" +BUNDLE_HEADS=$(git bundle list-heads "$BUNDLE_FILE") +echo "$BUNDLE_HEADS" + +# 如果没有指定目标分支,尝试从bundle中推断 +if [[ -z "$TARGET_BRANCH" ]]; then + # 提取第一个分支名 + TARGET_BRANCH=$(echo "$BUNDLE_HEADS" | head -n1 | awk '{print $2}' | sed 's|refs/heads/||') + if [[ -z "$TARGET_BRANCH" ]]; then + print_error "无法从bundle中推断分支名,请使用 -b/--branch 指定" + exit 1 + fi + print_info "推断的目标分支: $TARGET_BRANCH" +fi + +# 预览模式 +if [[ "$PREVIEW_MODE" == true ]]; then + print_info "预览模式 - 显示bundle中的提交:" + + # 创建临时分支来查看提交 + TEMP_BRANCH="temp_preview_$(date +%s)" + git fetch "$BUNDLE_FILE" "refs/heads/$TARGET_BRANCH:$TEMP_BRANCH" + + echo + print_info "Bundle中的新提交:" + if git show-branch --current "$TEMP_BRANCH" 2>/dev/null | grep -q "\[$TEMP_BRANCH\]"; then + git log --oneline --graph "$TEMP_BRANCH" ^HEAD 2>/dev/null || git log --oneline "$TEMP_BRANCH" -10 + else + git log --oneline "$TEMP_BRANCH" -10 + fi + + # 清理临时分支 + git branch -D "$TEMP_BRANCH" + + echo + print_info "预览完成。要应用bundle,请重新运行脚本而不使用 --preview 选项" + exit 0 +fi + +# 检查当前状态 +if [[ -n "$(git status --porcelain)" ]]; then + print_warning "工作目录不干净,建议先提交或暂存更改" + echo "未提交的更改:" + git status --short + echo + read -p "是否继续? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_info "操作已取消" + exit 0 + fi +fi + +# 应用bundle +if [[ "$CREATE_NEW_BRANCH" == true ]]; then + # 创建新分支 + NEW_BRANCH_NAME="${TARGET_BRANCH}_$(date +%Y%m%d_%H%M%S)" + print_info "创建新分支: $NEW_BRANCH_NAME" + + git fetch "$BUNDLE_FILE" "refs/heads/$TARGET_BRANCH:$NEW_BRANCH_NAME" + git checkout "$NEW_BRANCH_NAME" + + print_success "成功创建并切换到新分支: $NEW_BRANCH_NAME" +else + # 更新现有分支或创建分支 + print_info "应用bundle到分支: $TARGET_BRANCH" + + # 检查分支是否存在 + if git show-ref --verify --quiet "refs/heads/$TARGET_BRANCH"; then + # 分支存在,更新它 + CURRENT_BRANCH=$(git branch --show-current) + + if [[ "$CURRENT_BRANCH" == "$TARGET_BRANCH" ]]; then + # 当前就在目标分支上 + git fetch "$BUNDLE_FILE" "refs/heads/$TARGET_BRANCH" + git merge FETCH_HEAD + else + # 在其他分支上,先切换 + git fetch "$BUNDLE_FILE" "refs/heads/$TARGET_BRANCH:$TARGET_BRANCH" + git checkout "$TARGET_BRANCH" + fi + else + # 分支不存在,创建它 + git fetch "$BUNDLE_FILE" "refs/heads/$TARGET_BRANCH:$TARGET_BRANCH" + git checkout "$TARGET_BRANCH" + fi + + print_success "成功应用bundle到分支: $TARGET_BRANCH" +fi + +# 显示应用后的状态 +echo +print_info "应用后的分支状态:" +git log --oneline --graph -5 + +echo +print_success "Bundle应用完成!" + +# 清理解压的文件(如果原文件是压缩的) +if [[ "$ORIGINAL_BUNDLE" != "$BUNDLE_FILE" && "$ORIGINAL_BUNDLE" == *.gz ]]; then + read -p "是否删除解压后的bundle文件 $BUNDLE_FILE? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + rm "$BUNDLE_FILE" + print_info "已删除解压后的文件: $BUNDLE_FILE" + fi +fi \ No newline at end of file diff --git a/bundle_solution.md b/bundle_solution.md new file mode 100644 index 0000000..b98aff1 --- /dev/null +++ b/bundle_solution.md @@ -0,0 +1,111 @@ +# Git Bundle 增量同步解决方案 + +## 问题描述 +- 分支inner基于master节点A0,开发了A1, A2, A3, A4 +- 已经将A1到A3打包成bundle_A123给同事C +- 同事B开发了B1并合并到master +- 将A4 rebase到B1后变成A1', A2', A3', A4' +- 需要将A4'发给同事C,但要避免重新传输A1', A2', A3' + +## 解决方案:增量Bundle策略 + +### 方案1:仅打包A4'(推荐) + +```bash +# 1. 在你的机器上,假设当前在inner分支上 +# 找到A3'的commit hash +git log --oneline -n 5 + +# 2. 只打包A4'这一个提交(假设A3'的hash是abc123) +git bundle create bundle_A4_only.bundle abc123..HEAD + +# 3. 将bundle_A4_only.bundle发送给同事C +``` + +### 同事C的操作流程: + +```bash +# 1. 首先应用之前的bundle_A123(如果还没有应用) +git fetch bundle_A123.bundle refs/heads/inner:inner_old +git checkout inner_old + +# 2. 应用新的增量bundle +git fetch bundle_A4_only.bundle refs/heads/inner:inner_new + +# 3. 检查差异并合并 +git log inner_old..inner_new --oneline +git checkout inner_new +``` + +### 方案2:基于已知点的增量bundle + +如果同事C已经有了A1, A2, A3的内容,可以: + +```bash +# 在你的机器上,创建从A3'到A4'的bundle +# 假设A3的原始hash是def456 +git bundle create bundle_A4_incremental.bundle def456..HEAD +``` + +### 方案3:使用--since参数 + +```bash +# 基于时间的增量bundle(如果知道A3的提交时间) +git bundle create bundle_recent.bundle --since="2024-01-01" HEAD +``` + +## 验证Bundle内容 + +```bash +# 查看bundle包含的内容 +git bundle list-heads bundle_A4_only.bundle +git bundle verify bundle_A4_only.bundle + +# 查看bundle的大小 +ls -lh *.bundle +``` + +## 最佳实践建议 + +1. **记录提交点**:每次创建bundle时记录base commit,方便后续增量操作 +2. **使用标签**:在关键节点打标签,便于引用 +3. **测试验证**:在本地模拟同事C的操作流程进行验证 + +## 示例脚本 + +```bash +#!/bin/bash +# create_incremental_bundle.sh + +# 参数检查 +if [ $# -ne 2 ]; then + echo "Usage: $0 " + echo "Example: $0 abc123 inner" + exit 1 +fi + +BASE_COMMIT=$1 +TARGET_BRANCH=$2 +BUNDLE_NAME="bundle_incremental_$(date +%Y%m%d_%H%M%S).bundle" + +# 创建增量bundle +git bundle create "$BUNDLE_NAME" "$BASE_COMMIT..$TARGET_BRANCH" + +# 验证bundle +if git bundle verify "$BUNDLE_NAME"; then + echo "✅ Bundle created successfully: $BUNDLE_NAME" + echo "📦 Bundle size: $(ls -lh "$BUNDLE_NAME" | awk '{print $5}')" + echo "📋 Bundle contents:" + git bundle list-heads "$BUNDLE_NAME" +else + echo "❌ Bundle verification failed" + exit 1 +fi +``` + +## 注意事项 + +1. 确保base_commit在同事C那边已经存在 +2. 如果A1', A2', A3'与原来的A1, A2, A3内容完全不同,增量bundle可能仍然很大 +3. 考虑使用Git的`--thin`选项来进一步减小bundle大小 +4. 建议在传输前压缩bundle文件 \ No newline at end of file diff --git a/create_incremental_bundle.sh b/create_incremental_bundle.sh new file mode 100755 index 0000000..21e348a --- /dev/null +++ b/create_incremental_bundle.sh @@ -0,0 +1,190 @@ +#!/bin/bash + +# Git Bundle 增量同步脚本 +# 用于解决rebase后的增量同步问题 + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 打印带颜色的消息 +print_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +print_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +print_error() { + echo -e "${RED}❌ $1${NC}" +} + +# 显示帮助信息 +show_help() { + echo "Git Bundle 增量同步脚本" + echo "" + echo "用法:" + echo " $0 [选项]" + echo "" + echo "选项:" + echo " -b, --base COMMIT 基础提交点 (必需)" + echo " -t, --target BRANCH 目标分支 (默认: 当前分支)" + echo " -o, --output FILE 输出文件名 (默认: 自动生成)" + echo " -c, --compress 压缩bundle文件" + echo " -h, --help 显示此帮助信息" + echo "" + echo "示例:" + echo " $0 -b abc123 -t inner" + echo " $0 --base HEAD~1 --output my_bundle.bundle --compress" + echo "" + echo "场景说明:" + echo " 当你rebase了分支后,想要只发送新的提交给同事," + echo " 而不是重新发送整个分支的内容。" +} + +# 默认值 +BASE_COMMIT="" +TARGET_BRANCH="" +OUTPUT_FILE="" +COMPRESS=false + +# 解析命令行参数 +while [[ $# -gt 0 ]]; do + case $1 in + -b|--base) + BASE_COMMIT="$2" + shift 2 + ;; + -t|--target) + TARGET_BRANCH="$2" + shift 2 + ;; + -o|--output) + OUTPUT_FILE="$2" + shift 2 + ;; + -c|--compress) + COMPRESS=true + shift + ;; + -h|--help) + show_help + exit 0 + ;; + *) + print_error "未知参数: $1" + show_help + exit 1 + ;; + esac +done + +# 检查必需参数 +if [[ -z "$BASE_COMMIT" ]]; then + print_error "必须指定基础提交点 (-b/--base)" + show_help + exit 1 +fi + +# 获取当前分支作为默认目标分支 +if [[ -z "$TARGET_BRANCH" ]]; then + TARGET_BRANCH=$(git branch --show-current) + if [[ -z "$TARGET_BRANCH" ]]; then + print_error "无法确定当前分支,请使用 -t/--target 指定目标分支" + exit 1 + fi +fi + +# 生成默认输出文件名 +if [[ -z "$OUTPUT_FILE" ]]; then + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + OUTPUT_FILE="bundle_${TARGET_BRANCH}_${TIMESTAMP}.bundle" +fi + +print_info "开始创建增量bundle..." +print_info "基础提交: $BASE_COMMIT" +print_info "目标分支: $TARGET_BRANCH" +print_info "输出文件: $OUTPUT_FILE" + +# 验证基础提交是否存在 +if ! git rev-parse --verify "$BASE_COMMIT" >/dev/null 2>&1; then + print_error "基础提交 '$BASE_COMMIT' 不存在" + exit 1 +fi + +# 验证目标分支是否存在 +if ! git rev-parse --verify "$TARGET_BRANCH" >/dev/null 2>&1; then + print_error "目标分支 '$TARGET_BRANCH' 不存在" + exit 1 +fi + +# 检查是否有新的提交 +COMMIT_COUNT=$(git rev-list --count "$BASE_COMMIT..$TARGET_BRANCH") +if [[ "$COMMIT_COUNT" -eq 0 ]]; then + print_warning "从 '$BASE_COMMIT' 到 '$TARGET_BRANCH' 没有新的提交" + exit 0 +fi + +print_info "发现 $COMMIT_COUNT 个新提交:" +git log --oneline "$BASE_COMMIT..$TARGET_BRANCH" +echo + +# 创建bundle +print_info "正在创建bundle..." +if git bundle create "$OUTPUT_FILE" "$BASE_COMMIT..$TARGET_BRANCH"; then + print_success "Bundle创建成功: $OUTPUT_FILE" +else + print_error "Bundle创建失败" + exit 1 +fi + +# 验证bundle +print_info "正在验证bundle..." +if git bundle verify "$OUTPUT_FILE"; then + print_success "Bundle验证通过" +else + print_error "Bundle验证失败" + exit 1 +fi + +# 显示bundle信息 +BUNDLE_SIZE=$(ls -lh "$OUTPUT_FILE" | awk '{print $5}') +print_info "Bundle大小: $BUNDLE_SIZE" + +print_info "Bundle包含的分支:" +git bundle list-heads "$OUTPUT_FILE" + +# 压缩bundle (如果需要) +if [[ "$COMPRESS" == true ]]; then + print_info "正在压缩bundle..." + if command -v gzip >/dev/null 2>&1; then + gzip -k "$OUTPUT_FILE" + COMPRESSED_FILE="${OUTPUT_FILE}.gz" + COMPRESSED_SIZE=$(ls -lh "$COMPRESSED_FILE" | awk '{print $5}') + print_success "压缩完成: $COMPRESSED_FILE (大小: $COMPRESSED_SIZE)" + else + print_warning "gzip命令不可用,跳过压缩" + fi +fi + +echo +print_success "增量bundle创建完成!" +echo +print_info "接下来的步骤:" +echo "1. 将 $OUTPUT_FILE 发送给同事C" +echo "2. 同事C执行以下命令应用bundle:" +echo " git fetch $OUTPUT_FILE refs/heads/$TARGET_BRANCH:${TARGET_BRANCH}_new" +echo " git checkout ${TARGET_BRANCH}_new" +echo +print_info "或者同事C可以使用以下命令直接更新现有分支:" +echo " git fetch $OUTPUT_FILE refs/heads/$TARGET_BRANCH:$TARGET_BRANCH" \ No newline at end of file