3

I have a local git repository that is configured to connect remotely over SSH on Gitlab (let's call it git@gitlab.com:myrepo/myproject.git), after cloning repositories I like to keep only a specific folder of the project locally, I do it using the command:

git filter-branch --prune-empty --subdirectory-filter subdirectory HEAD~..

This command makes the local repository keeps track of the subdirectory folder on the remote git project (and overlook all the files that are before this folder on my project). As long as I'm using it only for pulling it's fine... The problem is if I try to push changes to that remote branch, if I try I see the following error:

$ git push -u origin master
To git@gitlab.com:myrepo/myproject.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@gitlab.com:myrepo/myproject.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

I haven't found anything to this specific case on git push --help. Does anyone know if it's possible to push on a remote folder when I only have a subfolder of the project on my local repository?

Rafael Muynarsk
  • 614
  • 2
  • 7
  • 21
  • No, Git simply doesn't work that way--you can't just operate on a subdirectory. You have to keep the whole tree. – John Szakmeister Aug 31 '19 at 16:53
  • "As long as I'm using it only for pulling it's fine..." -- that seems doubtful. Pulling would likely bring the original tree back, but the fact that you're rewriting the entire history makes it seem just as likely git would fail to merge since there may not be any commits in common. – John Szakmeister Aug 31 '19 at 17:41
  • It looks like this is getting closer to being possible: https://stackoverflow.com/questions/600079/how-do-i-clone-a-subdirectory-only-of-a-git-repository/52269934#52269934 However, the server-side portions don't appear to be there yet. – John Szakmeister Aug 31 '19 at 17:44
  • @JohnSzakmeister This is not a duplicate. OP has a full clone, not a partial one, and wants to do a subtree mergeback. That _is_ possible in Git. – jthill Aug 31 '19 at 17:45
  • @jthill Not really. He filtered everything off but what he wanted rewriting the entire history in the process. What he really wants is to clone just the subdirectory he’s interested in and track that. – John Szakmeister Aug 31 '19 at 18:15

2 Answers2

2

git push pushes complete histories; what you're doing is a subtree merge. Git can do this, change your filter-branch from HEAD to HEAD~.. to not rewrite the entire history, then you can

git checkout origin/master
git merge -s subtree master
git push origin HEAD:master

Any other filter-branch cutoff that preserves a merge base will do, it all depends on how much you really want rewritten locally.


If checkouts of the full origin snapshot are so painful you don't want to go through them even once you can avoid the unnecessary worktree churn with some light finagling.

Create a vanishingly-lightweight clone for scratch work and switch to it:

git branch -tf mergeback origin/master
git clone -nsb mergeback -o local . `mktemp -d`
cd $_

Note that a clone of the entire linux repo done this way uses 320KB (that's not a typo) on my box. Then, you can do a minimum-checkout merge:

git reset --quiet                    # load just the index, not the worktree
git merge -s subtree local/master    # merge my master branch into the right subtree
git push local mergeback             # push the results back to the main repo
cd -                                 # back to my repo

and from there you can git push origin mergeback:master to push the results up to gitlab.

Edit: p.s. rather than rewriting a commit with filter-branch, you can also hoist the subtree in a new commit: git read-tree -um @:path/to/subdir; git commit, that way the merge won't be introducing evil twins of existing commits.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • The first block of code works but it brings the whole again tree for a moment, for me that wouldn't work very well as I have some scripts running in random moments in this folder... So I think the merging solution in the second block of code is the one for me even though I still didn't manage to make it work here. The idea is that I will work on the subdirectory, commit the changes, merge these changes to the mergeback branch (that carries the whole tree) and then I can push this mergeback branch as master to gitlab, right? Or you're doing something else? – Rafael Muynarsk Aug 31 '19 at 18:58
  • That's it: minimum-checkout merge to an ad hoc mergeback branch (which is just your full remote-tracking origin/master history with a local branch name for easy reference). What's not working? – jthill Aug 31 '19 at 19:20
  • There's something wrong with the merges `git merge -s subtree master` and `git merge -s subtree master`... They both give me the error `fatal: refusing to merge unrelated histories`. (I've tested it cloning my remote repository, using the command from my question to work on a specific folder, changing a file, commiting it and then I've tried your commands... the error is on the merge) – Rafael Muynarsk Aug 31 '19 at 20:27
  • 1
    Ah, my bad, instead of doing your filter-branch on `HEAD`, do it on `HEAD~..`, that will hoist the subtree only in the tip commit, the subtree merge notices the correspondence and doesn't treat the hoist as a change to be merged. – jthill Aug 31 '19 at 20:53
  • I've just crossed with the "evil twins of commits" problem that you've mentioned here... But I didn't manage to run `git read-tree -um @:path/to/subdir` successfully. On the sample of the question should it be `git read-tree -um @:subdirectory` even when I'm already inside the subdirectory? – Rafael Muynarsk Sep 01 '19 at 04:38
  • 1
    Yes, the path is rooted at the top of the committed tree, see `git help revisions` for the commit:path/in/commit syntax. If you're on Unix you'll be able to delete your current directory, on Windows as I recall you won't. ... hunh, I just learned (from checking the help myself) that you can use `.` as the current directory, so `@:.` means the commit path you're currently in. – jthill Sep 01 '19 at 05:25
-3

Have you tried to use git push -- force ? it's not best practices I know but it just might work for you or you can pull everything from the remote repo first and then push every thing pack and it might just work for you

kyrolos magdy
  • 393
  • 1
  • 4
  • 19