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="user@corp.com"
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="user@corp.com"
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?

user1042840
  • 1,925
  • 2
  • 16
  • 32
  • 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? – Ma3x Sep 26 '16 at 17:22
  • 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. – user1042840 Sep 26 '16 at 17:26
  • 1
    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: https://git-scm.herokuapp.com/blog/2010/03/08/rerere.html. – Ma3x Sep 26 '16 at 17:31
  • Hmm... ok, thx. You said `in your scenario` - what is then preferred way of handling such issues in dotfiles? – user1042840 Sep 27 '16 at 19:39
  • 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. – Ma3x Sep 27 '16 at 19:54
  • 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. – user1042840 Sep 27 '16 at 21:33

1 Answers1

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".

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 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. – user1042840 Sep 29 '16 at 07:32
  • @user1042840 I have always been able to force a smudge filter to run again with a global `git checkout`. See as an example https://github.com/VonC/compileEverything/blob/3fb31b840b6d03e28125ffccb69122c07cd63837/gitolite/install_or_update_gitolite.sh#L16-L21 (March 2013), when I wanted to activate my filter 'shebang' (https://github.com/VonC/compileEverything/blob/3fb31b840b6d03e28125ffccb69122c07cd63837/gitolite/attributes: https://github.com/VonC/compileEverything/blob/3fb31b840b6d03e28125ffccb69122c07cd63837/gitolite/shebang_replace): normally, a `git checkout HEAD -- .` is enough. – VonC Sep 29 '16 at 07:57
  • 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? – user1042840 Sep 29 '16 at 17:47
  • Yes, since 2013. I will retest and double check – VonC Sep 29 '16 at 17:58
  • 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. – user1042840 Sep 29 '16 at 20:12
  • @user1042840 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) – VonC Sep 29 '16 at 20:30