Branches are pointers
Each commit is a saved snapshot of your project that points back to its parent. A branch is just a name that points at one commit. Here the branch main points at commit B, the newest.
HEAD is Git's marker for the branch you are on. When you commit, the branch that HEAD is attached to moves forward to the new commit. Every other branch stays where it is.
git switch -c feature makes a new branch at the commit you are on and switches to it. No files are copied: feature is one more pointer to B, and HEAD now rides on feature.
You edit and commit. The new commit C points back to B, and only feature moves forward to C. main still points at B, so the working version stays safe while you experiment.
Merging
Done? git switch main, then git merge feature. main is simply behind on the same line, so Git slides main forward to C. This is a fast-forward: no new commit is made.
Work goes on in both places. You commit D on feature, then switch to main and commit a quick fix E. Now each branch has a commit the other lacks, so a simple slide is impossible.
git merge feature tries to combine D and E. Both changed the same line of app.py, so Git cannot choose. It stops and marks the clash: main's side (HEAD) above =======, feature's side below.
You edit app.py to the line you want and delete the markers, then git add and git commit. That makes the merge commit M with two parents, E and D, and main moves to M.
The other way to combine work: git rebase main replays D on top of E as a new commit D′. History becomes one straight line with no merge commit. Never rebase commits you have already shared.
Sharing
Back to the merge. A remote is a shared copy of the repository on a server, usually named origin. git push uploads your new commits and moves main on the server to M too.
A teammate has pushed commit F. git pull downloads it and merges it into your branch. Your main was simply behind, so this is a fast-forward too: main slides to F.
New work: on feature you merge main in (a fast-forward), commit G and push the branch. Then you open a pull request: a page on a site such as GitHub where teammates review G before it is merged into main.
Try it in a terminal
;; step 1/12 · A branch is a pointer $ git branch * main $ cat .git/refs/heads/main # a branch is a tiny file 7b9e41d... # the id of commit B
Commit ids in the examples (such as 7b9e41d) are made up and shortened. Real ids are 40-character hashes, so yours will differ. A rebased commit gets a new id because its parent changed. If D and E clash, rebase stops with the same conflict, and you resolve it, git add, then git rebase --continue.