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.
(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
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)
cd into it
cd <git repository A directory> eg. cd /c/Working/GIT/myprojects
Delete the link to the original repository to avoid accidentally making any remote changes (eg. by pushing)
git remote rm origin
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
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
Cleanup step
git reset --hard
Cleanup step
git gc --aggressive
Cleanup step
git prune
You may want to import these files into repository B within a directory not the root:
Make that directory
mkdir <base directory> eg. mkdir FOLDER_TO_KEEP
Move files into that directory
git mv * <base directory> eg. git mv * FOLDER_TO_KEEP
Add files to that directory
git add .
Commit your changes and we’re ready to merge these files into the new repository
git commit
Stage Three
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)
cd into it
cd <git repository B directory> eg. cd /c/Working/GIT/FOLDER_TO_KEEP
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
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.
Finally, you probably want to clean up a bit by removing the remote connection to repository A
git remote rm repo-A-branch
Push and you’re all set.
git push
精彩评论