保护你的 GitHub 隐私 - 如何批量修改历史提交者信息(邮箱 / 名字)

教程:保护你的 GitHub 隐私 - 如何批量修改历史提交者信息(邮箱 / 名字)

*本文存在 AI 生成内容,但内容已经过验证

目录

一、为何要关注 GitHub 提交历史的隐私?

GitHub 是一个代码托管和协作的公开平台。当你进行 git commit 时,你的 Git 配置中的用户名和邮箱地址会默认嵌入到这次提交的元数据中。

有心之人可以用一些方法还原出元数据,从而得到你的邮箱,如果你的邮箱是 QQ 邮箱,则后果更加严重。

你可能在某个时间点后设置了隐私邮箱,但是在那之前可能没有注意。这并不是不可挽回的,本文介绍的方法和脚本将帮助你快速替换先前所有提交的邮箱和名字。

二、重要警告 (操作前必读!)

修改 Git 历史记录是一项极其危险的操作,请务必理解以下风险:

  • 这会彻底重写你仓库的提交历史。所有受影响提交的唯一 ID (SHA-1 哈希值) 都会改变。
  • 如果你的仓库有其他协作者,他们本地的仓库副本将与你修改后的远程仓库历史不再兼容。他们需要废弃本地旧副本并重新克隆,或者进行复杂的 rebase 操作。在执行前务必通知并协调所有协作者!
  • 一旦强制推送(git push --force),远程仓库的历史将被覆盖。如果没有备份,将无法恢复到修改前的状态。备份是必须的。

三、准备工作

  1. 安装 Git Bash: 在 Windows 上,推荐使用 Git Bash 作为运行脚本和 Git 命令的环境。请从 Git 官网 下载并安装。
  2. 安装 Python 和 pip: git filter-repo 工具需要 Python 环境。请从 Python 官网 下载并安装。安装时确保勾选 “Add Python to PATH”。
  3. 安装 git filter-repo: 打开 Git Bash,运行 pip install git-filter-repo
  4. 准备仓库 URL 列表: 创建一个名为 repo_urls.txt 的文本文件,将你想要处理的所有 GitHub 仓库的 URL(HTTPS 或 SSH 格式皆可)每行一个地粘贴进去。将这个文件与后面要创建的 .sh 脚本放在同一个目录下。
    1
    2
    3
    4
    # repo_urls.txt 示例:
    https://github.com/your_username/repo1.git
    https://github.com/your_username/repo2.git
    git@github.com:your_username/repo3.git
  5. 配置 GitHub 认证: 确保你的 Git Bash 环境能够免密访问 GitHub(例如,通过配置好的 SSH 密钥或 Git Credential Manager for Windows)。脚本中的 git clonegit push 需要认证。

四、步骤一:备份你的仓库

