Git merge conflict to always take the newest file
Asked Answered
M

4

13

How can I make a git conflict ALWAYS get resolved by taking the newest file (latest changed timestamp), completley without a prompt?

I am building a syncscript with git backend, and I never want to actually merge a file, just override all older copies with the one that was edited/deleted/added last.

Edit: With newest file, I mean newest commit containing that file.

Mucin answered 30/9, 2011 at 7:16 Comment(8)
What do you mean by "newest" file? When you merge there is only one file with a timestamp. The other file(s) being merge only exist as blob objects in other commits; they are not files and don't have timestamps.Peng
You are right, I need to redifine: The file in the newest commit.Mucin
What do you mean by newest commit? If you are merging the commits will not necessarily be ordered relative to each other. Do want to compare author timestamps? committer timestamps? Do you trust the clocks of the machines where the different commits were made? If the two commits are made at exactly the same time, who wins?Peng
There's no generic way around this - git usually is pretty good at assuming how to resolve conflicts, and in cases it does not, it usually means that there is no way to figure it out (in the general case) without being a human being.Locket
@CharlesBailey: commit timestamp, no matter when in the tree it occurs. And yes, I will trust the clocks of the committing machines. If two commits are made at exactly the same time, just take one of them, doesnt matter. :)Mucin
@Romain: I know I will not have the exactly right code after the merge, I just want the latest. If git cannot resolve the merge, scrap it and take the copy of the one that did the latest commit.Mucin
@Lilleman: Are you sure that git is the right tool for what you want to do. I don't know the complete background but it feels like what you're trying to achieve is more suited to a tool such as rsync.Peng
@Romain: I have thought about rsync, but I want to maintain history, and I want it to be very simple and basic. git fulfills every need but this merge-problem.Mucin
E
2

Based on @Lilleman answer which is good but has a bug: if a file is modified on local master which is older than origin/master the expected behaviour is to pick origin/master but if you add a commit on top of local master it will pick the local (older version) of the file. git log needs to be executed with an extra argument which is the file path.

here a better version including variable branch names it's a bit hacky but it works

merge

#!/bin/sh
cd `findup .git`
PATH=$PATH:$( dirname "${BASH_SOURCE[0]}" )
HEAD=`git rev-parse --abbrev-ref HEAD`

echo "* merge=newest" > .gitattributes
sed -ie '/merge "newest"/,+2d' .git/config
echo "[merge \"newest\"]" >> .git/config
echo -e "\tname = Merge by newest commit" >> .git/config
echo -e "\tdriver = git-merge-newest %O %A %B %L %P $HEAD $1" >> .git/config
git merge $1
sed -ie '/merge "newest"/,+2d' .git/config
rm .gitattributes
cd - 

git-merge-newest

#!/bin/sh
if git merge-file -p -q "$2" "$1" "$3" > /dev/null;
        then git merge-file "$2" "$1" "$3";
        else
                MINE=$(git log --format="%ct" --no-merges "$6" -1 $5);
                THEIRS=$(git log --format="%ct" --no-merges "$7" -1 $5);
                if [ $MINE -gt $THEIRS ]; then
                  git merge-file -q --ours "$2" "$1" "$3" >/dev/null
                else
                  git merge-file -q --theirs "$2" "$1" "$3">/dev/null
                fi
fi

findup used

#!/bin/sh

pwd="`pwd`"
start="$pwd"
while [ ! "$pwd" -ef .. ]; do
[ -e "$1" ] && echo -n "$pwd" && exit
cd .. || exit 1
pwd="`pwd`"
done
exit 1
Ebonee answered 21/4, 2020 at 16:56 Comment(0)
M
7

I came up this little merge driver that does what I want. For my purpose it is hard coded to the "master" branch and to the "origin" remote. I do not know how to make these parts dynamic.

