How to move a file from one git repository to another while preserving history

21,294

This worked for me, but with a whole directory.

As shown here

~$ cd neu
~/neu$ git filter-branch --subdirectory-filter FooBar HEAD
~/neu$ git reset --hard
~/neu$ git remote rm origin
~/neu$ rm -r .git/refs/original/
~/neu$ git reflog expire --expire=now --all
~/neu$ git gc --aggressive
~/neu$ git prune
~/neu$ git remote add origin git://github.com/FooBar/neu.git

EDIT: For a single file:

Filter the directory first:

 git filter-branch --prune-empty --subdirectory-filter myDirectory -- --all

Filter the single file:

 git filter-branch -f --prune-empty --index-filter "git rm --cached --ignore-unmatch $(git ls-files | grep -v 'keepthisfile.txt')"

Do some cleanup:

 git reset --hard
 git gc --aggressive
 git prune

This should do it.

Share:
21,294

Related videos on Youtube

Randall Cook
Author by

Randall Cook

Follow me on Twitter @randallecook!

Updated on July 09, 2022

Comments

  • Randall Cook
    Randall Cook almost 2 years

    I am trying to move a single file (call it foo.txt) from one repository to another (unrelated) repository, preserving its history. ebneter's question shows how to do this for a subdirectory. taw's question has some hints and suggestions, but not a step-by-step procedure to follow. jkeating's question looked promising, but didn't work for me. Google searches came up empty for this particular use case. What I am looking for is a clear sequence of commands to accomplish this.

    The sequence of commands I started to follow was this:

    $ git clone source-repo/ source-repo-copy
    $ cd source-repo-copy
    $ git filter-branch --tree-filter 'test ! "$@" = "foo.txt" && \
      git rm --cached --ignore-unmatch $@ || true' --prune-empty
    

    The logic of my filter command is to git rm all files that are not foo.txt. I added the || true to the command to force it to have a zero return value to satisfy filter-branch.

    My intention was to set source-repo-copy as a remote for my target repository (target-repo), and assuming git filter-branch filtered out everything but foo.txt, fetch and merge source-repo-copy into target-repo. Unfortunately, the git filter-branch command seemed to have no effect. It ran with no errors and appeared to grind through the 600+ commits in source-repo, but when it finished, the git log and files in source-repo-copy looked the same. Shouldn't all files but foo.txt be missing and all commits that didn't touch it be gone from the log?

    At this point I don't know how to proceed. Any suggestions?

  • Randall Cook
    Randall Cook almost 12 years
    Yes, the --subdirectory-filter examples all seem straightforward, but I'm looking to move a single file, not a subdirectory. Any ideas with that?
  • blang
    blang almost 12 years
    please try it on a testrepo first, the new solution works for me
  • Randall Cook
    Randall Cook almost 12 years
    I filtered the directory as you suggested, using '.' instead of 'myDirectory'. I was left with zero files in the directory (just .git). git log showed only merge commits (12 of them). I then filtered the single file. I got errors because git rm was being called with an empty parameter list. I added || true to the end of the command to prevent git rm's complaints from aborting the filter. After that, still zero files, and only 4 merge commits in git log. I finished up with git reset/gc/prune but saw no change. I guess it didn't work. :( I'm glad I tried this on a test repo. :)
  • blang
    blang almost 12 years
    Please upload a testrepo with your structure on github or something. (Or tell me the hierachy). If the git consists of testDir/myFile{1..3}.txt it works fine. The filter for the single file can't work if ur directory is empty, try to irgnore the directory filter and step on the filter the single. There's a chance your shell doesn't recognize the command well. git filter-branch -f --prune-empty --index-filter 'git rm --cached --ignore-unmatch $(git ls-files | grep -v "keepFile.txt")'
  • Randall Cook
    Randall Cook almost 12 years
    Just after I posted the question I went on vacation, and haven't been able to test this answer thoroughly. Still, I appreciate blang's work. You get the check mark. :)
  • Kyle Strand
    Kyle Strand about 9 years
    TortoiseGit makes this trivial, at least in Windows (right-click-drag the files from one explorer window to another, and select Git Copy and add files to this WC). Any idea how this is implemented under the hood, and whether it can be done similarly at the command line? I'd be really surprised if it removes and then restores the origin, or makes a temporary clone, though I guess it's possible...
  • Micah Smith
    Micah Smith over 8 years
    It wasn't clear to me at first the steps to take for a single file rather than directory. This is my understanding, for anyone similarly confused: The two filter-branch commands in the EDIT should replace the one filter-branch command in the original code. Then, the "Do some cleanup" commands are actually included in the rest of the original code section and should be run as such. Finally, you need to git push origin new-branch.
  • Dolphin
    Dolphin about 5 years
    This can also be used for more than one file by modifying the grep statement with an or: grep -v 'file1\|file2\|file3'