Advanced git concepts - All you need to learn to be a pro git user

Jul 6, 2022 | 25 min read

Since you have reached to advanced git concept, I welcome you to get yours hand dirty with the given example and get a deep understanding of git. But if you are here, by mistake or your are new to git, then first go through the blog: Vocabulary of git - a beginner guide , also get your feet wet before diving deep into git from blog :How to get Started with git - a beginner cheat sheet .

Hence this blog is a third blog on series: Git basic to Advanced. Now, lets get started:

Git Stash

Git stash temporary shelves (or stashes) changes you've made to your working copy so you can work on something else, and then come back and re-apply them later on. Stashing is handy if you need to quickly switch context and work on something else, but you're mid-way through a code change and aren't quite ready to commit.

E.g. Let's say you are working on a new feature in your Git repository. You have made some modifications to multiple files but haven't completed the implementation yet . However, your team lead asks you to switch to a different branch urgently to fix a critical bug. Since you are not completed with the feature so it's not a good practice to commit the half done code, instead in this case you will need to use git stash command to temporarily add your current version of code to somewhere safe so you can get back later at the current version once you are done with the fix that was urgent.

$ git status
On branch main

Changes to be committed:
    new file:   style.css
Changes not staged for commit:
    modified:   index.html

Now you want to switch branches, but you don’t want to commit what you’ve been working on yet, so you use git stash or git stash push alternatively , to store the working directory to somewhere safe so you can get it back later at the current state.

$ git stash 
Saved working directory and index state 
  "WIP on main: 5002d47 our new homepage"
HEAD is now at 5002d47 our new homepage
  (To restore them type "git stash apply")

Now after this commad you can see your working directory is absolutely clean

$ git status
On branch main
nothing to commit, working tree clean

After this point you can switch to any of the branch and do your work to whatever branch and later on get on your earlier point.

By default, git stash will stash only modified and staged tracked files. You can also stash untracked files using flag

Flags:

--include-untracked or -u : Includes untracked file in stash

--all or -a : includes untracked file and ignored files as well

$ git stash --include-untracked

List all the stashes: as you can add any no of stashes in git you can list using command git stash list or git stash show as below

$ git stash list 
stash@{0}: WIP on master: 049d078 Create index file
stash@{1}: WIP on master: c264051 Revert "Add file_size"
stash@{2}: WIP on master: 21d80a5 Add number to log

You can view your stashes with the command: git stash list or git stash show

There can be multiple stashes at same time:

To make the get all the changes in last stash in working directory you can run

$ git stash pop

or

$ git stash apply

 $ git stash apply
  On branch master
  Changes not staged for commit:
    (use "git add <file>..." to update what will be committed)
    (use "git checkout -- <file>..." to discard changes in working directory)
      modified:   index.html
      modified:   lib/simplegit.rb
  no changes added to commit (use "git add" and/or "git commit -a")

If you want to apply one of the older stashes, you can specify it by naming it, like this:

$ git stash pop stash@{1}

or

$ git stash apply stash@{1}

git stash clear empties the stash list by removing all the stashes.

git stash drop stash@{2} : Deletes a particular stash and the changes from the stash list.

Sometime ,you may want to move the specific or recent stash to a new branch instead of continuing in same branch then you can use command

git stash branch

This command creates a new branch for you with your selected branch name, checks out the commit you were on when you stashed your work, reapplies your work there, and then drops the stash if it applies successfully:

$ git stash branch testchanges
  M   index.html
  M   lib/simplegit.rb
  Switched to a new branch 'testchanges'
  On branch testchanges
  Changes to be committed:
    (use "git reset HEAD <file>..." to unstage)
      modified:   index.html
  Changes not staged for commit:
    (use "git add <file>..." to update what will be committed)
    (use "git checkout -- <file>..." to discard changes in working directory)
      modified:   lib/simplegit.rb
  Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014)

Undoing Changes (Commit history)

Git Reset

The git reset command is a complex and versatile tool for undoing changes.

Git reset is a command that allows developers to manipulate the commit history in a Git repository. It provides flexibility in undoing changes, discarding commits, and rewriting history.

It has three primary forms of invocation. These forms correspond to command line arguments --soft, --mixed, --hard

git reset --soft : This variant moves the branch pointer to the specific commit, without modifying the staging area or working directory. It effectively "uncommit" the changes, leaving the changes in the working directory and staging area for you to modify and re-commit. This is useful when you want to make additional changes or restructure your commits before creating a new commit.

