从当前 HEAD 创建一个新的 Git 存储库,将其设置为原始存储库的远程,并将将来的更改镜像到两个存储库?

Create a new Git repository from current HEAD, set it as a remote of the original, and mirror future changes to both repositories?

当前存储库有提交

  A -> B -> C
            ^
            |
         HEAD

我想创建一个新的存储库,其主分支从当前存储库的提交 C (HEAD) 开始。

  C
  ^
  |
HEAD

此外,如果新提交 D 添加到当前存储库:

  A -> B -> C -> D
                 ^
                 |
                HEAD

新存储库将变为:

  C -> D
       ^
       |
      HEAD

在下push/mirror。

由于在我对项目提交更改的同时学习开发软件,由于在其长期历史(500 次提交)中添加和删除了大文件,存储库的大小已经增加。

这个工作流程可以在 git 中轻松实现吗? (同时使用 GitHub 和 GitLab)

您可以轻松地从现有的存储库创建一个新的存储库,至少在本地:只需 git clone <src repo> [dest dir](可能使用 --depth 或类似的方法来节省大小,尽管有一些警告,请参阅 the manual 了解详情)。使新存储库自动 遵循原始存储库的历史并不容易。新的回购将有其 origin 设置指向原始的,但更新将像往常一样需要 pull/fetch+merge/whatever。您也许可以在旧仓库中设置一些 post-commit 钩子来自动执行 cd <new repo> ; git pull ; cd $OLDPWD 位,我不太了解 git 的钩子是如何工作的。或者,您可以将新回购设置为旧回购中的远程并推送到它,但我不确定这会如何影响新回购的工作树(即签出的内容)。并且使任何这些与像 GitHub 这样的远程提供商一起工作将是一个完全不同的蠕虫病毒。

如果您想尝试清理您的历史记录,您可能需要查看 rebase 并可能 cherry-pick

你想做的几乎是不可能的。在 git 中,导致给定提交的历史记录是该提交不可分割的一部分。因此,在以下两个历史

中用C表示的提交
  A -> B -> C
            ^
            |
         HEAD

  C
  ^
  |
HEAD

实际上是两个不同的提交对象,很可能有两个单独的哈希值。实现所需设置的唯一方法是将这两个不同的提交对象调整为具有相同的哈希值,在这种情况下,您可以欺骗 git 将基于 C 的新提交推送到不同的存储库中不同的史前史。这在理论上可以实现,但在实践中很难实现(如果你设法做到这一点,那么你也将能够破解双git盟友签署的文件或改变比特币区块链)。

所需流程的近似值是在您的本地存储库中维护与两个远程对应的两个分支。您将在其中一个分支上工作,并定期将其合并到另一个分支中:

old_repo_branch:      A -> B -> C ---->  D' -> E'
                                         ^     ^
                                        /     /
                                       /     /
new_repo_branch:                C' -> D --> E

您必须将 new_repo_branch 推送到新存储库,并将 old_repo_branch 推送到旧存储库。但是,如果您需要对开发进行分支,这样的流程将变得难以管理(因为每个并行开发流都需要分支,并且每对各自的分支同样需要保持同步)。

前言

你真的应该考虑你的工作流程。很可能您正在尝试实现从某些古老的 VCS 复制的奇怪工作流程。 Git用于追踪历史,让你重写。但是您需要决定您想要的历史记录。对历史进行变体管理可能不是一个好主意。

500 次提交对于 Git 来说不是一个大数字,Linux 内核 得到了大约 63.000 (!) commits just in 2018 ;)

解决方案

尽管如此,这里有一个 hacky 概念证明可以满足您的需求。不需要专门的存储库,重写的历史只是存储在一些专门的分支中。第一个 运行 将创建该孤立分支,随后的 运行 将使用最新的提交更新它。两个调用看起来一样:

$ path/to/crazy-rebase <rewritten-branch> <last-commit-to-transfer>

例如:

$ ./crazy-rebase cutoff master

工作原理

在第一个 运行 期间,脚本从给定的修订版(例如 master)创建一个孤立分支(例如 cutoff),没有任何先前的历史记录。 所有进一步的 运行s 将挑选每个提交(尚未存在)到这个孤立的分支(使用变基)。所需的提交是从最后一次成功完成推导出来的(实际上这存储在特殊参考 CUTOFF_BASE)中。

脚本 crazy-rebase:

#!/usr/bin/env bash

CUTOFF=""
CURRENT=""

LAST_BASE="CUTOFF_BASE"


error() {
    local errcode=$?
    echo "ERR: $*" >&2
    exit $errcode
}

log() {
    echo "LOG: $*" >&2
}

ret() {
    return ""
}


prepare() {
    local cutoff=""
    local current=""
    local base_hash

    git show-ref --quiet "$cutoff" &&
    return 0

    log "Preparing cut-off branch '$cutoff' ..." &&
    base_hash="`git show -s --pretty=%H "$current"`" &&
    git checkout --quiet --orphan="$cutoff" "$current" &&
    git commit -m "Cutoff branch, based on '$base_hash'" &&
    git checkout --quiet "$current" &&
    git update-ref "$LAST_BASE" "$base_hash" &&
    log "Cut off branch '$cutoff' created." &&
    exit 0 ||
    error "Failed to init cut-off branch '$cutoff'."
}

rebase() {
    local cutoff=""
    local current=""
    local current_hash
    local errcode

    log "Rebasing commits '$LAST_BASE..$current' onto cut-off branch '$cutoff' ..."
    current_hash="`git show -s --pretty=%H "$current"`" &&
    git rebase --rebase-merges --onto "$cutoff" "$LAST_BASE" "$current_hash" || {
        errcode=$?
        log "STARTING INTERACTIVE SHELL TO RESOLVE REBASE."
        log "Use 'git rebase --continue' after resolving the issue e.g. with 'git mergetool'."
        log "Do not forget to exit this shell to continue the script."
        $SHELL
        if test -e "`git rev-parse --git-dir`/rebase-merge"; then
            git rebase --abort 2>/dev/null
            git checkout --quiet "$current"
            ret $errcode
            error "Failed to transfer commits '$LAST_BASE..$current' to '$cutoff'."
        fi
    } &&
    git rebase --rebase-merges HEAD "$cutoff" &&
    git checkout --quiet "$current" &&
    git update-ref "$LAST_BASE" "$current" &&
    log "Cut-off branch '$cutoff' updated." &&
    true
}


prepare "$CUTOFF" "$CURRENT" &&
rebase "$CUTOFF" "$CURRENT" &&
true

如果要将结果推送到远程存储库,请使用此选项:

$ git push <remote> cutoff:<name-of-cutoff-on-remote>