ChatGPT解决这个技术问题 Extra ChatGPT

Simple tool to 'accept theirs' or 'accept mine' on a whole file using git

I don't want a visual merge tool, and I also don't want to have to vi the conflicted file and manually choose the between HEAD (mine) and the imported change (theirs). Most of the time I either want all of their changes or all of mine. Commonly this is because my change made it upsteam and is coming back to me through a pull, but may be slightly modified in various places.

Is there a command line tool which will get rid of the conflict markers and choose all one way or another based on my choice? Or a set of git commands which I can alias myself to do each one.

# accept mine
alias am="some_sequence;of;commands"
alias at="some_other_sequence;of;commands"

Doing this is rather annoying. For 'accept mine' I have tried:

randy@sabotage ~/linus $ git merge test-branch
Auto-merging Makefile
CONFLICT (content): Merge conflict in Makefile
Automatic merge failed; fix conflicts and then commit the result.

randy@sabotage ~/linus $ git checkout Makefile 
error: path 'Makefile' is unmerged

andy@sabotage ~/linus $ git reset --hard HEAD Makefile 
fatal: Cannot do hard reset with paths.

How am I supposed to get rid of these change markers?

I can do:

git reset HEAD Makefile; rm Makefile; git checkout Makefile

But this seems rather round about, there must be a better way. And at this point, I'm not sure if git even thinks the merge happened, so I don't think this necessarily even works.

Going the other way, doing 'accept theirs' is equally messy. The only way I can figure it out is do:

git show test-branch:Makefile > Makefile; git add Makefile;

This also gives me a messed up commit message, which has Conflicts: Makefile in it twice.

Can someone please point out how to do the above two actions in a simpler way? Thanks

I have to give it to you as a three year+ git command line user I find this ridiculously hard to do from memory. It really should be built in by default.

T
Thomas Ahle

The solution is very simple. git checkout <filename> tries to check out file from the index, and therefore fails on merge.

What you need to do is (i.e. checkout a commit):

To checkout your own version you can use one of:

git checkout HEAD -- <filename>

or

git checkout --ours -- <filename>

(Warning!: If you are rebasing --ours and --theirs are swapped.)

or

git show :2:<filename> > <filename> # (stage 2 is ours)

To checkout the other version you can use one of:

git checkout test-branch -- <filename>

or

git checkout --theirs -- <filename>

or

git show :3:<filename> > <filename> # (stage 3 is theirs)

You would also need to run 'add' to mark it as resolved:

git add <filename>

I found it a bit strange that --ours and --theirs means exactly the opposite of what I intuitively thought when trying out this command...
Be careful when using git show – this skips newline normalization.
This is nice for a few files, but when you have many files in conflict (because a date in a comment was changed!), how do you do it ?
@Santhos: the -- is used by Git to separate revisions (branch names etc.) from path names (filenames, directories). It is important if Git cannot decide if a name is the name of branch or the name of file. This follows POSIX (or GNU) convention of using double dash to separate options from arguments (filenames).
@Sammaron @Joshua Muheim; the theirs/ours can appear swapped if you are resolving conflicts in the context of a rebase operation. Because rebase works by checking out the target branch then cherry-picking commits from "your" branch onto the target, the incoming change ("theirs") is from "your" branch, and the current branch is the target branch ("ours").
S
Sicco

Try this:

To accept their changes: git merge --strategy-option theirs

To accept your changes: git merge --strategy-option ours


Note that this will keep your changes for ALL conflicting files, so could be dangerous if an unexpected conflict occurs.
And you can use this for other merge-y commands like cherry-pick and rebase.
Is there any way you can specify a directory in which you want only theirs, but manually resolve the conflicts otherwise? Ex: git merge --strategy-option theirs some/dir. So, if a conflict is in a file from path some/dir, then accept theirs, but if it is not, then ask me?
I am having fatal: No current branch.
k
kynan

Based on Jakub's answer you can configure the following git aliases for convenience:

accept-ours = "!f() { git checkout --ours -- \"${@:-.}\"; git add -u \"${@:-.}\"; }; f"
accept-theirs = "!f() { git checkout --theirs -- \"${@:-.}\"; git add -u \"${@:-.}\"; }; f"

They optionally take one or several paths of files to resolve and default to resolving everything under the current directory if none are given.

Add them to the [alias] section of your ~/.gitconfig or run