git reset --mixed : This is the default behavior of git reset if no option is specified. It moves the branch pointer to the specified commit and updates the staging area to match the state of that commit. This means that the changes in the commits between the current state and the specific commit are removed from the staging area, but still remain in the working directory as uncommitted changes. This is useful when you want to unstage changes but keep the modifications intact for further review or modification.

git reset --hard : This is the most powerful and potentially dangerous option. It moves the branch pointer to the specified commit and updates both the staging area and the working directory to match the state of that commit. Any uncommitted changes and all commits after the specific commit will be permanently discarded. This is useful when you want to completely undo changes and start fresh from a specific commit, but be cautious as this operation is not reversible.

Git Revert

In Git, the git revert command is used to create a new commit that undoes the changes made in a previous commit. It allows you to revert or undo the effects of a specific commit while preserving a complete history of the project.

When you run git revert , Git will create a new commit that applies the inverse of the changes introduced by the specific commit. This means that the changes made in the commit you are reverting will be undone, effectively canceling out those changes.

The git revert command is different from other Git commands like git reset or git checkout, which manipulate the commit history directly. Instead of modifying the commit history, git revert creates a new commit that negates the changes made by a specific commit.

By using git revert, you can safely undo changes without rewriting history, which is useful when you want to preserve the integrity of the commit history and collaborate with others.

Git Clean

It generally removes all the files that are untracked. From working directory i.e. When running git clean all files that are not added to git are removed and the removal is permanently deleted. It does not remove untracked or ignored files

git clean: Removes untracked files from the working directory (with safety precautions).

git clean -n: Performs a dry run or shows a list of files that would be deleted(i.e. Only shows).

git clean -f: Forcefully removes untracked files (including directories).

git clean -df: Forcefully removes untracked files and directories.

git clean -xf: Forcefully removes untracked files and ignored files.

Rewriting History

Git Commit - -amend

Sometimes commit are done too early and possibly forget to add some files that were to be added in the last commit history, or you mess up your last commit message. If you want to redo that commit, make the additional changes you forgot, stage them, and commit again but in same history then you can use the --amend option:

$ git commit --amend 

The git commit --amend command is a convenient way to modify the most recent commit.

$ git commit --amend  --no-edit

It lets you combine staged changes with the previous commit instead of creating an entirely new commit. The --no-edit flag will allow you to make the amendment to your commit without changing its commit message.

$ git commit --amend -m "an updated commit message"

If you want to change the last commit messager then you run git commit --amend -m "new commit message"

Changing older or multiple commits

Git Squash

It’s also possible to take a series of commits and squash(crush or squeze) them down into a single commit with the interactive rebasing tool. In Git, the git squash command is not a built-in command, but rather a technique used to combine multiple commits into a single commit. It allows you to condense a series of commits into a cleaner and more cohesive commit history.

The general process of squashing commits involves combining commits that are related or represent a single logical change. This can be useful to create a more concise and organized commit history before pushing your changes to a shared repository or submitting a pull request.

- To squash commits, you typically use the git rebase command in interactive mode (git rebase -i).

Here's a step-by-step example:

  1. Run git rebase -i HEAD~N, where N is the number of commits you want to squash, starting from the oldest commit.
  2. An interactive rebase file will open in your default text editor, listing the commits you specified in reverse chronological order.
  3. Change the word pick to squash (or s for short) for the commits you want to squash into the previous commit or you can even delete or perform various operations but be extra cautious
  4. Save and close the file.
  5. Another text editor will open, allowing you to modify the commit message for the combined commit.
  6. Save and close the file.

After these steps, Git will apply the changes of the squashed commits and create a new consolidated commit with a new commit message. The previous individual commits will be replaced with the squashed commit.

It's worth noting that squashing commits alters the commit history, so it should be used with caution, especially if you've already pushed the commits to a shared repository. Squashing is more appropriate for local branches or feature branches before integration into the main branch.

Git Rebase/Merge (When to use Rebase or merge ?):

The primary reason for rebasing is to maintain a linear project history. For example, consider a situation where the main branch has been changed(progressed) since you started working on a feature branch. You want to get the latest updates to the main branch in your feature branch, but you want to keep your branch's history clean so it appears as if you've been working off the latest main branch. This gives the later benefit of a clean merge of your feature branch back into the main branch.

Rebasing is the process of moving or combining a sequence of commits to a new base commit. Rebasing is most useful and easily visualized in the context of a feature branching workflow.

It allows you to integrate changes from one branch onto another by moving, combining, or modifying commits.

