Avoiding merge conflicts when merging master into per-host customized branch
Asked Answered
I

1

8

I want to store all my dotfiles in git repository with separate branch for each machine. I have a problem I can't solve that blocks me from using git for that purpose. I'd like to know how other people solved it.

I'm going to have a single master branch which contains only template definitions and definitions which are supposed to be common for all branches, for example:

export EMAIL="@EMAIL@"
export SURFRAW_google_results=1000

I will replace @EMAIL@ with my correct e-mail on machine branches and commit it but SURFRAW_google_results will stay the same. For example, on work branch I'll have this:

export EMAIL="[email protected]"
export SURFRAW_google_results=1000

Now, I decided to change SURFRAW_google_results=1000 to 10. It's supposed to be shared globally, so I first I change it on master:

export EMAIL="@EMAIL@"
export SURFRAW_google_results=10

and then on I rebase work onto master:

$ git checkout work
$ git rebase master

And now I get conflict because the line that is above the line I changed is different:

<<<<<<< a60114eea1c98d2354a07bd4fd0cdd63cba62e93
export EMAIL="@EMAIL@"
export SURFRAW_google_results=10
=======
export EMAIL="[email protected]"
export SURFRAW_google_results=1000
>>>>>>> m

In case of bash I could quickly get away with including a machine-specific part by sourcing mechanism but how about configs that do not support including/sourcing other configs such as .fluxbox/keys or .screenrc?

Islet answered 26/9, 2016 at 17:16 Comment(6)
If I understand your problem correctly you don't want to resolve the conflict but you want to prevent it in the future when the same file will change and you will want to rebase again?Kincardine
Yes, I'm looking for a way to prevent such conflicts. I read many articles about people storing machine-specific parts of configs on separate branches but nobody described how to deal with conflicts.Islet
I don't think you can prevent conflicts in your scenario, however you can record the desired resolution (possibly a different one for each machine-branch) so that in the future you don't need to resolve these conflicts anymore. I've never had to use this myself, but see if this can solve your problem: git-scm.herokuapp.com/blog/2010/03/08/rerere.html.Kincardine
Hmm... ok, thx. You said in your scenario - what is then preferred way of handling such issues in dotfiles?Islet
With "in your scenario" I meant both the issues and the desired way of avoiding conflict resolution. Normally one would simply resolve the conflict when a change like that happens on the master branch. If you want to repeat the resolution every time a conflict like that happens you would use rerere to record the desired resolution.Kincardine
Ok, I see. I also considered leaving @EMAIL@-like placeholders on machine branches and replacing these values with correct values with Makefile or a simple script and not committing them. But this has a disadvantage of making my repository status dirty every time I run this script. Really, nobody has come across such problems when setting up their dotfiles repository? It's all about branches and easy synchronization.Islet
J
9

Instead of modifying concurrent data directly between branches (which results in conflict as illustrated in this answer), you can considering content filter driver, using .gitattributes declaration.

smudge (image from "Customizing Git - Git Attributes", from "Pro Git book")

The idea is to manage those dotfiles in version, but with template placeholders in place of the actual values (which depends on the branch).
The generated actual dotfiles remains ignored (by the .gitignore).
That means your actual working tree does not get "dirty".

The files with the actual values can also be versioned, but with different names (so no conflicts when merging/rebasing)

The smudge script select the correct value file depending on the branch name, and generate the correct dotfile based on the dotfile template the smudge script is applied on during a git checkout.

To have a smudge acting differently per branch, I would recommend calling a script which starts by looking the name of the current branch.
See an example in my older answer "Best practice - Git + Build automation - Keeping configs separate".

#!/bin/sh
branch=$(git rev-parse --symbolic --abbrev-ref HEAD)

The OP mentions that, for the smudge script to apply on checkout, removing the index is needed. That is true, as I explained here:

the index is the middle-man for moving things from your work-tree to the object-store AND for moving things from the object-store to your work-tree.

By removing the index, you force git to restore it, which means applying any content filter driver if present.

See "git: re-checkout files after creating smudge filter".

Jeraldinejeralee answered 29/9, 2016 at 6:19 Comment(6)
I tried smudge/clean filters - they would be great because they: 1. do not make working tree dirty 2. prevent merge conflicts 3. can be run automatically. But the problem is that they don't run when file is the same on both branches. For example, if I have a template .bashrc filled with @EMAIL@ on my master branch and I switch to work that has the same template .bashrc and additionally has a .conf file that contains real definitions of all placeholders such as EMAIL then smudge filter is not run. I didn't find a way to force it.Islet
@Islet I have always been able to force a smudge filter to run again with a global git checkout. See as an example github.com/VonC/compileEverything/blob/… (March 2013), when I wanted to activate my filter 'shebang' (github.com/VonC/compileEverything/blob/…: github.com/VonC/compileEverything/blob/…): normally, a git checkout HEAD -- . is enough.Jeraldinejeralee
Unfortunately none of the methods I tried works with git 2.9.0 and with git compiled from its current HEAD if files specified in .gitattributes do not change - filters are not run. Does it really work for you?Islet
Yes, since 2013. I will retest and double checkJeraldinejeralee
This alone doesn't work: git checkout HEAD -- "$(git rev-parse --show-toplevel)" but this does: rm .git/index && git checkout HEAD -- "$(git rev-parse --show-toplevel)" but I wonder what disadvantages (apart from performance downgrade) it might have and whether in work in 10 years from now.Islet
@Islet You are correct, and I have edited the answer to make that clearer (and added some links to explain it further). That will indeed work (I cannot speak as for "10 years for now", but unless Git changes radically, you are safe)Jeraldinejeralee

© 2022 - 2024 — McMap. All rights reserved.