1

I need to create patches from one repo (repo-a) and merge them into another (repo-b). These patches needs to contain the commits themself, so I found that git format-patch and git am is a perfect fit.

We are using multiple branches in repo-a and from time to time, files are added from two different branches and merged into main.

git am doesn't like this.. Here is a full example of the problem;

mkdir repo-a; cd repo-a

git init

touch randomfile
git add randomfile
git commit -m "Adding an initial file"

git checkout -b anotherbranch
touch a-file
git add a-file
git commit -m "Adding a-file from anotherbranch"

git checkout main
touch a-file
git add a-file
git commit -m "Adding a-file from main"

git merge anotherbranch

git format-patch -o patches HEAD~2
# patches/0001-Adding-a-file-from-anotherbranch.patch
# patches/0002-Adding-a-file-from-main.patch

cd ..
mkdir repo-b; cd repo-b
git init
cp -r ../repo-a/patches ./

git am patches/*
# Applying: Adding a-file from anotherbranch
# applying to an empty history
# Applying: Adding a-file from main
# error: a-file: already exists in index
# Patch failed at 0002 Adding a-file from main
# hint: Use 'git am --show-current-patch=diff' to see the failed patch
# When you have resolved this problem, run "git am --continue".
# If you prefer to skip this patch, run "git am --skip" instead.
# To restore the original branch and stop patching, run "git am --abort".

The above fails because git already has the file. Normally, git is happy and adds the patches as it should..

Is there a way to do this, and keep the git commits? I don't want to create one big patch, I want the commits as well.

I have read https://git-scm.com/docs/git-format-patch and enter link description here. I have also tried some random options (like --find-copies-harder), but without any luck.

Some additional info:

  • rebo-b is made from repo-a using cp -r, rm -rf .git and-some-other-files and a new git init.
    • rebo-b is therefor the main repo, rebo-a is just a stripped down version.
    • The main goal of all this is to have someone be able to work on repo-b, but without them having access to everything that is in rebo-b. After copying repo-b to repo-a, I am doing a lot of file-deletions, but only deletions.
xeor
  • 5,301
  • 5
  • 36
  • 59
  • You've simply hit the patch equivalent of an add/add conflict. The patch says "create a new file named `a-file`" and you *already have a file with that name*, what would you like to do, overwrite the existing file, rename the existing file, rename the new file, add the additional text *to* the existing file (at the top or the bottom?), or what? – torek Jun 29 '22 at 08:07
  • I'm not sure, I just want to make a set of patches from time to time onto another repo. I want git to be the magic creature it is and just solve it.. :p Joke aside, I understand the problem better now, so I might be able to figure out a better way – xeor Jun 29 '22 at 10:02
  • Alas, Git isn't as magical as we might like. There is something that `git merge` doesn't do with add/add conflicts (and neither does `git am`) that might be sensible in most cases: if the new file on "both sides" of the "merge" operation is identical, just take one copy of it. I'm not sure if I would want `git am` / `git apply` to do this by default, even though I think having `git merge` do it by default would probably be fine... – torek Jun 30 '22 at 08:54

3 Answers3

1

git format-patch is well suited to extract a linear history of commits, but not for merges :

DESCRIPTION
Prepare each non-merge commit with its "patch" ...

[...]
CAVEATS
Note that format-patch will omit merge commits from the output, even if they are part of the requested range.

  • as you can see in your example :

a patch is created for each of your commits on anotherbranch and main, but none for the merge commit, and if you inspect the pacthes, they do not include information to state that they both have the same parent.

This is not enough information to automatically rebuild a history with merges.


There may be several strategies to port commits from repo-a to repo-b while preserving branching and merges, but we would need a bit more context to give you a more precise answer.

For example : one way could be to add repo-a as a remote in repo-b, and use git rebase -r (-r is short for --rebase-merges) or some subcommand of git filter-repo to port complete chunks of history.

LeGEC
  • 46,477
  • 5
  • 57
  • 104
  • That's a good idea, however, repo-a is not a clone of rebo-b, it is a `cp -r ...; rm -rf .git ...; git init`. So git tells me `fatal: refusing to merge unrelated histories` if I try that. I updated the question with this detail. – xeor Jun 30 '22 at 06:16
  • @xeor : perhaps you could describe this (the `cp ... && rm -rf .git` part), as well as why you keep these two "unrelated" repositories, yet want to port parts of history from `a` to `b`, in a separate question ? – LeGEC Jun 30 '22 at 06:19
  • for a quick hack : if you know at precisely what commit in `repo-a` you did the "clone that is not a clone" operation, you can run (locally) `git replace `, and then retry the `rebase` command – LeGEC Jun 30 '22 at 06:22
  • Addded some more explanation to the question. The reason I have them separate is because I sometimes need people to work on some, but not all parts of the repo. But those people can't get access to the whole thing. It is undefined what parts they need access to, and a submodule won't really do the trick (in most cases).. I'll read up on `git replace`, maybe it helps :) – xeor Jun 30 '22 at 06:41
  • @xeor : ok. Do look into `git-filter-repo` then : you can use it to script a way of rewrting the content of `repo-a` in an intermediate repository, such that, if you run it a second time later, the existing commits match. – LeGEC Jun 30 '22 at 06:49
  • Looks like git has a `--allow-unrelated-histories` on it's merge command (https://stackoverflow.com/a/10548919/452081), that looks very promising on first try... Will investigate that more – xeor Jun 30 '22 at 13:00
1

I found a way to do this in the way I wanted.

git remote add otherrepo ../otherrepo
git fetch otherrepo

# If you want to fetch lfs objects as well
git lfs fetch --all otherrepo

# -X subtree: Or you will get a lot of merge-conflicts, some of them with empty lines.
# --signoff: To add signoff on the merge-commit (not the merged commits)
# --allow-unrelated-histories: This is the main trick!
git merge -X subtree --signoff --allow-unrelated-histories otherrepo/main

The whole history and everything works as it should. The merge-commit contains what is merged, and history, graph, authors and everything looks good.

xeor
  • 5,301
  • 5
  • 36
  • 59
0
git merge anotherbranch

I would rather rebase the branch on top of the target branch (on top of main), before making a patch between that new branch HEAD and main (instead of HEAD~2)

git switch anotherbranch
git rebase main
git format-patch -o patches main
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Any other way? There are multiple branches, and I don't have control of them. Do you mean that I can get all branches from upstream, loop through and rebase them all a top of main? Or am I thinking of it the wrong way? What if the branches are not on upstream anymore? – xeor Jun 29 '22 at 07:59
  • @xeor I meant just test that for one branch, in order to see if the git am still complains on repoB. – VonC Jun 29 '22 at 08:05