Git isn't really about branches. It's really about commits. Branches—or branch names, to be precise—are just a way to find commits. It's the commits that matter.
If you form the question as: Can two different commits contain files that have the same name, yet have different contents? the answer is clear: yes, they can.
Once you ask the obvious follow-up question: Given two different commits, in which file F has different content, when I merge these commits, what happens to the content of F? you see where the problem begins to come in.
In Git, merging is about combining work. Because each commit represents a snapshot of files, Git needs a third commit here. This third commit is some shared, common commit that is on both branches. Remember, in Git, a commit is often on many branches at the same time. The phrase on a branch just means that by using the branch name—the tip commit of the branch—and working backwards, we get to the commit in question.
Hence, if we have a string of commits that diverges:
I--J <-- branch1
/
...--G--H
\
K--L <-- branch2
we can consider combining work by starting with the shared common commit H
, which holds a snapshot of all files. We compare this snapshot in H
to the snapshot in J
. If file F has changed in going from H
to J
, that's a change. Next, we compare the snapshot in H
to that in L
. If file F has changed here, that's a change. Git will now combine the changes.
If the changes are "configure the system to act one way" and "configure the system to act some other way", these changes may conflict. Whether, and how, they conflict depends on the way the configuration works. Git only understands lines. If line 47 of file F in commit H
is changed in both J
and L
, and changed differently, that's a conflict. If only commit J
has a change to line 47, Git thinks it must be OK to take that change from J
.
I want to keep unmodified config files on every branch all the time. does anybody know how can I achieve this?
The usual answer is: don't even try. These files should not be branch-dependent, because when you have this:
...--G--H <-- master, dev
the files in commit H
are the same, regardless of whether you are using commit H
because of the name master
, or because of the name dev
. Both branch names identify the same commit H
.
Instead, make these configuration files untracked (and probably ignored as well). That means Git never pays any attention to them. They won't change when you switch from one commit to another, nor when you switch from one branch to another even if that means switching commits too.
Do the credential-and-database-based stuff in different work-trees. Each Git repository comes with a work-tree, so you can just use two different clones of the same base repository to get two different work-trees easily.
If your Git is at least 2.5 (and preferably at least 2.15 because of a fairly big bug that was not fixed until then), you can use one Git repository with git worktree add
to add a second work-tree to the one repository.
If you really insist on doing this all in one repository, and modifying files based on which branch name you gave to a recent git checkout
or git switch
command, consider using a post-checkout hook. You can write any code you like in such a hook, and you can read the name of the branch that was used here. Doing this is painful and full of pitfalls: the separate work-tree method is far superior, provided you can afford the disk space for it.