47

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?

Community
  • 1
  • 1
Randall Cook
  • 6,728
  • 6
  • 33
  • 68
  • 2
    Possible duplicate of [How to move files from one git repo to another (not a clone), preserving history](http://stackoverflow.com/questions/1365541/how-to-move-files-from-one-git-repo-to-another-not-a-clone-preserving-history) – tripleee Feb 17 '16 at 10:21

1 Answers1

37

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.

Sean Duggan
  • 1,105
  • 2
  • 18
  • 48
blang
  • 2,090
  • 2
  • 18
  • 17
  • 1
    Yes, the `--subdirectory-filter` examples all seem straightforward, but I'm looking to move a single file, not a subdirectory. Any ideas with that? – Randall Cook May 09 '12 at 22:06
  • please try it on a testrepo first, the new solution works for me – blang May 09 '12 at 22:45
  • 1
    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. :) – Randall Cook May 10 '12 at 01:26
  • 2
    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")' – blang May 10 '12 at 06:59
  • 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. :) – Randall Cook May 23 '12 at 21:37
  • 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... – Kyle Strand Feb 05 '15 at 22:39
  • 1
    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`. – Micah Smith Aug 20 '15 at 19:55
  • This can also be used for more than one file by modifying the `grep` statement with an or: `grep -v 'file1\|file2\|file3'` – Dolphin Feb 18 '19 at 13:14