Git key concept

Dilip Kumar
7 min readJan 13, 2025

--

Git Branching

Imagine a timeline (like a straight line) representing the main branch. Each commit is a point on this line.

main:    A --- B --- C

Now, you create a new branch called feature from commit B. The timeline splits into two parallel lines:

$ git checkout -b feature
main:    A --- B --- C
\
feature: D --- E

Git Remote vs local branch

Local Branches are branches that exist only in your local repository. They are created, modified, and managed on your machine. Local branches can have any name you choose (e.g., feature/login, bugfix/issue-123). By convention, the default branch is often named main.

$ git branch feature/login  # Create a local branch
$ git checkout feature/login # Switch to the local branch

Remote branches are references to the state of branches in a remote repository (e.g., on GitHub, GitLab, or Bitbucket). They allow you to collaborate with others by tracking changes made in the remote repository. It is used to synchronize work between local and remote repositories. Remote branches are prefixed with the name of the remote (usually origin). Example: origin/main, origin/feature/login.

$ git fetch origin  # Fetch remote branches
$ git checkout origin/feature/login # Check out a remote branch (detached HEAD state)
$ git push -u origin <local-branch-name> # Push a local branch to the remote
$ git push origin --delete <remote-branch-name> # Delete a remote branch

Relationship Between Local and Remote Branches: A local branch can track a remote branch. This means the local branch is linked to a specific remote branch, and Git will keep them in sync. Example: A local branch feature/login can track origin/feature/login.

  • By default, when you clone a repository, Git creates a local branch (e.g., main) that tracks the corresponding remote branch (origin/main).
  • When you create a new branch locally and push it to the remote, Git automatically sets up tracking.

HEAD in Git

HEAD is a special pointer that represents your current position in the commit history. It essentially points to the latest commit in the branch you are currently working on. HEAD is a symbolic reference to the current branch. For example, if you are on the main branch, HEAD points to refs/heads/main.

Detached HEAD: If HEAD points directly to a commit (instead of a branch), you are in a detached HEAD state. This happens when you check out a specific commit (e.g., git checkout <commit-hash>).

Moving HEAD: When you make a new commit, HEAD (and the current branch) moves forward to point to the new commit.

Before Commit: HEAD -> main -> Commit C
After Commit: HEAD -> main -> Commit D

Resetting HEAD: You can move HEAD to a previous commit using git reset.

$ git reset --hard HEAD~1  # Move HEAD back by 1 commit

Git Merging

After working on the feature branch, you want to merge it back into main. The result is a new commit (F) that combines the changes from both branches.

$ git checkout main
$ git merge feature # The branch you want to merge into main branch here.
main:    A --- B --- C ------- F
\ /
feature: D --- E ----/
  • F is the merge commit that combines C (from main) and E (from feature).
  • --squash: This option squashes all the commits from the branch being merged into a single new commit on the target branch. This simplifies the history but loses the individual commits from the feature branch.

Git Rebasing

Instead of merging, you can rebase feature onto main. This moves the feature branch to start from C instead of B.

Before rebase:

main:    A --- B --- C
\
feature: D --- E

After rebase:

main:    A --- B --- C
\
feature: D' --- E'
$ git checkout feature
$ git rebase main # The branch you want to rebase onto. Think of this as the branch you want to "base" your current branch on.

Fast-Forward Merge

If main hasn’t moved forward since feature was created, Git can perform a fast-forward merge. This simply moves the main pointer to the latest commit on feature.

Before merge:

main:    A --- B
\
feature: D --- E

After merge:

main:    A --- B --- D --- E

Cherry-picking

Cherry-picking in Git is a powerful feature that allows you to apply a specific commit from one branch to another. Instead of merging an entire branch, you can selectively pick individual commits and apply them to your current branch. This is useful when you want to include specific changes without bringing in all the commits from another branch.

How Cherry-Picking Works

  1. Identify the commit: Find the commit hash (e.g., a1b2c3d) you want to apply.
  2. Apply the commit: Use the git cherry-pick command to apply that commit to your current branch.

Example Scenario

Imagine you have two branches:

  • main: The main branch.
  • feature: A feature branch with multiple commits.
main:      A --- B --- C
\
feature: D --- E --- F
  • Commit D adds a new feature.
  • Commit E fixes a bug.
  • Commit F adds another feature.

You only want to apply the bug fix (E) from the feature branch to main.

Steps to Cherry-Pick