在进行任何破坏性操作之前,必须备份你的仓库。我们将使用 git clone --mirror 创建每个仓库的完整镜像备份(裸仓库)。

  1. 创建备份脚本 (backup_repos.sh):
    在与 repo_urls.txt 同一个目录下,创建名为 backup_repos.sh 的文件,并将以下内容粘贴进去:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    #!/bin/bash

    # --- 配置 ---
    # 包含仓库 URL 的文件路径 (相对于脚本)
    URL_FILENAME="repo_urls.txt"
    # 存储备份的目录名
    BACKUP_DIR="github_backups"

    # --- 脚本主体 ---
    echo "开始备份仓库..."

    # 获取脚本所在目录及 URL 文件路径
    SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
    URL_FILE_PATH="$SCRIPT_DIR/$URL_FILENAME"
    BACKUP_FULL_PATH="$SCRIPT_DIR/$BACKUP_DIR"

    echo "URL 文件: $URL_FILE_PATH"
    echo "备份目录: $BACKUP_FULL_PATH"
    echo "-----------------------------------------"

    # 检查 URL 文件是否存在
    if [ ! -f "$URL_FILE_PATH" ]; then
    echo "错误: 仓库 URL 文件 '$URL_FILE_PATH' 未找到!"
    exit 1
    fi

    # 创建备份目录(如果不存在)
    mkdir -p "$BACKUP_FULL_PATH"
    # 记录当前目录,方便返回
    original_dir=$(pwd)
    cd "$SCRIPT_DIR" # 确保我们在脚本目录下操作

    # 读取文件中的每个 URL
    while IFS= read -r repo_url || [[ -n "$repo_url" ]]; do
    if [ -z "$repo_url" ]; then
    continue # 跳过空行
    fi

    echo "处理仓库: $repo_url"

    # 从 URL 获取仓库名 (移除 .git 后缀)
    repo_name=$(basename "$repo_url" .git)
    backup_path="$BACKUP_DIR/${repo_name}.git" # 相对路径

    # 检查备份是否已存在
    if [ -d "$backup_path" ]; then
    # 备份已存在,尝试更新
    echo " 备份已存在于 '$backup_path',尝试更新..."
    cd "$backup_path" || { echo "错误: 无法进入目录 '$backup_path'"; cd "$SCRIPT_DIR"; continue; }

    # 确保 origin 存在并指向正确的 URL (对于旧备份可能需要)
    existing_origin_url=$(git remote get-url origin 2>/dev/null)
    if [ $? -ne 0 ] || [ "$existing_origin_url" != "$repo_url" ]; then
    echo " 设置/更新 remote 'origin' 指向 $repo_url"
    git remote remove origin 2>/dev/null # 移除旧的(如果存在)
    git remote add origin "$repo_url"
    fi

    # 从远程获取最新数据 (包括所有分支和标签), --prune 删除远程已不存在的引用
    echo " 正在执行 git fetch --prune..."
    git fetch --prune origin '+refs/*:refs/*' # 更健壮地同步所有引用
    if [ $? -ne 0 ]; then
    echo " 警告: 更新备份失败 (git fetch)!"
    else
    echo " 备份更新成功。"
    fi
    cd "$SCRIPT_DIR" # 返回脚本目录
    else
    # 备份不存在,执行镜像克隆
    echo " 创建新的镜像备份到 '$backup_path'..."
    # 使用相对路径进行克隆
    git clone --mirror "$repo_url" "$backup_path"
    if [ $? -ne 0 ]; then
    echo " 错误: 克隆 '$repo_url' 失败!"
    rm -rf "$backup_path" # 清理失败的克隆尝试
    else
    echo " 新的镜像备份创建成功。"
    # (可选) 进入验证并添加 origin, 虽然 mirror 会加,但以防万一
    cd "$backup_path" || { echo "错误: 无法进入目录 '$backup_path'"; cd "$SCRIPT_DIR"; continue; }
    if ! git remote get-url origin &>/dev/null; then
    git remote add origin "$repo_url" &>/dev/null
    fi
    cd "$SCRIPT_DIR" # 返回脚本目录
    fi
    fi
    echo "-----------------------------------------"

    done < "$URL_FILE_PATH"

    # 返回到运行脚本时的原始目录
    cd "$original_dir"

    echo "所有仓库备份处理完毕。"
    echo "备份文件位于: $BACKUP_FULL_PATH"

    exit 0
  2. 运行备份脚本:

    • 在当前文件夹下打开 Git Bash(右键菜单可以快速打开,不需要 cd)。
    • cd 到包含 repo_urls.txtbackup_repos.sh 的目录。
    • 赋予脚本执行权限: chmod +x backup_repos.sh(此步骤在 Windows 上并不是必须的)
    • 运行脚本: ./backup_repos.sh
    • 脚本将在当前目录下创建一个 github_backups 子目录,并将每个仓库的镜像克隆保存在其中。如果再次运行,它会尝试更新已有的备份。
    • 如何还原备份:使用 git push -f 强制推送。

五、步骤二:处理仓库 - 修改历史并推送

