12

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.

Lilleman
  • 7,392
  • 5
  • 27
  • 36
  • 1
    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. – CB Bailey Sep 30 '11 at 07:19
  • 1
    You are right, I need to redifine: The file in the newest commit. – Lilleman Sep 30 '11 at 08:37
  • 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? – CB Bailey Sep 30 '11 at 08:40
  • 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. – Romain Sep 30 '11 at 08:45
  • 1
    @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. :) – Lilleman Sep 30 '11 at 08:48
  • @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. – Lilleman Sep 30 '11 at 08:49
  • @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`. – CB Bailey Sep 30 '11 at 09:27
  • @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. – Lilleman Sep 30 '11 at 09:53

4 Answers4

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.

Lilleman
  • 7,392
  • 5
  • 27
  • 36
  • Could you add the changes you made to `.git/config` to define this as a custom merge driver? – kgutwin Apr 17 '14 at 17:11
  • 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; – Lilleman Apr 18 '14 at 13:33
  • 1
    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.https://gist.github.com/marcobazzani/306e3589f918575d20d821522a6fef77 – marcobazzani Apr 10 '20 at 22:32
  • 1
    @marcobazzani 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. – Lilleman Apr 13 '20 at 15:32
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.

kusma
  • 6,516
  • 2
  • 22
  • 26
  • Thats a poke in the right direction. Thanks, trying to find the answer with google and man pages oncustom merge drivers. :) – Lilleman Sep 30 '11 at 09:16
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
marcobazzani
  • 373
  • 4
  • 16
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

ruwan800
  • 1,567
  • 17
  • 21