如何将文件从一个 git repo 移动到另一个(不是克隆),保留历史记录

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1365541/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 06:58:20  来源:igfitidea点击:

How to move files from one git repo to another (not a clone), preserving history

gitrepository

提问by ebneter

Our Git repositories started out as parts of a single monster SVN repository where the individual projects each had their own tree like so:

我们的 Git 存储库最初是作为单个怪物 SVN 存储库的一部分,其中每个项目都有自己的树,如下所示:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

Obviously, it was pretty easy to move files from one to another with svn mv. But in Git, each project is in its own repository, and today I was asked to move a subdirectory from project2to project1. I did something like this:

显然,使用svn mv. 然而在Git中,每一个项目都是在自己的仓库,今天有人问我一个子目录从移动project2project1。我做了这样的事情:

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

But that seems pretty convoluted. Is there a better way to do this sort of thing in general? Or have I adopted the right approach?

但这似乎很复杂。一般来说,有没有更好的方法来做这种事情?还是我采用了正确的方法?

Note that this involves merging the history into an existing repository, rather than simply creating a new standalone repository from part of another one (as in an earlier question).

请注意,这涉及将历史记录合并到现有存储库中,而不是简单地从另一个存储库的一部分创建一个新的独立存储库(如前面的问题所示)。

采纳答案by Cascabel

Yep, hitting on the --subdirectory-filterof filter-branchwas key. The fact that you used it essentially proves there's no easier way - you had no choice but to rewrite history, since you wanted to end up with only a (renamed) subset of the files, and this by definition changes the hashes. Since none of the standard commands (e.g. pull) rewrite history, there's no way you could use them to accomplish this.

是的,打的--subdirectory-filterfilter-branch是关键。您使用它的事实基本上证明没有更简单的方法 - 您别无选择,只能重写历史记录,因为您只想得到文件的(重命名)子集​​,而这根据定义会更改哈希值。由于没有任何标准命令(例如pull)重写历史记录,因此您无法使用它们来完成此操作。

You could refine the details, of course - some of your cloning and branching wasn't strictly necessary - but the overall approach is good! It's a shame it's complicated, but of course, the point of git isn't to make it easy to rewrite history.

当然,您可以改进细节 - 您的某些克隆和分支并不是绝对必要的 - 但整体方法很好!很遗憾它很复杂,但是当然,git 的重点不是让重写历史变得容易。

回答by Smar

If your history is sane, you can take the commits out as patch and apply them in the new repository:

如果您的历史记录是健全的,您可以将提交作为补丁取出并将它们应用到新存储库中:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

Or in one line

或者在一行

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

