... It is said the branch is just a pointer to that commit O's hash not a record of history. Is it true?
That is correct. History, in Git, is the set of commits. A branch name simply selects one commit to be the end of the history for that branch. Running git checkout branch-name
selects the given branch-name as the current branch, and hence the commit to which the name points as the current commit. This extracts the selected commit into Git's index (from which Git will make the next commit) and into your work-tree (where you can see and work on your files).
Running git commit
takes whatever is in Git's index at that point, adds the appropriate metadata including the current commit's hash ID, writes out a new commit—which acquires its unique hash ID in the process—and then writes the new commit's hash ID into the current branch name, thereby making the name point to the new commit.
No commit—no internal Git object of any form, really—can ever be changed, not one single bit, once it's made. The reason for this is that the hash ID is a simple cryptographic checksum of the object's content. Change the content and you change the hash ID: you have a new, different object; the existing object remains unchanged.
Since commit L
(whatever its real hash ID is) already exists, its content is now frozen for all time. That includes the fact that it has just one parent, commit K
. You can make a new merge commit—a commit with two (or more) parents—whose parents are both I
and whatever you like—say O
, for instance—and make your branch-name PostgreSQL
point to that new commit:
K--L--M--N--O <-- master (using MySQL)
/ \
...--F--G--H \
\ \
I---------------P <-- PostgreSQL
You would obtain this state by running:
git checkout PostgreSQL; git merge master
possibly with some additional options, and possibly with some steps of resolving conflicts.
The snapshot (i.e., data contained in all the files) stored in commit P
can be anything you like, although one is in general encouraged to let Git build the snapshot on its own. Git does this by combining work done (changes made) since the merge base, commit H
in this case, by diff-ing the snapshot in H
vs that in I
to see what you changed, diff-ing the snapshot in H
vs that in O
to see what they changed, and combining the two sets of changes.
Because the snapshot in O
contains changes that were introduced into the snapshot in K
, the diff from H
to O
will include the changes you'd see by comparing H
to K
. So the result of this combining will include changes you did not want.
Again, the snapshot in P
can be whatever you like: you can tell Git not to make commit P
yet, and then make further changes to the copies of your files that Git has stored in Git's index.1 That is, you could run git merge --no-commit
: Git would do its best to combine changes as usual, and perhaps even succeed on its own. But you would then take the resulting files—as currently stored in both Git's index and your work-tree—and change them to, in effect, "undo" commit K
. Since you can only edit the work-tree copy, you must then git add
the updated file(s) as usual, after which:
git merge --continue
(or git commit
) will commit the result. This produces what is sometimes called an evil merge.
Instead of an evil merge, you can commit a normal (non-evil? good?) merge and immediately run:
git revert <hash-of-K> # or master~4, if I counted correctly
to make a new, additional commit that has the effect of undoing the changes from commit K, producing:
K--L--M--N--O <-- master (using MySQL)
/ \
...--F--G--H \
\ \
I---------------P--Q <-- PostgreSQL
If you diff the snapshot in P
against the snapshot in Q
, the changes you see will be the opposite of the changes you'd see by diffing the snapshot in H
against that in K
. So commit Q
would match P
except for undoing (reverting) K
.
As discussed in comments, storing configuration information separately, rather than in committed files, is probably the way to go instead; but the above is one way of achieving the result you want. The differences between each of the various possible approaches show up, not today, but at some point in the future, when you'd like the two branches to be less different, or not even to have two separate branches at all.
1Technically these are not separate copies, but the point here is that commit P
will be made from whatever is in Git's index. If you use git merge --no-commit
, then modify work-tree files, you must also run git add
on those modified work-tree files, to update the index.