git rebase branch-name

$ git rebase main 

Running git rebase with the -i flag begins an interactive rebasing session. Instead of blindly moving all of the commits to the new base, interactive rebasing gives you the opportunity to alter individual commits in the process. This lets you clean up history by removing, splitting, and altering an existing series of commits.

The golden rule of Rebase :

Not only does rebase come with a good part, it has some dangers associated with it, So, before you run git rebase, always ask yourself, “Is anyone else looking at this branch?” If the answer is yes, take your hands off the keyboard and start thinking about a non-destructive way to make your changes (e.g., the git revert command).

The fundamental similarities between rebase and merge is Rebasing and merging are two methods in Git used to integrate changes from one branch to another. Both approaches aim to bring together the modifications made in separate branches into a consolidated branch; in other words Both of these commands are designed to integrate changes from one branch into another branch—they just do it in very different ways.

The main differences between these two approaches is

Merging and Rebasing are essential operations in Git that help manage the complexities of collaborating on a shared codebase. They are necessary when multiple developers are working on different branches or when changes need to be integrated from one branch to another. Here's some background on why merging and rebasing are required:

Merging combines the changes from one branch into another by creating a new merge commit. This commit has two or more parent commits, representing the branches being merged. While instead of creating a merge commit, rebase rewrites the commit history of the branch being rebased.

When you merge a branch, the commit history of both branches is preserved, and a new commit is created to represent the merge itself. This often results in a "merge bubble" or a merge commit, which can help track the integration of changes while rebase changes made on the branch are rebased, incorporating the changes of the third branch. This results in a linear commit history, as if the work was done sequentially on a single branch.

Merge is a non-destructive operation that does not modify the existing commits on either branch. It's useful for combining branches with different commit histories or when multiple developers are working on a project simultaneously while rebase is useful for maintaining a cleaner and more linear commit history, especially when incorporating changes from a long-lived branch or preparing changes for integration into a main branch.

Which one is actually better ?

Which is better between merge and rebase solely depends on the team and their preferences. There are two alternative ideas regarding it,

One who supports merging, point of view explains that your repository’s commit history is a record of what actually happened. It’s a historical document, valuable in its own, and shouldn’t be modified , if you and the repository should preserve that for posterity , if your team is driven by this mental picture you may use merge instead of rebase.

The other point of view is that the commit history must look like a story that your project is ongoing, For e.g. it is like writing a book , you wouldn’t publish the first draft of a book, so why to show the mess? When you’re working on a project, you may need a record of all your missteps and dead-end paths, but when it’s time to show your real work to the world, you may want to tell a more coherent story of how to get from A to B.

Teams in this thought process use tools like rebase and filter-branch to rewrite their commits before they’re merged into the mainline branch. They use tools like rebase and filter-branch, to tell the story in the way that’s best for future readers.

Hence, there is no such thing as a better one over other , it depends solely on the team preference and what the team wants the code to look like.

Resolving a Git Conflict:

Resolving and understanding git conflicts can be challenging but is actually manageable with the right approach. Here are some steps to help you resolve conflicts effectively and gain a better understanding of the process:

  • Take a deep breath and stay calm: Conflicts can be frustrating, but it's essential to approach them with a calm and patient mindset. Remember that conflicts are a normal part of collaborative development.

  • Review the conflict: When you encounter a conflict, Git will mark the conflicting sections with special markers (<<<<<<<, =======, >>>>>>>). Take a moment to understand what these markers represent. The <<<<<<< marker indicates the beginning of the conflicting changes from your branch, followed by =======, which separates your changes from the incoming changes (>>>>>>>).

  • Understand the conflicting changes: Read through the conflicting sections and try to understand the changes made by both parties. Pay attention to the context of the changes and the lines involved. This understanding will help you make informed decisions during the resolution process.

  • Communicate with your team: If you're working in a team, reach out to your teammates to discuss the conflicting changes and ensure everyone is on the same page. Collaboration and clear communication can help in resolving conflicts efficiently.

  • Decide on the resolution strategy: Consider the conflicting changes and decide on the best strategy to resolve them. Some common approaches include:

    • Accept incoming changes: If the changes from the other branch are correct and you want to discard your changes, you can simply remove your changes and keep the incoming changes.
    • Accept your changes: If you believe your changes are correct and should be kept, you can remove the incoming changes and retain your modifications.
    • Manual merge: In some cases, you may need to combine the changes from both branches manually. Edit the conflicting sections, removing the conflict markers and choosing the appropriate changes from both branches. Be cautious to maintain the intended functionality and avoid introducing new issues.
  • Test your changes: After resolving the conflicts, test your changes to ensure they work as expected. Run any relevant tests, verify the functionality, and ensure that the resolution did not introduce any errors.

  • Commit and push the resolved changes: Once you're satisfied with the resolution, use git add to stage the resolved files, followed by git commit to create a new commit. Provide a clear and descriptive commit message that explains the conflict resolution. Finally, push the changes to the remote repository using git push to make them available to others.

  • Reflect and learn: After resolving conflicts, take the opportunity to reflect on the process and learn from it. Consider what led to the conflicts and explore ways to prevent similar conflicts in the future, such as improving communication or establishing coding conventions.