这个脚本将自动化以下过程:为列表中的每个仓库克隆镜像、使用 git filter-repo 修改提交者信息、重新添加 origin 远程、最后强制推送到 GitHub。

  1. 创建处理脚本 (process_repos.sh):
    在与 repo_urls.txtbackup_repos.sh 同一个目录下,创建名为 process_repos.sh 的文件,并将以下代码粘贴进去,请不要执行,而是继续参考以下步骤修改脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    #!/bin/bash

    # --- 配置 ---
    # !!! 修改为你想要统一设置的新的用户名和邮箱 !!!
    NEW_NAME="Your New Anonymous Name" # 例如:设置为你的 GitHub 用户名
    NEW_EMAIL="your-github-id+your-username@users.noreply.github.com" # 强烈推荐使用 GitHub 提供的 no-reply 邮箱

    # 包含仓库 URL 的文件名 (应与脚本放在同一目录下)
    URL_FILENAME="repo_urls.txt"
    # 处理仓库时使用的临时基础目录
    BASE_DIR="processed_repos"

    # --- 脚本主体 ---

    # 获取脚本所在的目录的绝对路径
    SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
    # 构建 URL 文件的完整路径
    URL_FILE_PATH="$SCRIPT_DIR/$URL_FILENAME"

    # 检查 URL 文件是否存在 (使用完整路径检查)
    if [ ! -f "$URL_FILE_PATH" ]; then
    echo "错误: 仓库 URL 文件 '$URL_FILE_PATH' 未找到!"
    echo "请确保 '$URL_FILENAME' 文件与脚本在同一个目录下。"
    exit 1
    fi

    echo "开始处理仓库..."
    echo "URL 文件: $URL_FILE_PATH"
    echo "将在子目录 '$BASE_DIR' 中进行处理和创建裸仓库"
    echo "设置的新用户名: $NEW_NAME"
    echo "设置的新邮箱: $NEW_EMAIL"
    echo "-----------------------------------------"

    # 创建基础目录(如果不存在),并记录当前目录以便返回
    original_dir=$(pwd)
    # 在脚本所在目录下创建处理目录
    PROCESS_FULL_PATH="$SCRIPT_DIR/$BASE_DIR"
    mkdir -p "$PROCESS_FULL_PATH"
    # 切换到处理目录执行操作
    cd "$PROCESS_FULL_PATH" || { echo "错误: 无法进入处理目录 '$PROCESS_FULL_PATH'"; exit 1; }


    # 读取文件中的每个 URL (使用完整路径读取)
    while IFS= read -r repo_url || [[ -n "$repo_url" ]]; do
    if [ -z "$repo_url" ]; then
    continue # 跳过空行
    fi

    echo "-----------------------------------------"
    echo "处理仓库: $repo_url"

    # 从 URL 获取仓库名 (移除 .git 后缀)
    repo_name=$(basename "$repo_url" .git)
    # 裸仓库目录名将在当前目录 (PROCESS_FULL_PATH) 下创建
    repo_dir="${repo_name}.git"

    # 如果目录已存在,删除重建以确保干净状态 (请小心!)
    if [ -d "$repo_dir" ]; then
    echo "警告: 目录 '$repo_dir' 已存在,将删除并重新克隆。"
    rm -rf "$repo_dir"
    fi

    # 1. 克隆镜像仓库到当前目录 (PROCESS_FULL_PATH)
    # git clone --mirror 会自动设置好 origin
    echo "正在克隆镜像..."
    git clone --mirror "$repo_url" "$repo_dir" # 直接指定目标目录名
    if [ $? -ne 0 ]; then
    echo "错误: 克隆 '$repo_url' 失败!跳过此仓库。"
    continue
    fi

    # 2. 进入裸仓库目录
    cd "$repo_dir" || { echo "错误: 无法进入克隆的裸仓库 '$repo_dir'"; cd ..; continue; } # 如果进入失败则返回处理目录并跳过

    # 3. 执行 git filter-repo (它会移除 origin)
    echo "正在运行 git filter-repo..."
    # 使用 callback 语法确保兼容性,并用 --force 忽略之前可能存在的 filter-repo 记录
    # 注意:字符串前的 b 表示 Python 字节串
    git filter-repo --name-callback "return b'$NEW_NAME'" --email-callback "return b'$NEW_EMAIL'" --force
    if [ $? -ne 0 ]; then
    echo "错误: git filter-repo 处理失败!"
    cd .. # 返回上一级 (PROCESS_FULL_PATH)
    continue
    fi
    echo "git filter-repo 处理完成。"

    # 4. 【关键】在 filter-repo 之后,push 之前,重新添加 origin
    echo "正在重新添加 remote 'origin'..."
    git remote add origin "$repo_url"
    if [ $? -ne 0 ]; then
    # 如果添加失败 (理论上不应发生,因为 filter-repo 刚删除了它),记录警告但尝试继续
    echo "警告: 重新添加 remote 'origin' 失败!后续推送可能失败。"
    # 可以选择在这里 exit 1 如果认为这是关键错误
    else
    echo "'origin' 已重新添加: $(git remote -v)"
    fi

    # 5. 强制推送 (警告:这将覆盖远程历史!)
    echo "正在强制推送到 $repo_url ..."
    git push --force --all origin
    git push --force --tags origin
    push_exit_code=$? # 保存推送命令的退出码

    if [ $push_exit_code -ne 0 ]; then
    echo "警告: 推送失败!(退出码: $push_exit_code) 请检查权限、网络或远程仓库状态。"
    # 不一定是致命错误,脚本继续,但需要注意
    else
    echo "强制推送完成。"
    fi

    # 6. 返回上一级目录 (PROCESS_FULL_PATH),准备处理下一个仓库
    cd ..
    echo "仓库 $repo_name 处理完毕。"

    done < "$URL_FILE_PATH" # <--- 使用完整的路径读取文件

    # 处理完毕后,返回到脚本启动时的目录
    cd "$original_dir"

    echo "-----------------------------------------"
    echo "所有仓库处理完毕。"
    echo "临时处理文件位于: $PROCESS_FULL_PATH (可根据需要删除)"

    exit 0
  2. 配置脚本:

    • 打开 process_repos.sh 文件,找到开头的配置部分。
    • NEW_NAME 的值修改为你希望在所有历史提交中显示的新用户名 (例如,你的 GitHub 用户名)。
    • NEW_EMAIL 的值修改为你希望在所有历史提交中显示的新邮箱地址。可以使用 GitHub 提供的 noreply 邮箱以保护隐私(可以在 GitHub -> Settings -> Emails 页面找到,格式通常是 数字ID+你的用户名@users.noreply.github.com),这样提交仍然能关联到你的 GitHub 账户,但不会暴露你的真实邮箱。
  3. 运行处理脚本:(极其小心,请认真检查配置)

    • 打开 Git Bash
    • cd 到包含 repo_urls.txt, backup_repos.shprocess_repos.sh 的目录。
    • 赋予脚本执行权限: chmod +x process_repos.sh
    • 运行脚本: ./process_repos.sh
    • 脚本会为列表中的每个仓库执行 克隆 -> 修改历史 -> 重新添加 origin -> 强制推送 的完整流程。请耐心等待其完成,并留意屏幕上的输出信息,特别是任何错误或警告。

