2

Problem

I'd like to move a folder (and subfolder contained files) from one repository to another, preserving the history.

I found one approach on SE: How to move files from one git repo to another (not a clone), preserving history. And a different idea on blog.neutrino.es. It is the last one that I'd like to discuss here.

Attempted Solution

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 $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

If I understand correctly, the idea is to pretend we're going to submit the commits by e-mail, and re-import them in another repository.

Error

I get this error message when doing the git am /tmp/mergepatchs/*.patch:

Applying: Initial commit
error: .gitignore: already exists in index
error: README.md: already exists in index
Patch failed at 0001 Initial commit
The copy of the patch that failed is found in:
   /Users/myuser/repo/org/.git/rebase-apply/patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

In order to understand better the process, I tried a single file first (instead of the whole directory). However "nothing" happened after git am (i.e. no new file, git status does not report any change). Why is that?

I then tried:

INITCOMMIT=$(git rev-list --parents HEAD | egrep "^[a-f0-9]{40}$")
git format-patch -1 -o /tmp/mergepatchs ${INITCOMMIT}

But then got the same error message as before.

Why are the patches failing?


Edit 1

I tried something related, inspired from How to create and apply a patch with Git.

In ~/repo/org:

$ git format-patch --root HEAD --stdout myfile.c > /tmp/mergepaths/01.patch

In ~/depo/dest:

$ git apply --stat /tmp/mergepaths/01.patch 
 0 files changed
$ git apply --check /tmp/mergepaths/01.patch 
$ git am < /tmp/mergepaths/01.patch 

Both stat and check tell me that "nothing" will be done. The patch object is far from empty. By the way, I don't know if this is relevant but both the creation and application of patches are done in branches.

Community
  • 1
  • 1
Hugues Fontenelle
  • 5,275
  • 2
  • 29
  • 44
  • Does this answer your question? [git move directory to another repository while keeping the history](https://stackoverflow.com/questions/41811986/git-move-directory-to-another-repository-while-keeping-the-history) – Hugh Perkins Nov 29 '21 at 14:57

2 Answers2

10

There's a tool for surgery like this, git filter-branch.

For something this simple, you don't need much of a safety net. For reference, though, this is how I do pretty much anything that might dirty up history or namespace or worktree

# make a throwaway sandbox to play in:
git clone -s . /tmp/deleteme
cd !$
git checkout -b sliced

# do it
git filter-branch --subdirectory-filter your/subdir

# and if the result looks good:
git push origin sliced

filter-branch docs

and you can push to any repo you have a url or a path for, just use the url directly instead of making a remote name for onesy-twosie work. To push and rename: git push u://r/l sliced:branchnameinthatrepo


From comments you want to both select and relocate the subdirectory. That's some fairly straightforward git read-tree work. Read-tree operates on the index, so you want an index filter for it. To wit, this one:

git filter-branch --index-filter '
        git read-tree --prefix=des/ti/nation/    $GIT_COMMIT:source/subdir
        git read-tree -m                         $GIT_COMMIT    `git mktree </dev/null`
'

which despite its unfamiliarity is quite simple if you just recall how git works.

As reminder or recap or intro as the case may be:

  1. The repository proper is an object store: ask for anything by type and unique name (aka SHA1), the repo obligingly regurgitates it; ask the repo to remember any content, you feed it type and bytes and it (a) stores that and (b) feeds you the unique name for it.

  2. The index is just a list, mapping pathnames to the repository content that goes there.

  3. git read-tree is the underlying operation for checkout and merge and reset -- it doesn't actually operate on the repo, all the objects it works with are already there. You feed it existing trees, it combines them with what's in the index (and optionally updates the worktree, though that's irrelevant here) to produce the index you want, or at least get you one step closer to it.

The first read-tree above is

    git read-tree --prefix=destination/subdir/ $GIT_COMMIT:source/subdir

and you can identify the fundamental nature of what git read-tree is going to do by counting how many trees you gave it. This one is a one-tree read, used here to add to the index (edit: btw, with no options, 1-tree git read-tree replaces the index). The tree getting added here is at source/subdir in the commit being filtered, and read-tree adds it all to the index with destination/subdir/ added to the front of the pathnames.

The next read-tree is a two-tree read, it does the index (and worktree, if there was any you wanted to do here) work for git checkout -- it applies the differences between the original tree and the target tree to the index. Here, the original tree is $GIT_COMMIT and the target tree, git mktree </dev/null, is the empty tree. So the operation is "find everything in the original tree in the index and make all those entries look exactly like the target tree" aka here "make them all go away". The added destination subdir above is not involved, it wasn't in the original tree so read-tree doesn't make that go away.

The filter's done, filter-branch commits what's in the new index (of content already in the repo, remember) and it's time for the next commit.

read-tree docs. This link skips the description, that's intentional. Don't read it.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • The trick about creating the "safety net" is great. Now I can toy around with the filter-branch. Thanks (+1 for that!). – Hugues Fontenelle Mar 04 '15 at 13:30
  • But I didn't manage the push yet, as what's in my `sliced` should go to a different folder into the destination repository. – Hugues Fontenelle Mar 04 '15 at 13:32
  • Thank you for your thorough edit. I wish I could upvote you more :-) It does answer my problem (I now have my files moved at the right place). – Hugues Fontenelle Mar 05 '15 at 09:45
  • Arguably it doesn't precisely answer my question (with `format-patch` and `am`). I'm weighing between waiting for more discussion regarding that approach, or just change the title of my question and accepting your answer. – Hugues Fontenelle Mar 05 '15 at 09:47
  • Thanks. Yeah, you put effort into dealing with what's in front of you and into explaining where that attempt got you. I didn't think that attempt was using the right tools for the situation is all. – jthill Mar 05 '15 at 15:32
  • Does adding `-- --all` is necessary/useful as shown in other answers? – Paweł Prażak Aug 23 '17 at 14:11
  • Does cleanup steps like `git reset --hard`, `git gc --aggressive`, `git prune` is necessary/useful as shown in other answers? – Paweł Prażak Aug 23 '17 at 14:12
2

It looks like the problem you are having is that certain files exist in both repositories you are trying to then create them from the patch. In your example these are README.md and .gitignore

When applying the patch with git-apply, you can ignore these files with --exclude=<path-pattern>. See http://git-scm.com/docs/git-apply

mproffitt
  • 2,409
  • 18
  • 24