Git log

The purpose of any version control system is to record changes to your code. This gives you the power to go back into your project history to see who contributed what, figure out where bugs were introduced, and revert problematic changes. In addition to see git commit logs there are other functions of git Log :

  1. Commit history: The primary purpose of git log is to show the commit history. It displays a chronological list of commits, starting from the most recent commit by default.
  2. Commit information: git log provides detailed information about each commit. It includes the commit hash(unique identifier) for the commit, the author's name and email, the date and time of the commit, and the commit message, which describes the changes made in that commit.
  3. Branch history: git log can be used to view the commit history of a specific branch. By default, it shows the commits for the current branch. However, you can specify a different branch or a combination of branches to view their commit history.
  4. Filtering and formatting: git log offers various options to filter and format the commit history. You can use flags like --author to filter commits by a specific author, --since and --until to limit commits within a specific timeframe, or --grep to search for commits with specific patterns in the commit message.
  5. Graphical representation: Git log can be enhanced with options like --graph and --oneline to display the commit history in a graphical and concise format. The --graph option draws ASCII art-based graphs that show the branching and merging of commits, while --oneline provides a condensed one-line representation of each commit.
  6. Commit diff: By using git log with the -p or --patch option, you can view the detailed changes made in each commit. This shows the diff (difference) between the commit and its parent commit, highlighting the added, modified, and deleted lines of code.

Basic git log: Running git log without any flags will display the commit history in reverse chronological order, starting from the most recent commit at the top.

$ git log
commit 1c910476b213bf7b149fea281edd16c9ee3c9ddd
Author: Sagar Chapagain saagarchapagain@gmail.com
Date:   Thu Jun 29 15:49:22 2023 +0545
     Design: Finalize design of Detail page 
commit 79835d5bbf36f6685bfbf42aa3cd76407917d881
Author: Sagar Chapagain saagarchapagain@gmail.com
Date:   Tue Jun 27 01:09:08 2023 +0545
    Bug:Fixed bug in search in car filter 

--oneline flag: The --oneline flag provides a concise representation of each commit on a single line. It includes the commit hash and the commit message.

$ git  - -oneline
95afde5 (origin/filter) final issues solved for brand model
fde6c7a bug:completed brand model dynamic checkbox selection

--author flag: You can use the --author flag to filter the commit history by a specific author. For example, to view the commits made by a user named "John Doe," you can use the following command:

$ git log --author="Sagar Chapagain"
commit 043f63a7b8841414f928e67ea1942726fcc61dc1 (HEAD -> Designv3.0)
Author: Sagar Chapagain saagarchapagain@gmail.com
Date:   Wed Jul 5 12:28:21 2023 +0545
    bug fixes for the latest support issues
    working on bugs
commit 9b16b7130d292a313fbfd2dc3d7d37cb93ca9903
Author: Sagar Chapagain saagarchapagain@gmail.com
Date:   Sat Jun 24 20:37:10 2023 +0545
    updated changes for New design of home page

--since and --until flags: The --since and --until flags allow you to specify a time range to filter the commits. For example, to view the commits made between two specific dates, you can use the following command:

$ git log --since="2023-01-01" --until="2023-06-30"

--grep flag: The --grep flag lets you search for commits that match a specific pattern in the commit message. For example, to find commits with the word "bug" in the commit message, you can use the following command:

$ git log --grep="bug"
commit 043f63a7b8841414f928e67ea1942726fcc61dc1 (HEAD -> Designv3.0)
Author: Sagar Chapagain saagarchapagain@gmail.com
Date:   Wed Jul 5 12:28:21 2023 +0545
    bug fixes for the latest support issues
    
    working on bugs