六、验证与后续步骤

  1. 验证: 脚本运行完毕后,访问你在 repo_urls.txt 中列出的 GitHub 仓库页面。检查这些仓库的提交历史 (commits 页面),确认作者信息是否已全部更新为你配置的 NEW_NAMENEW_EMAIL
  2. 清理 (可选):
    • github_backups 目录包含了你所有仓库修改前的备份,请妥善保管,以防万一需要恢复(恢复需要手动操作,本教程未提供恢复脚本)。
    • processed_repos 目录是脚本运行时使用的临时工作目录,在确认所有仓库都已成功处理并推送到 GitHub 后,可以安全删除
  3. 配置未来提交: 为了确保未来的提交也使用匿名信息,请在你的 Git Bash 中运行以下命令设置全局配置:
    1
    2
    git config --global user.name "Your New Anonymous Name" # 使用你脚本中设置的 NEW_NAME
    git config --global user.email "your-github-id+your-username@users.noreply.github.com" # 使用你脚本中设置的 NEW_EMAIL
  4. 配置 GitHub 设置: 访问 GitHub -> Settings -> Emails 页面:
    • 确保 “Keep my email addresses private” 选项是勾选状态。
    • 考虑勾选 “Block command line pushes that expose my personal email”,这可以增加一层保护,防止你不小心推送包含私人邮箱的提交。

七、结论

通过使用 git filter-repo 工具和自动化脚本,你可以有效地清理 GitHub 仓库的历史提交记录,将过去的用户名和邮箱替换为更安全的匿名信息,从而增强个人隐私保护。修改历史是高风险操作,务必做好备份并谨慎行事。同时,配置好未来的 Git 提交设置,确保持续保护你的隐私。


保护你的 GitHub 隐私 - 如何批量修改历史提交者信息(邮箱 / 名字)
https://idontwannago.cn/posts/29213/
作者
idontwannagoo
发布于
2025年5月7日
许可协议