How to "git subtree" only one file or directory?
Asked Answered
T

3

45

I use git subtree like:

git subtree add \
   --prefix=directory_destination_path \
   --squash [email protected]:kicaj/projectname.git \
   master

But in directory_destination_path, it copies the entire repo from projectname.git.

How do I copy only a subdirectory or file from projectname.git to directory_destination_path?

Also, how do I update (automatic) file changes in both repositories were still the same? It is possible?

Toy answered 11/3, 2014 at 19:10 Comment(1)
Possible duplicate of #39479654 that has an answer about extracting history of a single file using git fast-export.Dropkick
F
31

If I understand, you seem to want to only merge in a certain directory of a different repository, and you want it to be a subtree in your repository. I am going to call the directory of interest in the project.git path_of_interest_in_project and call the destination in your repo directory_desination_path.

Try adding the remote project.git as a remote, then checking out one of its branches locally. Then use git-subtree split to split out just the directory of project.git you are interested in. After that merge it into your repo using subtree merge.

git remote add project [email protected]:kicaj/projectname.git
git branch project_master project/master

The branch project_master should now store the entire history of your project.git repo.

Then you'll need to use the git-subtrees-split process.

git checkout -f project_master
git subtree split --squash --prefix=path_of_interest_in_project -b temp_branch

There should now be a branch called temp_branch containing just the directory you are interested in. Now you can perform a git-subtree-merge to bring it all into your repo.

git checkout -f master
git subtree merge --allow-unrelated-histories --prefix=directory_destination_path temp_branch

This should merge in the temp_branch into your master branch.

Frohman answered 11/3, 2014 at 20:29 Comment(12)
git subtree split squash --prefix=path_of_interest_in_project temp_branch always return: does not exist; use git subtree addToy
I change path_of_interest_in_project to Model/Behavior - there are directoriesToy
I would like use repo1/Model/Behavior/file.php to repo2/Model/Behavior/file.php They should always the same content regardless of which I changeToy
I had a typo. It should be --squashFrohman
As far as them being in sync, theres no built in way to just automatically keep these files (only) in sync. You would have to write a script to handle it all. An easier thing might be to track these files as independent repo's and use them as subtress in both projects. This is simply not a good workflow overall.Frohman
Problems: you need to git fetch before you can git branch otherwise you get fatal: Not a valid object name. And when I got to the git subtree split I wasn't sure what to do after that because I got fatal: ambiguous argument 'temp_branch': unknown revision or path not in the working tree..Klipspringer
There was a typo in the command. I edited it. A -b was needed before the name of the branch. It now works exactly as the OP intended.Iselaisenberg
then how to update the only one file or directory? do every steps again?Analogize
As far I can tell, git subtree split --squash --prefix=path_of_interest_in_project -b temp_branch only if path_of_interest_in_project is a directory. In which case it grabs the commits related to all the files in that directory. Is there a way to specify one and only one file?Ranunculus
This answer must be out of date and should be updated. git subtree merge without git subtree add produces a "does not exist" error for me. The --allow-unrelated-histories flag is not accepted either.Disorderly
also git subtree split --squash gives "The '--squash' flag does not make sense with git subtree split"Smolder
Omitting --squash and using git subtree add instead of merge --allow-unrelated-histories worked fine in my case! :)Smolder
R
9

Suppose you wanted to add the images/ prefix of the octocat git repo at the ref master.

Suppose we want to use the remote hosted at https://github.com/octocat/octocat.github.io.git (Note: GitHub returns error: Server does not allow request for unadvertised object in the following fetch command is you specify a sha1 not associated with named ref)

Starting on the branch you wish to add the subtree to, let's first fetch the desired commit history and checkout our new branch

git fetch https://github.com/octocat/octocat.github.io.git master:tmp_octocat_master
git checkout tmp_octocat_master

Next, let's create a new history where only the files under our desired prefix (images/) exist.

git subtree split --prefix=images -b subtree_split_branch

Finally, let's add this subtree to our desired branch (presumably the last branch you were on, git checkout -)

git checkout -
git subtree add --prefix=public/images subtree_split_branch

Now you should have all the desired files on your current branch with a full git history.

Alt Squashed history

At time you want the commits in our subtree to be squashed into a single commit. This to a certain extent defeats the purposes of adding a subtree but it has it's place. The following is a variation of what is described above to limit the history pulled into your repo

Starting on the branch you wish to add the subtree to:

git fetch --depth=1 https://github.com/octocat/octocat.github.io.git master:tmp_octocat_master
git checkout tmp_octocat_master

Note: we are specifying --depth=1 in the above since we are using the --squash is the following git subtree split command.

git subtree split --squash --prefix=images -b subtree_split_branch
git checkout -
git subtree add --prefix=public/images subtree_split_branch
Ranunculus answered 9/8, 2018 at 20:0 Comment(3)
NOTE: I do not believe git subtree split currently supports specifying a single file. It builds off and passed the --prefix command directly to git read tree. > --prefix=<prefix>/ Keep the current index contents, and read the contents of the named tree-ish under the directory at <prefix>. The command will refuse to overwrite entries that already existed in the original index file. Note that the <prefix>/ value must end with a slash.Ranunculus
why do we need public in --prefix=public/images?Phytophagous
Then how can this subtree get updated based on its own remote?Subir
S
5

It's a great answer from @eddiemoya, however some commands don't work as of today. So I'll just repost them here:

# create a target branch in the new repo
git branch target

# check out the other repo into a new branch
git remote add temp [email protected]:aRepo/any-service.git
git fetch temp
git branch temp_master temp/master

# split desired folder into a temp branch
git checkout -f temp_master
git subtree split --prefix=path/to/desired/folder -b temp_branch

# check out target branch and add all commits in temp branch to the correct folder
git checkout -f target
git subtree add --prefix=path/to/destination/folder temp_branch
# ... voila

# clean up
git branch -d temp_branch
git branch -d temp_master
git remote remove temp
Sanborn answered 16/9, 2022 at 10:2 Comment(1)
It's working for pulling the first time. What about updating the destination folder later? I've tried git subtree merge but getting refusing to merge unrelated historiesRetouch

© 2022 - 2024 — McMap. All rights reserved.