commit bf377a381a92df4760adb56566d97567cb72356a
Author: sagar chapagain saagarchapagain@gmail.com
Date:   Thu Mar 18 15:06:54 2021 +0545
    completed some bugs 2021-03-18
commit 9842e7910ff147dbd107324c4d48a59cd5f23230
Author: sagar chapagain saagarchapagain@gmail.com
Date:   Fri Feb 12 06:49:16 2021 +0545

-p or --patch flag: The -p or --patch flag shows the detailed changes made in each commit, including the diff (difference) between the commit and its parent commit. This provides a comprehensive view of the modifications introduced in each commit.

$ git log -p

--graph flag: The --graph flag adds ASCII art-based graph lines that illustrate the branching and merging of commits. This option is particularly useful when working with complex branching and merging scenarios.

git log --graph
* commit 043f63a7b8841414f928e67ea1942726fcc61dc1 (HEAD -> Designv3.0)
| Author: Sagar Chapagain saagarchapagain@gmail.com
| Date:   Wed Jul 5 12:28:21 2023 +0545
| 
|     bug fixes for the latest support issues
|     
|     working on bugs
| 
* commit 9b16b7130d292a313fbfd2dc3d7d37cb93ca9903
| Author: Sagar Chapagain saagarchapagain@gmail.com
| Date:   Sat Jun 24 20:37:10 2023 +0545
| 
|     updated changes
| 
* commit 42b08eca84cf425facd333ebce6c06444027a2b4 (origin/Designv3.0)
| Author: Sagar Chapagain saagarchapagain@gmail.com
| Date:   Thu Mar 2 09:49:02 2023 +0545

Git Fork: Forking is a feature provided by Git-based version control systems, such as GitHub and GitLab, that allows developers to create a personal copy of a repository. When you fork a repository, you create a separate copy of the entire repository, including all its files, branches, and commit history. This copy is stored in your personal GitHub or GitLab account.

Git Pull Requests

A pull request is a feature provided by Git-based version control systems, such as GitHub and GitLab, that allows developers to propose changes to a codebase to the internal team or a open source team.

In their simplest form, pull requests are a mechanism for a developer to notify team members that they have completed a feature. Once their feature branch is ready, the developer files a pull request via their git account. This lets everybody involved know that they need to review the code and merge it into the main branch.

But, the pull request is more than just a notification—it’s a dedicated forum for discussing the proposed feature. If there are any problems with the changes, teammates can post feedback in the pull request and even tweak the feature by pushing follow-up commits. All of this activity is tracked directly inside of the pull request.

Steps of Pull Requests

  • Forking or Branching: The developer creates a new branch in their repository either by forking the main repository (in an open-source project) or by creating a new branch in the same repository. This forking of the repository creates a separate copy of the repository that you have full control over.
  • Cloning the Forked Repository: Once you have forked the repository, you clone it to your local machine using Git. This creates a local copy of the forked repository that you can work with.
  • Making Changes: The developer makes the desired changes, such as adding new code, modifying existing code, or fixing bugs, in their branch. They commit these changes to their branch.
  • Opening a Pull Request: Once the changes are ready for review, the developer opens a pull request. They specify the branch containing their changes and the branch (often the main branch like 'master' or 'develop') into which they want to merge their changes.
  • Code Review and Discussion: Other developers or team members review the code changes in the pull request. They can provide comments, ask questions, and suggest modifications. This process encourages collaboration, knowledge sharing, and the identification of potential issues or improvements.
  • Iteration and Continuous Improvement: Based on the feedback received, the developer can make additional commits to their branch, addressing the comments and suggestions raised during the code review process. The pull request is automatically updated with these new changes.
  • Merge or Close: Once the reviewers are satisfied with the changes, the pull request can be merged into the target branch. This incorporates the changes from the developer's branch into the main codebase. Alternatively, if the changes are no longer needed or deemed unnecessary, the pull request can be closed without merging.

Git Bisect:

Git bisect is a powerful tool for finding the commit that introduced a bug. It performs a binary search through your commit history, allowing you to quickly identify the exact commit that caused the issue.It automates the process of narrowing down the range of commits to identify the exact commit where the issue was introduced.

