开发者

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

开发者 https://www.devze.com 2023-02-12 10:38 出处:网络
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.

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.

(I should mention that at n开发者_如何转开发o point was this file modified in the same commit as any other files; I suspect that may make this task slightly easier.)


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_INDEX_FILE.new git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $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
OR
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_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
    

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 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
    

    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
    
0

精彩评论

暂无评论...
验证码 换一张
取 消