Moving part of a git repository’s history into another repository

There are lots of posts on here about moving a folder out of one repository into a new repository using git filter-branch; what I need to do is move a single file into a new repository.

I’ve already created the new repository, and added the old one from the filesystem as a ‘remote,’ and created a new “root commit” (just adding a README for the new single-file project.) Now I need to transplant the commits pertaining to this particular file onto that new root-commit.

  • Storing generated files in Git
  • Ignoring symlinks in git
  • Pre-commit hooks getting partial committed file list
  • How do I run git bisect only on commits that changed a certain file?
  • Changing info issue using git commands
  • git diff tries to compare files which don't exist
  • (I should mention that at no point was this file modified in the same commit as any other files; I suspect that may make this task slightly easier.)

  • How can I recover a commit to an unnamed branch in Git?
  • Problem cloning a single SVN Branch via git svn
  • How do I go back to previous Git Commit?
  • Git add not working with .png files?
  • Moving a directory containing a subproject / remains from project within project
  • Phantom file in github commit
  • 4 Solutions collect form web for “Moving part of a git repository’s history into another repository”

    This is based on an example in the filter-branch manpage:

    git filter-branch --index-filter 'git ls-files -s | grep $'\t'<file-to-keep>$ | \
        GIT_INDEX_FILE=$ git update-index --index-info && \
        mv $ $GIT_INDEX_FILE' --prune-empty -- --all

    The index filter prints the current contents of the index using git ls-files -s, greps out only the file to keep (that grep is fairly obsessive – the fields are tab-delimited, with the filename being the last one), then creates a new index using that information, and moves it on top of the old one.

    The --prune-empty option causes filter-branch to remove any commits which now do nothing (i.e. they only touched other files), and the -- --all tells it to rewrite all refs.

    As always with filter-branch, it’s best to do this in a fresh clone, so if you screw anything up really badly you’re safe, even though filter-branch does keep backups in refs/originals. You might also want to read the checklist for shrinking a repository in the manpage; the upshot is that once you’re done, the best way to actually get rid of all the stuff you no longer need is to simply clone the filtered repository.

    This will actually work even if the file was modified in the same commits as other files, though I suppose you could try to be sneaky and take advantage of that fact by simply generating patches for all commits which did touch that file, then going and constructing a new repository by applying those patches… but why bother?

    (Side note: it’s way easier to remove a single file than to keep a single file. All you have to do in that case is use git rm --cached --ignore-unmatch <filename> for an index filter.)

    I ended up using the responses from this (otherwise irrelevant) post to construct a solution. Instead of rebasing into a tree with a new root, I modified the old root and --onto-rebased the content to the new root:

    Can I remove the initial commit from a Git repo?

    A solution in a different vein – add the old repo as another remote, and then rebase and cherry-pick can work.

    git clone old_repo
    git clone new_repo
    cd new_repo
    git remote add temp_repo old_repo
    git fetch temp_repo
    # bring the changed from old_repo into new_repo
    git rebase --onto <...> temp_repo/master
    git cherry-pick [list of relevant commits]
    git remote rm temp_repo
    git push origin HEAD

    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.

    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.

    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 update-index --index-info && \
      mv $ $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

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

    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

    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

      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

      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
    Git Baby is a git and github fan, let's start git clone.