Here's a step-by-step overview of how to use Git bisect:

  1. Start the Bisect: Use the command git bisect start to begin the bisect process. You can also specify a range of commits to limit the search, or simply let Git consider the entire commit history.

  2. Identify Good and Bad States: Identify a known "good" state (a commit where the issue is not present) and a known "bad" state (a commit where the issue is present). Use the commands git bisect good and git bisect bad to mark these states. Git will initially assume the current commit as the bad state.

    Git bisect automatically checks out a commit in the middle of the range between the good and bad states. It allows you to test and determine if the issue exists in that commit.

  3. Test and Determine: After checking out a commit, perform tests or run your application to observe if the issue is present. Based on the outcome, use git bisect good or git bisect bad to mark the commit as good or bad, respectively.

  4. Repeat: Git bisect will automatically select another commit in the middle of the remaining range based on the previous result. Repeat the testing process by running your tests or application and marking the commit accordingly.

  5. Continue until the bad commit is found: Repeat the process of testing and marking commits as good or bad until Git identifies the specific commit that introduced the bug. Once the bad commit is found, Git bisect will print the commit hash and additional information.

  6. Stop the Bisect: Use the command git bisect reset to end the bisect process. It will return your repository to the original state, with the HEAD set to the commit where you started the bisect.

Git Workflows

A Git workflow is a recipe or recommendation for how to use Git to accomplish work in a consistent and productive manner.

In other words, Git workflows are a set of guidelines and practices that help teams collaborate and manage changes to a codebase using Git, a popular version control system. There are several commonly used Git workflows, each with its own advantages and suited for different types of projects and team structures.

Here are some of the most popular Git workflows:

1. Centralized Workflow:

  • In this workflow, there is a single central repository that acts as the source of truth.
  • Developers clone the repository, make changes locally, and push their changes to the central repository.
  • It is a simple workflow suitable for small teams or projects with a linear history of changes.

2. Feature Branch Workflow:

  • Each feature or task is developed in a dedicated branch.
  • Developers create a new branch for each feature, make changes, and push the branch to the central repository.
  • Once the feature is complete, it is merged back into the main branch (usually 'master' or 'develop').
  • This workflow allows for parallel development and easy collaboration.

3. Gitflow Workflow:

  • Gitflow is a branching model that provides a strict structure for managing larger projects.

  • It defines long-lived branches such as 'master' for production-ready code and 'develop' for ongoing development.

  • Features, bug fixes, and releases are developed in separate branches and merged into 'develop' or 'master' based on the stage of development.

  • It provides a clear separation of features and stability levels, making it suitable for complex projects and teams.

    In Gitflow, the two main branches are:

Master (or Main): The master branch represents the mainline branch of development. It contains the stable and production-ready code.

Develop: The develop branch is where ongoing development and integration of new features take place. It acts as a branch for the next release. Developers create feature branches from the develop branch to work on specific features or tasks

Apart from these two primary branches, Gitflow also defines additional branches for different purposes:

Feature branches: Developers create feature branches from the develop branch to work on specific features or tasks. These branches are short-lived and are used for developing new features or making significant changes.

Release branches: Release branches are created from the develop branch when preparing for a new release. The release branch allows for final testing, bug fixes, and last-minute changes before merging into the master branch.

4. Forking Workflow:

  • Commonly used in open-source projects, this workflow involves each developer forking the main repository to their personal repository.
  • Developers create feature branches in their own repositories, make changes, and submit pull requests to the main repository.
  • The maintainers of the main repository review the changes and merge them if they meet the project's standards.
  • This workflow allows for a decentralized approach to collaboration and contributes to a more open and inclusive development process.

Trunk Workflow:

The term "trunk" refers to the main branch of development, typically named 'main' or 'trunk' itself.

Trunk-based development is a version control management practice where developers merge small, frequent updates to a core “trunk” or main branch. Since it streamlines merging and integration phases, it helps achieve CI/CD and increases software delivery and organizational performance.

  • Feature Development: Developers work directly on the main branch rather than creating long-lived feature branches. This approach promotes smaller and more frequent commits, avoiding long-lived feature branches that can lead to integration issues.
  • Continuous Integration: Developers commit their changes to the main branch frequently, ensuring continuous integration of new features and bug fixes. This enables early detection of integration issues and promotes collaboration.
  • Feature Flags: Feature flags or toggles are used to control the visibility and activation of new features. This allows features to be developed and merged into the main branch while remaining hidden until they are ready for release.
  • Continuous Deployment: The main branch is always kept in a deployable state, allowing for continuous deployment or fast and frequent releases.

The Trunk-Based Development workflow aims to minimize branching and promote frequent integration of changes, leading to faster feedback loops, reduced conflicts, and increased development velocity. It emphasizes maintaining a single, always-deployable main branch, referred to as the trunk.

Here are some key differences between the Git workflows & Trunk workflow