(Taken from Exherbo's docs)

(摘自Exherbo 的文档

回答by mcarans

Having tried various approaches to move a file or folder from one Git repository to another, the only one which seems to work reliably is outlined below.

在尝试了各种将文件或文件夹从一个 Git 存储库移动到另一个存储库的方法之后,下面概述了唯一一种似乎可靠工作的方法。

It involves cloning the repository you want to move the file or folder from, moving that file or folder to the root, rewriting Git history, cloning the target repository and pulling the file or folder with history directly into this target repository.

它涉及克隆要从中移动文件或文件夹的存储库,将该文件或文件夹移动到根目录,重写 Git 历史记录,克隆目标存储库并将具有历史记录的文件或文件夹直接拉入此目标存储库。

Stage One

第一阶段

  1. Make a copy of repository A as the following steps make major changes to this copy which you should not push!

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. cd into it

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Delete the link to the original repository to avoid accidentally making any remote changes (eg. by pushing)

    git remote rm origin
    
  4. Go through your history and files, removing anything that is not in directory 1. The result is the contents of directory 1 spewed out into to the base of repository A.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. For single file move only: go through what's left and remove everything except the desired file. (You may need to delete files you don't want with the same name and commit.)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    
  1. 制作存储库 A 的副本,因为以下步骤会对您不应该推送的此副本进行重大更改!

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. cd进去

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. 删除原始存储库的链接以避免意外进行任何远程更改(例如,通过推送)

    git remote rm origin
    
  4. 浏览您的历史记录和文件,删除不在目录 1 中的任何内容。 结果是目录 1 的内容喷出到存储库 A 的底部。

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. 仅用于单个文件移动:检查剩下的内容并删除除所需文件之外的所有内容。(您可能需要删除不需要的同名文件并提交。)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

Stage Two

第二阶段

  1. Cleanup step

    git reset --hard
    
  2. Cleanup step

    git gc --aggressive
    
  3. Cleanup step

    git prune
    
  1. 清理步骤

    git reset --hard
    
  2. 清理步骤

    git gc --aggressive
    
  3. 清理步骤

    git prune
    

You may want to import these files into repository B within a directory not the root:

您可能希望将这些文件导入到存储库 B 中的目录而非根目录中:

  1. Make that directory

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Move files into that directory

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Add files to that directory

    git add .
    
  4. Commit your changes and we're ready to merge these files into the new repository

    git commit
    
  1. 制作那个目录

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. 将文件移动到该目录中

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. 将文件添加到该目录

    git add .
    
  4. 提交您的更改,我们准备将这些文件合并到新的存储库中

    git commit
    

Stage Three

第三阶段

  1. Make a copy of repository B if you don't have one already

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (assuming FOLDER_TO_KEEP is the name of the new repository you are copying to)

  2. cd into it

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Create a remote connection to repository A as a branch in repository B

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. Pull from this branch (containing only the directory you want to move) into repository B.

    git pull repo-A-branch master --allow-unrelated-histories
    

    The pull copies both files and history. Note: You can use a merge instead of a pull, but pull works better.

  5. Finally, you probably want to clean up a bit by removing the remote connection to repository A

    git remote rm repo-A-branch
    
  6. Push and you're all set.

    git push
    
  1. 如果您还没有,请制作存储库 B 的副本

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (假设 FOLDER_TO_KEEP 是您要复制到的新存储库的名称)

  2. cd进去

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. 创建到存储库 A 的远程连接作为存储库 B 中的一个分支

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. 从此分支(仅包含要移动的目录)拉入存储库 B。

    git pull repo-A-branch master --allow-unrelated-histories
    

    pull 复制文件和历史记录。注意:您可以使用合并而不是拉,但拉效果更好。

  5. 最后,您可能希望通过删除到存储库 A 的远程连接来清理一下

    git remote rm repo-A-branch
    
  6. 按下即可。

    git push
    

回答by anhoppe

I found thisvery useful. It is a very simple approach where you create patches that are applied to the new repo. See the linked page for more details.

我发现非常有用。这是一种非常简单的方法,您可以在其中创建应用于新存储库的补丁。有关更多详细信息,请参阅链接页面。

It only contains three steps (copied from the blog):

它只包含三个步骤(复制自博客):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

The only issue I had was that I could not apply all patches at once using

我遇到的唯一问题是我无法使用一次应用所有补丁

git am --3way <patch-directory>/*.patch

Under Windows I got an InvalidArgument error. So I had to apply all patches one after another.

在 Windows 下,我收到了 InvalidArgument 错误。所以我不得不一个接一个地应用所有补丁。

回答by Joachim Nilsson

KEEPING THE DIRECTORY NAME

保留目录名称

The subdirectory-filter (or the shorter command git subtree) works good but did not work for me since they remove the directory name from the commit info. In my scenario I just want to merge parts of one repository into another and retain the history WITH full path name.

子目录过滤器(或较短的命令 git subtree)效果很好,但对我不起作用,因为它们从提交信息中删除了目录名称。在我的场景中,我只想将一个存储库的一部分合并到另一个存储库中,并保留带有完整路径名的历史记录。

My solution was to use the tree-filter and to simply remove the unwanted files and directories from a temporary clone of the source repository, then pull from that clone into my target repository in 5 simple steps.

我的解决方案是使用树过滤器并简单地从源存储库的临时克隆中删除不需要的文件和目录,然后通过 5 个简单的步骤从该克隆中提取到我的目标存储库中。

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

回答by Hugh Perkins

The one I always use is here http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/. Simple and fast.

我经常使用的是http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/。简单快速。

For compliance with stackoverflow standards, here is the procedure:

为了符合 stackoverflow 标准,这里是过程:

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print }')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

回答by olibre

This answer provide interesting commands based on git amand presented using examples, step by step.

这个答案提供了基于git am示例并使用示例逐步呈现的有趣命令。

Objective

客观的

  • You want to move some or all files from one repository to another.
  • You want to keep their history.
  • But you do not care about keeping tags and branches.
  • You accept limited history for renamed files (and files in renamed directories).
  • 您想将部分或全部文件从一个存储库移动到另一个存储库。
  • 你想保留他们的历史。
  • 但是你并不关心保持标签和分支。
  • 您接受重命名文件(以及重命名目录中的文件)的有限历史记录。

Procedure

程序

  1. Extract history in email format using
    git log --pretty=email -p --reverse --full-index --binary
  2. Reorganize file tree and update filename change in history [optional]
  3. Apply new history using git am
  1. 使用电子邮件格式提取历史记录
    git log --pretty=email -p --reverse --full-index --binary
  2. 重新组织文件树并更新历史记录中的文件名更改 [可选]
  3. 使用新的历史记录 git am


1. Extract history in email format

1.以电子邮件格式提取历史记录

Example: Extract history of file3, file4and file5

示例:提取历史file3file4file5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Clean the temporary directory destination

清理临时目录目标

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

Clean your the repo source

清理你的回购

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

Extract history of each file in email format

以电子邮件格式提取每个文件的历史记录

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "
/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5
" > "$historydir/
my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77
"' {} ';'

Unfortunately option --followor --find-copies-hardercannot be combined with --reverse. This is why history is cut when file is renamed (or when a parent directory is renamed).

不幸的是选项--follow--find-copies-harder不能与--reverse. 这就是为什么在重命名文件时(或重命名父目录时)历史会被删除的原因。

After: Temporary history in email format

之后:电子邮件格式的临时历史记录

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2


2. Reorganize file tree and update filename change in history [optional]

2.重新组织文件树并更新历史记录中的文件名更改[可选]

Suppose you want to move these three files in this other repo (can be the same repo).

假设您想在另一个仓库中移动这三个文件(可以是同一个仓库)。

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Therefore reorganize your files:

因此重新组织您的文件:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:/
my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77
:g" -i "
cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 
"' {} ';'

Your temporary history is now:

您的临时历史记录现在是:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

Change also filenames within the history:

更改历史记录中的文件名:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Note:This rewrites the history to reflect the change of path and filename.
      (i.e. the change of the new location/name within the new repo)

注意:这会重写历史记录以反映路径和文件名的更改。
      (即新仓库中新位置/名称的更改)



3. Apply new history

3. 应用新历史

Your other repo is:

你的另一个回购是:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

Apply commits from temporary history files:

从临时历史文件中应用提交:

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

Your other repo is now:

你的另一个回购现在是:

cd new_repo
git am "$temp"/*.patch 

Use git statusto see amount of commits ready to be pushed :-)

git status看被推提交准备的金额:-)

Note:As the history has been rewritten to reflect the path and filename change:
      (i.e. compared to the location/name within the previous repo)

注意:由于历史已被重写以反映路径和文件名的变化:(
      即与上一个 repo 中的位置/名称相比)

  • No need to git mvto change the location/filename.
  • No need to git log --followto access full history.
  • 无需git mv更改位置/文件名。
  • 无需git log --follow访问完整历史记录。


Extra trick: Detect renamed/moved files within your repo

额外技巧:在你的仓库中检测重命名/移动的文件

To list the files having been renamed:

要列出已重命名的文件:

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

More customizations: You can complete the command git logusing options --find-copies-harderor --reverse. You can also remove the first two columns using cut -f3-and grepping complete pattern '{.* => .*}'.

更多自定义:您可以git log使用选项--find-copies-harder--reverse. 您还可以使用cut -f3-和 grepping 完整模式 '{.* => .*}'删除前两列。

git remote remove origin <old-repo>

回答by ViToni

Having had a similar itch to scratch (altough only for some files of a given repository) this script proved to be really helpful: git-import

有一个类似的痒痒(虽然只适用于给定存储库的某些文件)这个脚本被证明是非常有帮助的:git-import

The short version is that it creates patch files of the given file or directory ($object) from the existing repository:

简短版本是它$object从现有存储库创建给定文件或目录 ( ) 的补丁文件:

git remote add origin <new-repo>

which then get applied to a new repository:

然后将其应用于新的存储库:

git push origin <current-branch>

For details please look up:

详情请看:

回答by Chetan Basutkar

Try this

尝试这个

cd repo1

cd repo1

This will remove all the directories except the ones mentioned, preserving history only for these directories

这将删除除了提到的目录之外的所有目录,仅保留这些目录的历史记录

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
        mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

Now you can add your new repo in your git remote and push it to that

现在你可以在你的 git remote 中添加你的新 repo 并将它推送到那个

##代码## ##代码## ##代码##

add -fto overwrite

添加-f覆盖

回答by jakub.g

I wanted something robust and reusable (one-command-and-go + undo function) so I wrote the following bash script. Worked for me on several occasions, so I thought I'd share it here.

我想要一些健壮且可重用的东西(一键执行 + 撤消功能),所以我编写了以下 bash 脚本。曾多次对我来说有效,所以我想我会在这里分享。

It is able to move an arbitrary folder /path/to/foofrom repo1into /some/other/folder/barto repo2(folder paths can be the same or different, distance from root folder may be different).

它能够将任意文件夹/path/to/foorepo1/some/other/folder/barrepo2(文件夹路径可以相同或不同,与根文件夹的距离可能不同)。

Since it only goes over the commits that touch the files in input folder (not over all commits of the source repo), it should be quite fast even on big source repos, if you just extract a deeply nested subfolder that was not touched in every commit.

由于它只遍历输入文件夹中的文件的提交(而不是源代码库的所有提交),因此即使在大型源代码库上它也应该非常快,如果您只是提取一个深层嵌套的子文件夹,而每个子文件夹都没有被触及犯罪。

Since what this does is to create an orphaned branch with all the old repo's history and then merge it to the HEAD, it will even work in case of file name clashes (then you'd have to resolve a merge at the end of course).

由于这样做是创建一个包含所有旧 repo 历史记录的孤立分支,然后将其合并到 HEAD,它甚至可以在文件名冲突的情况下工作(然后你必须在最后解决合并) .

If there are no file name clashes, you just need to git commitat the end to finalize the merge.

如果没有文件名冲突,您只需要git commit在最后完成合并。

The downside is that it will likely not follow file renames (outside of REWRITE_FROMfolder) in the source repo - pull requests welcome on GitHub to accommodate for that.

缺点是它可能不会遵循REWRITE_FROM源存储库中的文件重命名(文件夹外) - 欢迎在 GitHub 上提出请求以适应这一点。

GitHub link: git-move-folder-between-repos-keep-history

GitHub 链接:git-move-folder-between-repos-keep-history

##代码##