#!/usr/bin/env sh
if git merge-file -p -q "$2" "$1" "$3" > /dev/null;
        then git merge-file "$2" "$1" "$3";
        else
                MINE=$(git log --format="%ct" --no-merges master -1);
                THEIRS=$(git log --format="%ct" --no-merges origin/master -1);
                if [ $MINE -gt $THEIRS ];
                        then git merge-file -q --ours "$2" "$1" "$3";
                        else git merge-file -q --theirs "$2" "$1" "$3";
                fi
fi

In short I look for the last commit with git-log that was not a merge, formatted as UNIX timestamp, then I compare them and run a custom git-merge with eiter ours or their version.

As a little bonus, it first makes a check to see if it is possible to merge the file without conflict. If that is possible, it merges both files.

Mucin answered 29/4, 2012 at 22:44 Comment(4)
Could you add the changes you made to .git/config to define this as a custom merge driver?Forked
This is what I use to make it work for me (run in a shell in your repository path): echo "* merge=newest" > .gitattributes; echo -e ".gitattributes\n.gitignore" > .gitignore; echo "[merge \"newest\"]" >> .git/config; echo -e "\tname = Merge by newest commit" >> .git/config; echo -e "\tdriver = git-merge-newest %O %A %B" >> .git/config;Mucin
this solution is inaccurate it takes always the latest commit of the branch to compare the latest modified instead of the file causing losing information here a better one that works everywhere.gist.github.com/marcobazzani/306e3589f918575d20d821522a6fef77Ebonee
@Ebonee If you could provide this solution as a complete answer to this question with instructions on how to get it running, I'd happy to try it out and if all works out well change the accepted anser.Mucin
D
3

I think you will have to write your own merge-driver for this. See "git help gitattributes", the "Defining a custom merge driver" sections for details on how to do just that.

Demobilize answered 30/9, 2011 at 8:59 Comment(1)
Thats a poke in the right direction. Thanks, trying to find the answer with google and man pages oncustom merge drivers. :)Mucin
E
2

Based on @Lilleman answer which is good but has a bug: if a file is modified on local master which is older than origin/master the expected behaviour is to pick origin/master but if you add a commit on top of local master it will pick the local (older version) of the file. git log needs to be executed with an extra argument which is the file path.

here a better version including variable branch names it's a bit hacky but it works

merge

#!/bin/sh
cd `findup .git`
PATH=$PATH:$( dirname "${BASH_SOURCE[0]}" )
HEAD=`git rev-parse --abbrev-ref HEAD`

echo "* merge=newest" > .gitattributes
sed -ie '/merge "newest"/,+2d' .git/config
echo "[merge \"newest\"]" >> .git/config
echo -e "\tname = Merge by newest commit" >> .git/config
echo -e "\tdriver = git-merge-newest %O %A %B %L %P $HEAD $1" >> .git/config
git merge $1
sed -ie '/merge "newest"/,+2d' .git/config
rm .gitattributes
cd - 

git-merge-newest

#!/bin/sh
if git merge-file -p -q "$2" "$1" "$3" > /dev/null;
        then git merge-file "$2" "$1" "$3";
        else
                MINE=$(git log --format="%ct" --no-merges "$6" -1 $5);
                THEIRS=$(git log --format="%ct" --no-merges "$7" -1 $5);
                if [ $MINE -gt $THEIRS ]; then
                  git merge-file -q --ours "$2" "$1" "$3" >/dev/null
                else
                  git merge-file -q --theirs "$2" "$1" "$3">/dev/null
                fi
fi

findup used

#!/bin/sh

pwd="`pwd`"
start="$pwd"
while [ ! "$pwd" -ef .. ]; do
[ -e "$1" ] && echo -n "$pwd" && exit
cd .. || exit 1
pwd="`pwd`"
done
exit 1
Ebonee answered 21/4, 2020 at 16:56 Comment(0)
Y
1

If anyone came here searching for a solution to avoid resolve conflicts arose from automatically generated files such as package-lock.json, pubspec.lock, *.pbxproj, then you can create a .gitattributes file defining a merge strategy

package-lock.json merge=union

See more info on git attributes

Yacano answered 3/12, 2020 at 8:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.