Branching Model:

Gitflow defines multiple long-lived branches, such as 'master', 'develop', feature branches, release branches, and hotfix branches while TBD emphasizes a single main branch, often named 'main' or 'trunk', where all development takes place.

Branching Strategy:

Gitflow encourages the creation of feature branches for each new feature or task. Developers work on these branches and merge them back into the 'develop' branch once the work is completed while TBD discourages long-lived feature branches. Instead, developers work directly on the main branch, committing their changes frequently and integrating them into the main branch as soon as possible.

Release Process:

Gitflow has a defined release branch for preparing and stabilizing releases. The release branch allows for final testing, bug fixes, and last-minute changes before merging into the 'master' branch while TBD typically relies on feature flags or toggles to control the visibility and activation of new features. Continuous deployment is often practiced, allowing for frequent releases directly from the main branch.

Complexity:

Gitflow provides a more complex branching model with multiple branches, which can be beneficial for larger teams or projects that require strict separation of development stages .TBD simplifies the branching model by focusing on a single main branch. It works well for smaller teams or projects with a need for rapid development and frequent releases.

To Conclude:

Gitflow offers a structured and controlled release process, whereas Trunk-Based Development prioritizes simplicity, continuous integration, and rapid deployment from a single main branch.

Git WorkTree

Git worktree is a feature introduced in Git 2.5 that allows you to maintain multiple working trees or working directories from a single Git repository. It enables you to have separate instances of your repository, each with its own working directory and checked-out branch.

Multiple Working Trees: With Git worktree, you can create additional working trees associated with the same repository. Each working tree is an independent copy of the repository, allowing you to work on different branches or commits simultaneously.

Isolated Changes: Each worktree has its own working directory, allowing you to make changes, switch branches, or perform operations without affecting other worktrees or the original repository.

Shared Repository: All worktrees created with Git worktree share the same repository data, such as object database and reflogs. This means they do not require additional disk space for duplicate repository data.

Usage: Git worktree is useful in various scenarios, such as working on multiple branches concurrently, testing changes without interrupting ongoing work, or reviewing different versions of the codebase.

Worktree Commands: Git provides commands to manage worktrees. Some commonly used commands include:

git worktree add : Creates a new worktree at the specified path and checks out the specified branch.

git worktree list: Lists all the existing worktrees.

git worktree prune: Cleans up stale or unused worktrees.

git worktree remove : Removes the specified worktree.

It's important to note that worktrees are designed for temporary or short-lived use. If you need a separate, long-term repository with its own remote, you should consider creating a clone instead of using Git worktree.

Git worktree is a powerful feature that provides flexibility and convenience when working on multiple aspects of a project simultaneously. It helps maintain separate working environments while keeping repository data shared and in sync.

Git Hooks

Git hooks are scripts that Git can execute at specific points during its workflow. These scripts allow you to automate or customize certain actions before or after specific Git events occur, such as committing code, pushing changes to a remote repository, or merging branches. Git hooks are typically written as shell scripts, but they can be written in any scripting language that can be executed on your system.

To use Git hooks, you need to create executable scripts in the appropriate .git/hooks directory within your local repository or on the remote server. Git provides template files for each hook that you can customize by removing the .sample extension and adding your desired logic.

By leveraging Git hooks, you can automate repetitive tasks, enforce coding standards, ensure code quality, and implement custom workflows as part of your Git process.

Git provides two types of hooks: client-side hooks and server-side hooks. Client-side hooks run on the developer's local machine, while server-side hooks run on the remote repository server.

  • Pre-Commit Hook: This hook is executed before a commit is created. It allows you to perform checks or validations on the changes being committed. For example, you can run tests, check coding standards, or perform syntax validations. If the hook script fails or returns a non-zero exit status, the commit process is aborted.
  • Pre-Push Hook: This hook is executed before a push operation is performed to a remote repository. It can be used to enforce certain rules or checks before allowing the push to proceed. For instance, you can run additional tests or check if the code adheres to specific guidelines. If the pre-push hook fails, the push operation is prevented.
  • Post-Commit Hook: This hook is executed after a commit is created. It can be used to trigger additional actions or notifications based on the commit. For example, you can automatically generate documentation, update a bug tracker, or send notifications to team members about the new commit.
  • Pre-Receive Hook: This hook runs on the remote repository server when new commits are being pushed to it. It allows you to perform validations or checks on the pushed changes before accepting them. This is often used in a collaborative environment to enforce certain policies or requirements before accepting changes into the shared repository.
  • Update Hook: This hook is executed on the remote repository server when a reference (branch, tag, etc.) is being updated. It can be used to enforce branch-level policies or perform additional checks before accepting the update. The update hook is useful for implementing more advanced workflows or access control mechanisms.