Switch to the main branch:

git checkout main

Cherry-pick commit E:

git cherry-pick E

After cherry-picking, the commit history will look like this:

main:      A --- B --- C --- E'
  • E' is a new commit with the same changes as E, but it has a different commit hash because it was applied to main.

Visualization

Before cherry-picking:

main:      A --- B --- C
\
feature: D --- E --- F

After cherry-picking commit E:

main:      A --- B --- C --- E'
\
feature: D --- E --- F

Cherry-picking is a flexible tool, but it should be used carefully to avoid duplicating commits or creating confusion in the commit history.

Git commit messages

The most common standard pattern for Git commit messages is the Conventional Commits specification. While not strictly enforced by Git itself, it’s a widely adopted convention that brings consistency and clarity to commit history.

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

<type> (Required):

Describes the kind of change introduced by the commit. It must be in lower case. Common types include:

  • feat: A new feature.
  • fix: A bug fix.
  • docs: Documentation-only changes.
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.).
  • refactor: A code change that neither fixes a bug nor adds a feature.
  • perf: A code change that improves performance.
  • test: Adding missing tests or correcting existing tests.
  • build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm).
  • ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs).
  • chore: Other changes that don't modify src or test files.

[optional scope] (Optional):

  • Provides context about the part of the project affected by the change.
  • Enclosed in parentheses ().
  • Examples: (parser), (login-module), (user-profile).
  • Use what makes sense for your project. Common options include module names, components, filenames, or general areas of the codebase.

<description> (Required):

  • A concise summary of the changes made.
  • Start with a capital letter.
  • Use the imperative, present tense: “Add feature” not “Added feature” nor “Adds feature.”
  • Keep it short (generally under 50 characters).

[optional body] (Optional):

  • Provides more detailed information about the changes.
  • Separated from the subject line by a blank line.
  • Use the imperative, present tense.
  • Explain the why behind the changes, not just the what.
  • Wrap lines at 72 characters.

[optional footer(s)] (Optional):

  • Contains metadata like breaking changes, issue references, or co-author credits.
  • Separated from the body by a blank line.
  • Breaking Changes: BREAKING CHANGE: <description of breaking change>
  • Issue References: Fixes #123, Closes #456 (GitHub, GitLab, etc., can automatically close issues with these keywords).
  • Co-author Credits: Co-authored-by: name <name@example.com>

Example # 1

feat(api): add user authentication endpoint

This commit introduces a new endpoint for user authentication using JWT.

The new endpoint allows users to log in with their username and password
and receive a JSON Web Token (JWT) that can be used to authenticate
subsequent requests.

BREAKING CHANGE: The `/login` endpoint now requires a username and password
in the request body instead of basic authentication.

Fixes #12

Example #2

fix(parser): handle edge case for empty input strings

The parser was crashing when encountering empty input strings. This commit
fixes the issue by adding a check for empty strings and returning an
appropriate default value.

This issue was discovered during user testing and was causing confusion for
users who accidentally submitted empty forms. By handling this case
gracefully, we improve the user experience and prevent unexpected errors.

Fixes #27
Co-authored-by: Jane Smith <jane.smith@example.com>

Example #3

feat(api): introduce new user profile endpoint

This commit introduces a new endpoint for retrieving user profile
information. The new endpoint provides more detailed information than the
previous endpoint and allows for filtering and sorting of results.

This new endpoint is part of our ongoing effort to improve the API and
provide more flexibility to developers.

BREAKING CHANGE: The `/user` endpoint has been removed and replaced by the
new `/users/{id}/profile` endpoint. Clients using the old endpoint will
need to be updated to use the new endpoint.

Closes #42
Co-authored-by: John Doe <john.doe@example.com>
Co-authored-by: Alice Lee <alice.lee@example.com>

Key Points about Footers:

  • Order Doesn’t Matter (Mostly): While generally BREAKING CHANGE is placed first, the order of other footers doesn't strictly matter.
  • One Footer Per Line: Each footer should be on its own line.
  • Token and Value: Each footer consists of a token (e.g., Fixes, Co-authored-by) followed by a colon and a space, and then the value (e.g., #27, Jane Smith <jane.smith@example.com>).

Have fun :-)

--

--

Dilip Kumar
Dilip Kumar

Written by Dilip Kumar

With 18+ years of experience as a software engineer. Enjoy teaching, writing, leading team. Last 4+ years, working at Google as a backend Software Engineer.

No responses yet