git config --global alias.accept-ours '!f() { git checkout --ours -- "${@:-.}"; git add -u "${@:-.}"; }; f'
git config --global alias.accept-theirs '!f() { git checkout --theirs -- "${@:-.}"; git add -u "${@:-.}"; }; f'

Not working for me... Are these for bash or some other shell?
These are git aliases, add them to the [alias] section in your ~.gitconfig or use git config --global accept-ours "...". Have edited my answer.
You have no idea how much time this alias saved me. Thumbs up!
@hakre Make sure you quote the alias, otherwise your shell will try to interpret it. Or just manually edit your ~/.gitconfig.
Shell syntax for default values: !f() { git checkout --ours -- "${@:-.}" git add -u "${@:-.}; }; f
D
Dar

Based on kynan's answer, here are the same aliases, modified so they can handle spaces and initial dashes in filenames:

accept-ours = "!f() { [ -z \"$@\" ] && set - '.'; git checkout --ours -- \"$@\"; git add -u -- \"$@\"; }; f"
accept-theirs = "!f() { [ -z \"$@\" ] && set - '.'; git checkout --theirs -- \"$@\"; git add -u -- \"$@\"; }; f"

Please, write the answer that are helpful for the other people.
P
Parakleta

The ideal situation for resolving conflicts is when you know ahead of time which way you want to resolve them and can pass the -Xours or -Xtheirs recursive merge strategy options. Outside of this I can see three scenarious:

You want to just keep a single version of the file (this should probably only be used on unmergeable binary files, since otherwise conflicted and non-conflicted files may get out of sync with each other). You want to simply decide all of the conflicts in a particular direction. You need to resolve some conflicts manually and then resolve all of the rest in a particular direction.

To address these three scenarios you can add the following lines to your .gitconfig file (or equivalent):

[merge]
  conflictstyle = diff3
[mergetool.getours]
  cmd = git-checkout --ours ${MERGED}
  trustExitCode = true
[mergetool.mergeours]
  cmd = git-merge-file --ours ${LOCAL} ${BASE} ${REMOTE} -p > ${MERGED}
  trustExitCode = true
[mergetool.keepours]
  cmd = sed -i '' -e '/^<<<<<<</d' -e '/^|||||||/,/^>>>>>>>/d' ${MERGED}
  trustExitCode = true
[mergetool.gettheirs]
  cmd = git-checkout --theirs ${MERGED}
  trustExitCode = true
[mergetool.mergetheirs]
  cmd = git-merge-file --theirs ${LOCAL} ${BASE} ${REMOTE} -p > ${MERGED}
  trustExitCode = true
[mergetool.keeptheirs]
  cmd = sed -i '' -e '/^<<<<<<</,/^=======/d' -e '/^>>>>>>>/d' ${MERGED}
  trustExitCode = true

The get(ours|theirs) tool just keeps the respective version of the file and throws away all of the changes from the other version (so no merging occurs).

The merge(ours|theirs) tool re-does the three way merge from the local, base, and remote versions of the file, choosing to resolve conflicts in the given direction. This has some caveats, specifically: it ignores the diff options that were passed to the merge command (such as algorithm and whitespace handling); does the merge cleanly from the original files (so any manual changes to the file are discarded, which could be good or bad); and has the advantage that it cannot be confused by diff markers that are supposed to be in the file.

The keep(ours|theirs) tool simply edits out the diff markers and enclosed sections, detecting them by regular expression. This has the advantage that it preserves the diff options from the merge command and allows you to resolve some conflicts by hand and then automatically resolve the rest. It has the disadvantage that if there are other conflict markers in the file it could get confused.

These are all used by running git mergetool -t (get|merge|keep)(ours|theirs) [<filename>] where if <filename> is not supplied it processes all conflicted files.

Generally speaking, assuming you know there are no diff markers to confuse the regular expression, the keep* variants of the command are the most powerful. If you leave the mergetool.keepBackup option unset or true then after the merge you can diff the *.orig file against the result of the merge to check that it makes sense. As an example, I run the following after the mergetool just to inspect the changes before committing:

for f in `find . -name '*.orig'`; do vimdiff $f ${f%.orig}; done

Note: If the merge.conflictstyle is not diff3 then the /^|||||||/ pattern in the sed rule needs to be /^=======/ instead.


What is sed -I ''? This does not exist on my system.
@simohe try sed -i '' instead. It just allows for in-place editing. I should have used lowercase anyway so I’ll update the post.