Git Reflog

Reflog, short for "reference log," is a feature in Git that records all changes to the tips of branches and other references in your repository. It serves as a safety net by maintaining a log of all previous reference states, even those that are no longer accessible through regular branch references. It can be useful for recovering lost commits or branches that were accidentally deleted.

  • Reference History: Git reflog keeps a history of reference changes, including branch checkouts, commits, merges, resets, and other operations that affect the branch tips and other references in your repository.
  • Local to Each Clone: Reflogs are maintained locally within each clone of a Git repository. This means that each clone has its own reflog, specific to the operations performed in that clone.
  • Useful Recovery Tool: Reflog is particularly useful for recovering lost commits or branches. If you accidentally delete a branch or reset to an unintended commit, you can use the reflog to find the previous state and restore it.
  • Short-Term History: The reflog primarily serves as a short-term history of reference changes. By default, it retains this history for 90 days, but the retention period can be configured. Older entries are eventually pruned from the reflog.
  • Accessing Reflog: You can access the reflog using the git reflog command. It displays a chronological list of reference changes, showing the commit IDs, branch names, and other relevant information.
  • Reflog References: The reflog records changes to various references, including branch heads (refs/heads/), remote branches (refs/remotes/), tags (refs/tags/), and other references (refs/stash, HEAD, etc.).
  • Safety Considerations: Reflog is a local feature and not typically shared or synchronized between repositories. If you need to recover a lost commit or branch, it is essential to identify and recover it in the same clone where the reference changes occurred.

Git cherry-pick

Git cherry-pick is a powerful command that enables arbitrary Git commits to be picked by reference and appended to the current working HEAD. Cherry picking is the act of picking a commit from a branch and applying it to another. git cherry-pick can be useful for undoing changes. For example, say a commit is accidently made to the wrong branch. You can switch to the correct branch and cherry-pick the commit to where it should belong.

Useful Scenario to use Cherry pick

Team collaboration:

Suppose a new product feature has a backend and frontend component. Maybe the backend developer creates an API structure that the frontend will also need to utilize. The frontend developer could use git cherry-pick to pick the commit .This pick would enable the frontend developer to continue progress on their side of the project.

Bug hotfixes:

Suppose a developer has started working on a new feature. During that new feature development they found a pre-existing bug. The developer creates an explicit commit patching this bug. This new patch commit can be cherry-picked from current branch directly to the main branch to fix the bug

Undoing changes and restoring lost commits:

Sometimes a pull request might get closed or mistaken without merging. Git never loses those commits and through commands like git log and git reflog they can be found and cherry picked back to life.

Here's how the cherry-pick command works:

Identify the Commit: First, you need to identify the commit you want to apply to another branch. You can find the commit hash using Git log or other Git visualization tools.

$ git log

Switch to the Target Branch: Checkout or switch to the branch where you want to apply the selected commit. This branch will be the destination branch for the cherry-picked commit.

$ git checkout <branch-name>

Execute the Cherry-pick Command: Use the following syntax to apply the commit to the current branch:

$ git cherry-pick <commit-hash>

Resolve Conflicts (if any): If there are any conflicts between the cherry-picked commit and the destination branch, Git will pause the cherry-pick process and indicate the conflicting files. You need to manually resolve the conflicts by editing the affected files, marking the conflicts as resolved, and staging the changes. Then you can continue the cherry-pick process by running :

$ git cherry-pick --continue

Repeat Cherry-picks (optional): If you want to cherry-pick multiple commits, you can repeat steps 1-4 for each commit.

Finish Cherry-picking: After applying all the desired commits, you can commit the changes created by the cherry-picks to complete the process.

Note: It's important to note that cherry-picking creates new commits in the destination branch with the same changes as the selected commits. The new commits will have different commit hashes, as they are not direct copies of the original commits.

Other blogs from this Series:

  1. Vocabulary of git - a begineer guide to understanding git
  2. How to get Started with git - A begineer cheatsheet
Author Profile Picture

Sagar Chapagain

I am a Software Engineer, a Solution Architect,a Mentor, a Trainor, a Technologist, Speaker, from land of Himalays, Enthusiasts in Tech, Investment and Economy, with a total years of experience in field of software and application development, Deployment .