7

I actually have two repositories:

  • main
  • external

The main repository was merged after some time into external/main directory (as subtree). Now I'd like to migrate the changes made to external/main back to main repository, but only these commits and no other unrelated commits like to external/<anything-else>.

I've actually tried the classical:

git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter main -- --all

but that removes all the commits made to the initial main repository too and only leaves the ones which were made in the external git repository.


So: how to retain only the commits:

a) made to the initial main repository

and

b) made to the subtree of external (external/main)


When I try git pull -s subtree ../external, the commits are all merged, including commits which didn't change anything in the subtree. I'd like to have only the commits which actually changed something in the subtree and there also only the information about files from the subtree.

bwoebi
  • 23,637
  • 5
  • 58
  • 79
  • How was `ZZZ` merged into `AAA`? – Maic López Sáenz Jan 19 '14 at 22:02
  • You could also use names that convey a bit of meaning and help the readability of the question, for example `main` and `external` for the repositories. – Maic López Sáenz Jan 19 '14 at 22:05
  • With the usual mechanism: via fetching the repo in `external` and then using `read-tree` to put it into a directory of `external` repo. – bwoebi Jan 19 '14 at 22:06
  • @LopSae edited, now better? – bwoebi Jan 19 '14 at 22:08
  • Definitely :) It's an interesting question. – Maic López Sáenz Jan 19 '14 at 22:19
  • I tried to replicate your scenario and was looking into the following command: git pull -s subtree ../external master. That is executed from the main repository. Would that work for you? – sTodorov Jan 21 '14 at 14:20
  • @sTodorov that's exactly what I tried… but then all the commits are merged, including commits which didn't change anything in the subtree; which I'd like to eliminate. (added that as a note to my question) – bwoebi Jan 21 '14 at 14:33
  • @bwoebi: see my answer below, this is default behavior as far as I read. Possible workaround included. – sTodorov Jan 21 '14 at 14:41
  • Can you use `git-subtree`? `git subtree push` seems to do exactly what you want. https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt – onionjake Jan 21 '14 at 22:05
  • @onionjake I actually get a rejected message `Updates were rejected because a pushed branch tip is behind its remote counterpart.` But I actually did `git pull -s subtree` just before doing the subtree-push… No idea what I'm doing wrong now? – bwoebi Jan 21 '14 at 23:15
  • Can you post exactly the command and the message as you typed it and it appeared? @bwoebi – onionjake Jan 22 '14 at 00:44
  • @onionjake `php-src root# git subtree push -P sapi/phpdbg tmp master` => `To ../phpdbg ! [rejected] 5f7799a971f6ccf57a2ee86760b7b68089165cd1 -> master (non-fast-forward) error: failed to push some refs to '../phpdbg' hint: Updates were rejected because a pushed branch tip is behind its remote counterpart. Check out this branch and merge the remote changes (e.g. 'git pull') before pushing again.` (tmp being a remote to ../phpdbg) – bwoebi Jan 22 '14 at 15:38

2 Answers2

2

You should be able to limit the commits rewritten by filter-branch using --not --remotes in the <rev-list options> section of your command:

git filter-branch --tag-name-filter cat --prune-empty
    --subdirectory-filter main -- --all --not --remotes

This will cause filter-branch not to include commits reachable from your remote branches. If you have multiple remotes, you can use something like --remotes=origin to specify which one to consider.

ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
  • hmm… very nice! thanks! It just somehow still included another unrelated subtree merge commit: https://github.com/krakjoe/phpdbg/commit/efc7d6db18a4f5ecc6c4437041c78f067c96e05d Any idea how to remove it… It somehow became the root commit of the branch what is causing problems… – bwoebi Jan 22 '14 at 21:24
  • @bwoebi, not being familiar with your repository it is hard for me to visualize what happened here. If you simply want to remove the current root commit, you can refer to [this question](http://stackoverflow.com/questions/645450/git-how-to-insert-a-commit-as-the-first-shifting-all-the-others), where the accepted answer shows how to rewrite all of your commits onto a new *empty* root commit. Now you can use `rebase -i` to remove the old root (now the second commit). – ChrisGPT was on strike Jan 23 '14 at 14:25
  • 1
    well… that didn't help, but http://stackoverflow.com/a/6149972/2153758 did. Anyway thank you… You've deserved the bounty. – bwoebi Jan 23 '14 at 14:56
1

In order to bring into main all the relevant changes that happened in external two things will need to happen:

  • Isolate the original main
  • Bring into main it all commits that happened in external

For the first, Isolate the original main:

Seems like you probably have main in its own repository.

If not it could be retrieved from the external repository as long as the original main commits where set as a parent of the commit that created the external/main subdirectory. An example of such is the process outlined in Git Book Subtree Merging page

Commits where subtree where introduced can be found as described in this answer.

And then it is just a matter of grabbing the whole set of commits that are the base of main and make a repository out of it.


For the second, Bring into main it all commits that happened in external:

You already have isolated the commits that contain changes in the exteral/main subfolder, but as you state it does not include the original main commits.

git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter main -- --all

This is because filter-branch will only check for the files in the specific subdirectory location, without knowing how to handle more complex operations like the read-tree that created the subdirectory.

After the filter-branch operation, you will be left with a set of commits that contain all changes that happened in main/external. Lets assume that this set of commits can be reached in the filteredMain branch.

Since the contents were moved from the subdirectory to the root the location of the files is now the same again as in the contents in the main repository. This will allow for both trees to be joined. Since this two trees, main's master branch and filterBranch have no history in common they can be joined by a rebase replaying the changes of the commits.

# in the main repository

# bring the external repository and get the branch
git remote add external /path/to/external
git fetch external filteredMain
git checkout filteredMain

# We need the first commit of this tree for the rebase command
firstCommit=$(git rev-list --max-parents=0 HEAD)

# run the rebase
git rebase --onto master $firstCommit filteredMain

After this the filteredMain branch should contain all the changes that happened in external/main replayed on top of the original master branch in the main repository.

Community
  • 1
  • 1
Maic López Sáenz
  • 10,385
  • 4
  • 44
  • 57
  • looks like a valid alternative, but actually the answer above is much more practical as it is a) less commands and b) operating on just one branch. – bwoebi Jan 24 '14 at 23:05
  • I might have misunderstood the problem because I don't see how the a single `filter-branch` command can be used to keep both `main` commits and `external/main` as a single tree. In any case, there is no worry, I had this answer on the works for a couple of days just for personal curiosity and decided to finish it even if the question was already done. – Maic López Sáenz Jan 25 '14 at 06:25