Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
673 views
in Technique[技术] by (71.8m points)

git merge - strategy for git and append-mostly files

I have some files in my repository that are bottom-growing: most of the changes involve adding new lines at the bottom of the file. This is mostly language and other property files.

As an annoying side effect, whenever two people make additions at the same time I get merge conflicts, and the resolution always involves manually copy-pasting so that lines from both versions get included.

Is there a tip, trick or methodology that will relieve some of the pain of this process?

For example, a simplistic solution would be to tell the developers to add new lines at random places in the middle of the file. This will probably work, but it involved a conscious effort, and a weird-looking history.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You could use the gitattributes mechanism to define a custom merge driver (like this one for instance) in order to copy automatically the relevant sections.

[merge "aggregate"]
        name = agregate both new sections
        driver = aggregate.sh %O %A %B

It will be a 3-way merge, which means you can easily diff %A and %B against %O (common ancestor) in order to isolate said new sections, and aggregate them in the result merged file.

That aggregate merge driver needs only to do:

comm -13 $1 $3 >> $2

(The comm utility is part of the GoW -- Gnu on Windows -- distribution, if you are on Windows)


Here is a little demo:

First, let's set up a Git repo, with a file modified in two branches ('master' and 'abranch'):

C:proggitests>mkdir agg
C:proggitests>cd agg
C:proggitestsagg>git init r1
Initialized empty Git repository in C:/prog/git/tests/agg/r1/.git/
C:proggitestsagg>cd r1

# Who am I?
C:proggitestsagg
1>git config user.name VonC
C:proggitestsagg
1>git config user.email vonc@xxx

# one file, first commit:
C:proggitestsagg
1>echo test > test.txt
C:proggitestsagg
1>git add .
C:proggitestsagg
1>git commit -m "first commit"
[master c34668d] first commit
 1 file changed, 1 insertion(+)
 create mode 100644 test.txt

# Let's add one more common line:
C:proggitestsagg
1>echo base >> test.txt
C:proggitestsagg
1>more test.txt
test
base
C:proggitestsagg
1>git add .
C:proggitestsagg
1>git commit -m "base"
[master d1cde8d] base
 1 file changed, 1 insertion(+)

Now we create a new branch, and make concurrent modifications in both versions of that file, at the end of it like the OP itsadok specifies in the question.

C:proggitestsagg
1>git checkout -b abranch
Switched to a new branch 'abranch'
C:proggitestsagg
1>echo "modif from abranch" >> test.txt
C:proggitestsagg
1>git add .
C:proggitestsagg
1>git commit -m "abranch contrib"
[abranch a4d2632] abranch contrib
 1 file changed, 1 insertion(+)
C:proggitestsagg
1>type test.txt
test
base
"modif from abranch"

# back to master
C:proggitestsagg
1>git checkout master
Switched to branch 'master'
C:proggitestsagg
1>echo "contrib from master" >> test.txt
C:proggitestsagg
1>git add .
C:proggitestsagg
1>git commit -m "contrib from master"
[master 45bec4d] contrib from master
 1 file changed, 1 insertion(+)
C:proggitestsagg
1>type test.txt
test
base
"contrib from master"

We have out two branches (note: git lg is an alias of mine)

C:proggitestsagg
1>git lg
* 45bec4d - (HEAD, master) contrib from master (86 minutes ago) VonC
| * a4d2632 - (abranch) abranch contrib (86 minutes ago) VonC
|/
* d1cde8d - base (87 minutes ago) VonC
* c34668d - first commit (89 minutes ago) VonC

Now let's try a merge:

C:proggitestsagg
1>git merge abranch
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Automatic merge failed; fix conflicts and then commit the result.

C:proggitestsagg
1>more test.txt
test
base
<<<<<<< HEAD
"contrib from master"
=======
"modif from abranch"
>>>>>>> abranch

... Failed as advertised ;) A git merge --abort will reset the situation.

Let's put in place our merge driver:

C:proggitestsagg
1>git config merge.aggregate.name "aggregate both new sections"
C:proggitestsagg
1>git config merge.aggregate.driver "aggregate.sh %O %A %B"
C:proggitestsagg
1>echo test.txt merge=aggregate > .gitattributes

At this point, a merge still fails:

C:proggitestsagg
1>git merge abranch
aggregate.sh .merge_file_a09308 .merge_file_b09308 .merge_file_c09308: aggregate.sh: command not found
fatal: Failed to execute internal merge

Normal: we need to write that script, and add it to the PATH:

vim aggregate.sh:
#!/bin/bash

# echo O: $1
# echo A: $2
# echo B: $3

# After http://serverfault.com/q/68684/783
# How can I get diff to show only added and deleted lines?
# On Windows, install GoW (https://github.com/bmatzelle/gow/wiki/)
ob=$(comm -13 $1 $3)
# echo "ob: ${ob}"

echo ${ob} >> $2

----

C:proggitestsagg
1>set PATH=%PATH%;C:proggitestsagg
1

And now, the aggregate merge driver can operate:

C:proggitestsagg
1>git merge --no-commit abranch
Auto-merging test.txt
Automatic merge went well; stopped before committing as requested

C:proggitestsagg
1>type test.txt
test
base
"contrib from master"
"modif from abranch"

Here you go: the end of the test.txt file from abranch has been added to the file on master.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...