Reputation: 1064
I generated a patch using the following command
git log -p -m -1 --pretty=email --first-parent XXXX
.
I'd like to apply this patch to another repository (having the same files but not necessarily the same history).
When using git apply
the patch is correctly applied, except that I have to commit the files myself with the right user, date ...
When using git am
I have the following errors:
error: toto.cpp: patch does not apply
I just don't manage to understand why it would work with apply
but no with am
Any idea how I can apply it with am
?
Edit:
The answers given in linked answer just explain that apply
won't commit the changes while am
will. More it says that am
uses apply
in background so it seems it's something specific to the am
command that fails.
Upvotes: 3
Views: 1535
Reputation: 489083
The git am
command is deliberately picky about its input format so that it can create a new commit whose hash ID is identical to the hash ID of the original commit, which in turn means that the new commit that git am
created is bit-for-bit identical to the original commit.
The git apply
command is deliberately not-so-picky about its input format because you, not git apply
, will create a new commit that is not bit-for-bit identical to any original commit.
Your question starts with the command:
git log -p -m -1 --pretty=email --first-parent
which suggests that you are showing a merge. The git format-patch
command never includes merges in its output, because git am
cannot make merges. Since the point of git am
is to make a bit-for-bit identical commit, while the point of git format-patch
is to produce output suitable for input to git am
and git am
cannot make merges, there's no need for git format-patch
to present a merge commit.
These combine to make the git am
tool useless for your particular purpose (which appears to be convert a merge commit to a changeset against its first parent, transport that changeset through email or something similar to email, and then apply it at the other end to get a different commit with similar but not identical metadata, as if via cherry-pick). You will just have to write your own tool, or use git apply
and make the commit manually.
As the above paragraph suggests, one method you could use to write your own tool is to cherry-pick the merge (with -m 1
) atop the first parent of the merge (probably on a detached HEAD), use git format-patch
to format that patch, and use git am
to apply that patch elsewhere. This script is quite untested but might work:
#! /bin/sh
. $(git --exec-path)/git-sh-setup
# abort if on orphan branch (not worth the necessary hackery)
git rev-parse -q --verify HEAD 2>/dev/null ||
die 'this does not work on an orphan branch'
# stolen out of git-filter-branch
finish_ident() {
# Ensure non-empty id name.
echo "case \"\$GIT_$1_NAME\" in \"\") GIT_$1_NAME=\"\${GIT_$1_EMAIL%%@*}\" && export GIT_$1_NAME;; esac"
# And make sure everything is exported.
echo "export GIT_$1_NAME"
echo "export GIT_$1_EMAIL"
echo "export GIT_$1_DATE"
}
set_ident () {
parse_ident_from_commit author AUTHOR committer COMMITTER
finish_ident AUTHOR
finish_ident COMMITTER
}
# begin format-patch-even-if-merge code
hash=$(git rev-parse --verify "$1") || exit # verify that it's valid
hash=$(git rev-parse $hash^{commit}) || exit # and that it's a commit
parents=$(git rev-parse $hash^@)
set $parents
# if a root commit or ordinary commit, just use git format-patch
case $# in
0|1) git format-patch --stdout -1 $1; exit;;
*) ;; # merge - use cherry-pick
esac
# this is the meat of the trick, here
firstparent=$1
exec 3>&1 1>&2 # save stdout and redirect to stderr
echo "cherry picking $hash onto $firstparent to make it format-able"
# save where we were (branch or hash); arrange to return
# there on exit or ^C etc
if ! returnto=$(git symbolic-ref --short HEAD 2>/dev/null); then
# already detached, save hash ID
returnto=$(git rev-parse HEAD)
fi
trap "git checkout $returnto; exit" 0 1 2 3 15
# Use the identity of the merge creator for new commits.
# NOTE: this should be optional and probably NOT the default,
# so it is commented out here.
#eval "$(set_ident <$hash)" ||
# die "setting author/committer failed for commit $hash"
# Move to a detached head on the first parent of the merge.
git checkout $firstparent || exit
# Now we can cherry-pick the merge to a non-merge.
git cherry-pick -m 1 $hash || exit
# Now show the commit we just made, to original stdout.
git format-patch --stdout -1 HEAD 1>&3
If you wish to transport a merge commit intact, the easy way to do that is through the standard git push
or git fetch
protocols. If those are not available for some reason, the git bundle
command can make a file (which Git calls a bundle) that contains all the data needed to transport some portion of a repository across a barrier that push/fetch cannot itself cross. You can then run git fetch
on the bundle file, so that you extract the necessary information after crossing that barrier some other way.
Note, however, that you need all the objects reachable from the merge commit: the bundle must contain every object that the other Git lacks. The whole point of the fetch and push commands is to let Git compute this set of objects: the two Gits talk to each other, to figure out what the sending Git has that the receiving Git lacks. That's what goes into the bundle. While it's OK for a bundle to have extra objects (the receiving Git can ignore them), it's a waste of resources to transport them, so fetch or push will compute the minimal object-set, pack it up into a "thin pack" (the fetch/push equivalent of a bundle), send it over the wire, and let the other end unpack it.
Upvotes: 4