Interactively merge files tracked with git and unt

2020-08-12 04:25发布

I use a couple of software packages (like gitlab) that you install by cloning from their git repo. They typically come with some config.example (under version control), which you copy to your own config file (not under version control or even ignored in .gitignore) and adapt to your needs.

When the upstream package is updated and for example changes the config file options that will obviously only be reflected in config.example.

Is there a chain of git commands that i'm missing that can help me compare the changes of config.example to the new one in upstream/HEAD and maybe even merge them interactively into my local config file?

Would be awesome if i could get something like the interactive patch mode in git add/commit --interactive.

7条回答
姐就是有狂的资本
2楼-- · 2020-08-12 04:36

To diff just config.example in your local repo with the corresponding file in upstream/HEAD you can run:

git diff upstream/HEAD config.example

Unfortunately I don't know of a way to make git directly apply the changes to a file that git doesn't track.

查看更多
Luminary・发光体
3楼-- · 2020-08-12 04:39

There is a tool called sdiff that might do what you want.

Invoke it (in your case) with sdiff -o config config.example config

查看更多
Anthone
4楼-- · 2020-08-12 04:40

git checkout --patch selects diff hunks, simplest here might be to put your content at the upstream path, do that, and clean up after:

cp config  config.example
git checkout -p upstream   config.example
mv config.example  config
git checkout @  config.example

that will get you the two-diff hunk selection from git add --patch.

查看更多
▲ chillily
5楼-- · 2020-08-12 04:41
  • You can use vim as a merge tool with vimdiff.
  • Emacs can do this as well with ediff-mode.
查看更多
孤傲高冷的网名
6楼-- · 2020-08-12 04:47

You could probably approach this in many ways. I could think of two: git-merge-file and good old patch. The git-merge-file method offers some interactivity, whereas the patch method offers none.

Solution with git-merge-file

Lets say you have an original file config.example which you use to create a local unversioned file, config.local. Now when upstream updates config.example, you can follow something like the following steps to merge any new changes.

$ git fetch
$ git show master:config.example > config.example.base
$ git show origin/master:config.example > config.example.latest
$ git merge-file config.local config.example.base config.example.latest

This will update config.local with the usual conflict markers, which you will then have to resolve with your favourite merge tool (ediff in Emacs is nice, I'm sure there are similar modes for Vim). For example, the following three files,

config.example.base:

Original: 
          some config

config.example.latest:

Original: 
          some config

Upstream:
          new upstream config

config.local:

Original: 
          some config

My changes:
          some other config

will be merged like this:

Original: 
          some config

<<<<<<< config.local
My changes:
          some other config
=======
Upstream:
          new upstream config
>>>>>>> config.example.latest

You could probably script this without much effort. Btw, git-merge-file can operate on any 3 files, they need not be version controlled under git. That means one could use it to merge any three files!

Solution with patch:

Assuming the same file names, config.local and config.example, the following should work.

$ git fetch
$ git diff master..origin/master -- config.example | sed -e 's%\(^[-+]\{3\}\) .\+%\1 config.local%g' > /tmp/mypatch
$ patch < /tmp/mypatch
查看更多
戒情不戒烟
7楼-- · 2020-08-12 04:47

If you want the full git merge, you can get git to do it with arbitrary content by setting up an index entry as git read-tree does and then invoking git's normal merge driver on just that entry. Git refers to the different content versions as "stages"; they are 1: the original, 2: yours, 3: theirs. The merge compares the changes from 1 to 2 and from 1 to 3 and does its thing. To set it up, use git update-index:

orig_example=        # fill in the commit with the config.example you based yours on
new_upstream=        # fill in the name of the upstream branch

( while read; do printf "%s %s %s\t%s\n" $REPLY; done \
| git update-index --index-info ) <<EOD
100644 $(git rev-parse $orig_example:config.example)  1 config
100644 $(git hash-object -w config)                   2 config
100644 $(git rev-parse $new_upstream:config.example)  3 config
EOD

and you've staged a merge of custom content for that path. Now do it:

git merge-index git-merge-one-file -- config

and it'll either automerge or leave the usual conflict droppings, fix it up as you like and git rm --cached --ignore-unmatch (or keep) the index entry if you want.

The pathname you put in the index (the "config" in all three entries here), by the way, doesn't have to already exist or have anything to do with anything. You could name it "wip" or "deleteme" or anything you want. The merge is of the content id'd in the index entry.

I think that's likely to do what you want here. If you really do want to pick and choose from the upstream changes you can put your own content at config.example and do git checkout -p upstream -- config.example, that does the inverse of git add -p, then put things back the way they were.

查看更多
登录 后发表回答