Sign In
Sign In

Best Practices for Using the git stash Command

Best Practices for Using the git stash Command
Hostman Team
Technical writer
Git
07.03.2025
Reading time: 8 min

Git is a distributed version control system developed by Linus Torvalds. It has become the standard in software development due to its efficiency and flexibility.

During the development process, there are situations when you need to urgently switch to another branch using the checkout command and make changes related to a different task. However, the current changes may not be ready for a commit yet, and you don’t want to lose them. In such situations, the git stash command comes to the rescue. This tool is indispensable, allowing you to safely store current changes for a while and then return to them without disrupting the integrity of the repository.

Let’s explore how to use git stash in development.

Basics of git stash

Let's think about how we normally work with the codebase and Git. We create something (a function or a small module), then run git add, followed by git commit and git push. Great; we have finished the task and can move on to the next one.

But what if the context changes, and you need to switch urgently?

You may not have finished writing the module yet, but you must complete another task now. You don’t want to leave the commit unfinished. This is where git stash comes in. So, what does it do?

The process of saving temporary changes consists of two stages:

  1. stash: We save the changes to a special storage. You can also add a comment for them.
  2. pop or apply: We bring the changes back into our working directory.

Preparation

The version control system must track changes you are going to stash. You can add files to the tracked list using the command:

git add .

Creating a Stash

To create a stash, use the following command:

git stash

Output:

Saved working directory and index state WIP on master: 099797d start

By default, the stash name contains the abbreviation “WIP” (Work In Progress) and the branch name. If you want to specify a comment, you can use the following commands: git stash push or git stash save:

git stash push -m "<your comment>"

The result will be:

Saved working directory and index state On master: <your comment>

The same result will occur with this command:

git stash save "<your comment>"

However, this command is considered deprecated — you can check the documentation for more details.

Retrieving Changes

Now, let’s return to the original task. We need to bring back the hidden changes. Use the command:

git stash pop

The output will tell you that the changes have been applied to the current working area and can now be used. It will also indicate that all data has been removed from the special temporary storage.

If you need to apply the changes without removing them from the stash, use the command:

git stash apply

Additional Commands and Parameters

List All Stashes

To view the list of changes that have been stashed in the repository, you can use the command:

git stash list

Example output:

stash@{0}: On master: User Story #2010
stash@{1}: WIP on master: 099797d start

Applying Specific Changes by Index

To apply a specific change, you can use the pop command with the stash index:

git stash pop 'stash@{1}'

Example output:

no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{1} (563f9c20ab12525795911fbed0c4ebf4a1298b4e)

Additional Parameters for the git stash Command

If you need to stash changes while keeping them in the working directory, use the --keep-index flag. In this case, files added to the tracked list using the git add command will remain:

git stash --keep-index

Output:

Saved working directory and index state WIP on master: 099797d start

If you call git status after this, the modified files will still be there:

On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   GitStash/Program.cs
        modified:   GitStash/SomeModule.cs

If you need to add files that git does not track yet, use the --include-untracked flag:

git stash --include-untracked

When you retrieve changes using pop, you will see a message about the presence of untracked files:

...
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        GitStash/NewClass.cs
...

Sometimes it may be convenient to split the uncommitted changes into separate stashes. In this case, the git stash -p command will help:

git stash -p

For each change, hiding will be done separately, with a prompt for confirmation. Here are the options for confirmation:

  • ? — to show all options
  • y — to stash the change
  • n — to not stash this part of the change
  • q — to stash all selected parts and finish

Viewing Specific Changes in a Stash

The show command displays information about the changes in a specific stash, for example:

git stash show
GitStash/Program.cs    | 3 ++-
GitStash/SomeModule.cs | 7 +++++-
2 files changed, 8 insertions(+), 2 deletions(-)

You can also specify the index of a specific stash:

git stash show 'stash@{1}'

Example output:

GitStash/SomeModule.cs | 5 ++++- 
1 file changed, 4 insertions(+), 1 deletion(-)

Clearing Changes from a Stash

To remove a specific stash, you can use the drop command. If you don't specify an index, it will remove the most recent set of stashed changes:

git stash drop

Or:

git stash drop 'stash@{1}'

Output:

Dropped stash@{1} (bedb3c2add59a3f203e2367602328dca8b33b6e9)

To completely clear the stash storage, you can use the command:

git stash clear

Creating a New Branch from a Stash

To create a new branch based on stashed changes, use the following command:

git stash branch <branch name> <stash index>

Or simply:

git stash branch <branch name>

For example:

git stash branch some-feature stash@{2}

How It Works

The set of changes we hide in the stash is actually a series of commits. Running this command creates two or three commits:

  • The commit stash@{0} contains the stashed files.
  • The parent commit is the HEAD commit in Git's current working directory.
  • If we run the command with the --keep-untracked flag, a separate commit will be created for the untracked files.

What happens when you run the pop command?

  • The stashed changes are returned to the repository's working copy and indexed by Git.
  • Other stashes are shifted.
  • The extracted commits are deleted.

The .git/refs/stash file contains a reference to the last commit for the stash.

cat .\.git\refs\stash

Output:

07ea0c456356e883610f43c20d9cb298ff2ebb8a

Use Cases

Let's look at some common use cases for this mechanism in practice:

Backup Before Merge or Rebase

The merge / rebase commands are necessary when working with multiple branches. However, conflicts often arise that can cause important changes in the current working directory to be lost.

  1. Before performing a merge, ensure that the current branch is up to date, i.e., it doesn't contain unsaved changes. If you have unsaved changes that should be preserved before merging, run this command:

git stash push -m "Backup before branch merge"
  1. Proceed with the merge, running merge or rebase.

  2. Conflicts may arise between the changes in the current branch and the changes in the other branch when executing these commands. You can resolve conflicts using either IDE tools or Git.

  3. After successfully completing the merge or rebase, you can restore the changes to the current working directory using apply or pop.

Non-Debugging Changes

The stash mechanism can also be helpful for working with non-debugging changes, such as temporary fixes, comments, or code formatting. Instead of committing these changes to the current commit, you can use git stash to save them temporarily. This helps in creating clean commits and improves the structure of the Git history.

Effective Project Configuration Management

Another scenario for using git stash is effectively managing project configurations. Depending on the task or environment you are working in, you might need to modify configuration files, but permanently saving them might not be practical.

  • Saving different configurations

Suppose there is a configuration file that defines the parameters for your application (e.g., config.json). You need several versions of this file for different use cases (e.g., local development, testing, and production). You can use the stash to save these configurations.

# Saving the configuration for local development
git stash save "Local configuration"

# Saving the configuration for testing
git stash save "Testing configuration"

# Saving the configuration for the production environment
git stash save "Production configuration"
  • Applying configurations as needed

When you need to switch between different configurations, simply use git stash apply or git stash pop to apply the corresponding stash:

# Applying the configuration for testing
git stash apply stash@{1}

Tips for Effective Using git stash

  • Use clear stash descriptions. The default messages created for stashes usually don’t convey the essence of the changes — they’re simply an abbreviation like WIP, a commit ID, and the branch name:
WIP on master: 099797d start

Use the push or save commands to add descriptive messages, for example:

git stash save "test configuration"

Or:

git stash push -m "Started working on issue #11 - added contract for the module"
  • Check and clean your stashes. During long-term project development, you may accumulate a large number of changes that are no longer relevant. Use the list and show commands to view the changes and git stash drop to remove obsolete stashes. This mechanism is not intended for long-term data or change storage.
  • Use stash with other commands. You can combine git stash with other commands, such as git stash branch, to create new branches, or with the rebase and merge commands to back up local changes.

Conclusion

In this article, we’ve explored the git stash command and its use cases. git stash is a powerful tool that can significantly simplify managing changes in your repository and improve your workflow. We’ve examined both basic and advanced scenarios for using this tool, including creating, applying, extracting, and managing stash entries.

Git
07.03.2025
Reading time: 8 min

Similar

Git

Git Rebase: How It Works and Why You Should Use It

In the Git version control system, there are two ways to combine one branch with another, represented by different commands: git merge. Commits from one branch are transferred into another by creating a merge commit. git rebase. Commits from one branch are transferred into another branch while preserving the original order of changes. Simply put: with git merge, the commits from one branch are “squashed” into one, while with git rebase they remain untouched, yet the branches are combined. Thus, the git rebase command allows you to combine commits from both branches by forming a shared history of changes. This guide will cover the git rebase command, which is responsible for rebasing commits (changes) from one branch to another. All the examples shown used Git version 2.34.1, running on a Hostman server with the Ubuntu 22.04 operating system. You can use these guides to install Git on your machine: Installing Git on Ubuntu Installing Git on Windows Understanding Git Rebase The best way to understand how rebasing works in Git is to look at an abstract repository consisting of several branches. At the same time, the rebasing operation should be considered step by step. Creating Branches Let’s assume we created a repository with a single branch master, containing just one commit. The master branch looks like this: master   commit_1 Then, based on master, we created a new branch hypothesis, where we decided to test some features. In this new branch, we made several commits improving the code. The branch now looks like this: hypothesis   commit_4   commit_3   commit_2   commit_1 Later, we added another commit to the master branch, urgently fixing a vulnerability. The master branch now looks like this: master   commit_5   commit_1 Now our repository has two branches: master   commit_5   commit_1 hypothesis   commit_4   commit_3   commit_2   commit_1 The master branch is the main branch, while hypothesis is secondary (derived). Later commits are listed above earlier ones, similar to the output of the git log command. Merging Branches Let’s say we want to continue working on the feature we had previously moved into the separate hypothesis branch. However, this branch does not contain the critical vulnerability fix we made in master. Therefore, we want to “synchronize” the state of hypothesis with master so that the fix commit also appears in the feature branch. In other words, we want the repository structure to look like this: master   commit_5   commit_1 hypothesis   commit_4   commit_3   commit_2   commit_5   commit_1 As you can see, the hypothesis branch now exactly repeats the history of master, even though it was originally created before commit_5. In other words, hypothesis now contains the history of both branches—its own and master. To achieve this, we need to rebase using the git rebase command. Afterward, the changes made in hypothesis can be merged into master using the classic git merge command, which creates a merge commit. Then the repository structure will look like this: master   commit_merge   commit_5   commit_1 hypothesis   commit_4   commit_3   commit_2   commit_5   commit_1 Moreover, running git merge after git rebase can reduce the likelihood of conflicts. Practice: git rebase Now that we’ve covered the theoretical aspect of the git rebase command, we can move on to testing it in a real repository of an improvised project. The repository structure will repeat the earlier theoretical example. Creating a Repository First, let’s create a separate directory to hold the repository: mkdir rebase Then move into it: cd rebase Now we can initialize the repository: git init In the console, a standard informational message should appear: hint: Using 'master' as the name for the initial branch. This default branch name hint: is subject to change. To configure the initial branch name to use in all hint: of your new repositories, which will suppress this warning, call: ... And in the current directory, a hidden .git folder will appear, which you can view with the following command: ls -a The -a flag means all and allows viewing the file system in extended mode. Its contents will be: .  ..  .git Before making commits, we need to specify some basic user information. First, the name: git config --global user.name "NAME" Then the email: git config --global user.email "NAME@HOST.COM" Populating the master Branch Using simple text files, we will simulate adding different functions to the project. Each new function will be represented as a separate commit. Create a file for the first improvised function: nano function_1 Fill it with the content: Function 1 Now index the changes made in the repository: git add . Just in case, check the indexing status: git status In the console, the following message should appear, showing the indexed changes: On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: function_1 Now we can commit: git commit -m "commit_1" The console should display a message confirming the successful commit into master: [master (root-commit) 4eb7cc3] commit_1  1 file changed, 1 insertion(+)  create mode 100644 function_1 Populating the hypothesis Branch Now let’s create a new branch called hypothesis: git checkout -b hypothesis The -b flag is necessary to switch immediately to the new branch. The console will display a confirmation message: Switched to a new branch 'hypothesis' Next, we need to make three commits in sequence with three files, similar to master: commit_2 with file function_2 and content: Function 2 commit_3 with file function_3 and content: Function 3 commit_4 with file function_4 and content: Function 4 If we then check the commit list: git log --oneline The console will display the following sequence: d3efb82 (HEAD -> hypothesis) commit_4 c9f57b7 commit_3 c977f16 commit_2 4eb7cc3 (master) commit_1 Here, the --oneline flag is used to display commit information in a compressed single-line format. Adding a Commit to the master Branch The last step is to add another commit to the main branch master. Let’s switch to it: git checkout master The console will confirm the switch: Switched to branch 'master' Now create another improvised function file: nano function_5 With the following content: Function 5 Next, index the changes: git add . And make the new commit: git commit -m "commit_5" If we check the current commit list: git log --oneline The master branch will now have two commits: 3df7a00 (HEAD -> master) commit_5 4eb7cc3 commit_1 Merging Branches with git rebase To perform rebasing, first you need to switch to the hypothesis branch: git checkout hypothesis And run the rebase: git rebase master After this, the console will display a message confirming a successful rebase: Successfully rebased and updated refs/heads/hypothesis. Now you can check the commit list: git log --oneline The console will show a list containing the commits of both branches in the original order: 8ecfd58 (HEAD -> hypothesis) commit_4 f715aba commit_3 ee47470 commit_2 3df7a00 (master) commit_5 4eb7cc3 commit_1 Now the hypothesis branch contains the complete history of the entire repository. Resolving Conflicts Just like with git merge, when using the git rebase command, conflicts may occur that require manual resolution. Let’s modify our repository in such a way that we artificially create a rebase conflict. Create another file in the hypothesis branch: nano conflict And write the following text into it: There must be a conflict here! Index the changes: git add . And make another commit: git commit -m "conflict_1" Now switch to the master branch: git checkout master Create a similar file: nano conflict And fill it with the following content: There must NOT be a conflict here! Again, index the changes: git add . And make a commit: git commit -m "conflict_2" Reopen the created file: nano conflict And change its content to: There definitely must NOT be a conflict here! Again, index the changes: git add . And make another commit: git commit -m "conflict_3" Now switch back to the hypothesis branch: git checkout hypothesis And perform another rebase: git rebase master The console will display a conflict message: Auto-merging conflict CONFLICT (add/add): Merge conflict in conflict error: could not apply 6003ed7... conflict_1 hint: Resolve all conflicts manually, mark them as resolved with hint: "git add/rm <conflicted_files>", then run "git rebase --continue". hint: You can instead skip this commit: run "git rebase --skip". hint: To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply 6003ed7... conflict_1 Git suggests editing the conflict file, indexing the changes with git add, and then continuing the rebase using the --continue flag. That’s exactly what we will do: nano conflict The file will contain two conflicting versions wrapped in special markers: <<<<<<< HEAD There definitely must NOT be a conflict here! ======= There must be a conflict here! >>>>>>> 6003ed7 (conflict_1) Our task is to remove the unnecessary parts and fill the file with a final version of arbitrary text: There must absolutely definitely unanimously NOT be any conflict here! Now index the changes: git add . And continue the rebase: git rebase --continue After this, the console will open a text editor suggesting you modify the original commit message of the commit where the conflict occurred: conflict_1 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # interactive rebase in progress; onto bd7aefc # Last commands done (4 commands done): # pick 8ecfd58 commit_4 # pick 6003ed7 conflict_1 # No commands remaining. # You are currently rebasing branch 'hypothesis' on 'bd7aefc'. # # Changes to be committed: # modified: conflict # The console will then display a message about the successful completion of the rebase process: [detached HEAD 482db49] conflict_1  1 file changed, 1 insertion(+), 1 deletion(-) Successfully rebased and updated refs/heads/hypothesis. Now if you check the commit list in the hypothesis branch: git log --oneline You will see the original sequence of all changes made: 482db49 (HEAD -> hypothesis) conflict_1 bd5d036 commit_4 407e245 commit_3 948b41c commit_2 bd7aefc (master) conflict_3 d98648d conflict_2 3df7a00 commit_5 4eb7cc3 commit_1 Notice that the commits conflict_2 and conflict_3, made in the master branch, appear in the history earlier than the conflict_1 commit. However, this applies to any commits made in the master branch. Rebasing a Remote Repository In addition to working with local branches, rebasing can be done when pulling changes from a remote repository. To do this, you need to add the --rebase flag to the standard pull command: git pull --rebase remote branch Where: remote is the remote repository branch is the remote branch Essentially, this pull configuration is equivalent to git rebase, except that the changes (commits) applied to the current branch are taken from the remote repository. Advantages of git rebase Linearity  The git rebase command makes it possible to form a fairly linear history of the target branch, consisting of sequentially made commits. Such a sequence without branching makes the history easier to perceive and understand. Fewer conflicts  Running git rebase beforehand can significantly reduce the likelihood of conflicts when merging branches with git merge. Conflicts are easier to resolve in sequential commits rather than in commits merged into a single merge commit. This is especially relevant when pushing branches to remote repositories. Disadvantages of git rebase History modification  Unlike merging, rebasing partially rewrites the history of the target branch, removing unnecessary history elements. Risk of errors  The ability to significantly restructure commit history can lead to irreversible errors in the repository. This means that some data may be permanently lost. Conclusion Merging two branches using rebasing, implemented with the git rebase command, is fundamentally different from the classic merge done with the git merge command. git merge turns the commits of one branch into a single commit in another. git rebase moves commits from one branch to the end of another while preserving the original order. A similar rebasing effect can also be achieved when using the git pull command with the additional --rebase flag. On one hand, the git rebase command allows you to achieve a cleaner and more understandable commit history in the main branch, which increases the maintainability of the repository. On the other hand, git rebase reduces the level of detail in changes within the branch, simplifying the history and removing some of its records. For this reason, rebasing is a feature intended for more experienced users who understand how Git works. Most often, the git rebase command is used together with the git merge command, allowing you to achieve the most optimal repository and branch structure.
16 September 2025 · 11 min to read
Microservices

Sending and Applying Git Patches via Email – No GitHub Needed

Git today is the most widespread and popular version control system. Probably 99% of all current projects use Git, from the Linux Kernel to simple JavaScript libraries consisting of just one file and one function. The Linux Kernel is a huge and very complex project. It involves a large number of programmers worldwide. Coordinating changes in this project would be simply impossible without an effective solution that allows this entire community to work independently of one another. Now, this seems like a simple and obvious solution. However, the path to it was long and thorny. A Brief Retrospective 1998 was an important year for Linux. Large vendors took notice of the project, and more and more developers joined. At that time, the project followed a fairly simple model for changes: developers would send their patches to Linus Torvalds, who decided whether to include the code or not. Torvalds liked this model because it gave him control over all changes. The patch mechanism was used back when code trees were small and computers were very large. A patch literally was a set of instructions on punch cards telling what and how to replace in a stack of these media to get a new program version. Punch tapes were literally cut into pieces and glued together in a specific way to introduce changes to the program code of that time.   In general terms, a set of patches is a set of instructions that allow editing (semi- or fully automatically) the source program to get a new version. A patch set is always smaller than the full code version. This turned patches into a convenient interface for transferring changes and collaborative programming. Problems arose when the developer community began to grow. Linus Torvalds became a "bottleneck"; the number of patches grew, and the time to review them increased. Developers began using the CVS version control system to ease collaboration. Of course, this went against Torvalds' original policy on Linux kernel changes. He disliked the existence of parallel project branches with their own workflow. On the other hand, developers felt frustrated sending patches to Torvalds, who physically could not review, accept, request fixes, or reject them in a timely manner. Developers complained they had to send multiple emails to get the "benevolent dictator's" attention. And if you’re looking for a reliable, high-performance, and budget-friendly solution for your workflows, Hostman has you covered with Linux VPS Hosting options, including Debian VPS, Ubuntu VPS, and VPS CentOS. The Emergence of Git The solution was to use a decentralized proprietary version control system called BitKeeper. The project used this software for a long time, but eventually, relations between the company developing BitKeeper and the Linux kernel developers soured. There was an amusing paradox: Linux Kernel is an open and free product licensed under the GNU General Public License (GPL). The main GPL principle is that anyone can freely use, distribute, and modify software released under this license, but all modifications must also be released under GPL. BitKeeper, however, was a fully closed proprietary commercial product owned entirely by its company.   Thus, the open and free project used a closed, non-free technology for coordinating development and versioning. Sooner or later, this fragile balance was going to break—and it did. This made using BitKeeper impossible. Torvalds rejected using Subversion and proposed Monotone instead. However, Monotone was unbearably slow. Eventually, Torvalds began writing his own version control system from scratch in C. Thus, Git was born. The new VCS was far from perfect but was positively received by the developer community and quickly gained the necessary tools. The new version control system rapidly gained popularity, and GitHub turned Git into the dominant solution for source code management in both open and commercial projects. Dominant... Indeed, any project, whether small or large (with thousands of contributors), is likely to be registered and hosted on GitHub. Even projects that don't use Git internally (like FreeBSD or OpenBSD) have read-only copies on GitHub. GitHub or Not GitHub? New developers (and not only them) tend to believe that without GitHub, project development and management are impossible. So, when you join a project as a developer (freelancer or FOSS contributor), you’ll be added to the team on this platform. Even if there are only two, three, or four of you... Even if the project consists of just a few dozen source files. GitHub everywhere. Is this good? It’s hard to answer simply yes or no. Certainly, GitHub has many useful tools; it’s convenient, fast, and reliable. Developers feel comfortable there, like in well-worn jeans. However, one should not forget that it’s a paid service managed by the well-known corporation Microsoft. Like any commercial product, GitHub is primarily focused on profit. If, for some reason, your project starts to interfere with that (damaging the platform’s image, etc.), your access will be instantly cut off. Recall the disputes GitHub had with the YouTube Downloader team, whose repositories were blocked, closed, and deleted simply because the RIAA demanded that GitHub restrict access to allegedly copyright-infringing software. This caused some (not a small number) teams to leave GitHub and switch to alternatives like GitLab or Gitea. In summary, setting aside moral and legal aspects, we see a contradiction: Git was designed as a decentralized version control system (unlike Subversion, for example), yet GitHub, which uses Git, enforces centralized management. Moreover, the developer effectively owns nothing; everything belongs to the "managing company." Is there life outside comfort? Can you use this great VCS without a third-party service? Can you accept patches without GitHub and send them to your team for review? Despite GitHub’s strong influence, Git’s architecture remains almost unchanged—it’s still a decentralized version control system. Git imposes absolutely no requirements on the exchange environment. You can use ordinary files (transfer them any way you want, even by copying to external media), upload patches to an FTP server, use SSH, or even Git’s built-in exchange protocol. This is very convenient. Recall the start of this article: Linus Torvalds accepted patches without GitHub (which didn’t exist then) by email and posted results on FTP servers. Sending Patches by Email Now, let's get to the main topic. Suppose we are a small, brave team that wants to be independent from anyone or anything. We have some money to buy a domain, VPS, and corporate email to exchange information and, of course, send and receive patches by email. Let's list tasks to build the necessary infrastructure for our project: Buy a domain. Buy corporate email and link it to our domain. Create mailboxes. Is it mandatory to buy a domain and corporate email? Not at all! You can use free mailboxes without a domain or purchase a domain later when needed. Everything depends on project requirements. However, from the early stages, the project may need a website, messaging (email), file exchange, and deployment infrastructure. You can buy these separately or combine them under one account for your project.  Suppose we are developing a web app and need infrastructure. After buying a domain and setting up DNS, we register as many mailboxes as needed. After creating mailboxes, we must configure access to them in mail clients and Git. Setting Up Git to Send and Receive Patches via Email It all starts with installing a special Git extension package called git-email. This is done using the package manager of your operating system or its distribution. For example: Fedora: sudo dnf install git-email Ubuntu / Debian: sudo apt-get install git-email On Windows, git-email is included in the standard Git installation package. Next step is configuration. In your OS terminal, run: git config --global --edit This will open your favorite terminal (or other) text editor, where you need to add the following lines to your Git configuration (the example uses test credentials; you should use your own!): [user] name = Maria Ortega email = zerozero@hostman-example.com [sendemail] smtpserver = smtp.hostman.com smtpuser = zerozero@hostman.site smtpencryption = ssl smtpserverport = 465 The parameter smtpencryption can be set to either ssl or tls. The second mode uses STARTTLS to initiate communication over an encrypted channel, while the first mode encrypts the connection immediately after it is established. The choice of mode and port depends on your email provider’s requirements. The [user] section is mandatory. Here, you identify yourself, and this information will appear in all patches and commits made by you. For stricter identification of patches and commits, Git supports signing sent information with GPG keys—but that’s another story. Now that we’ve set up Git to send patches via email let’s try it out. First, we need to clone a copy of the current working repository version. There are various ways to do this, which we’ll discuss at the end of the article. After cloning, make some changes to your project. Create a file named log_stderr.go: package main import ( "fmt" "time" "os" ) func logStderr(message string, args ...interface{}) { x := time.Now() fmt.Fprint(os.Stderr, x.Format(time.RFC822)) fmt.Fprint(os.Stderr, " - ") fmt.Fprintf(os.Stderr, message, args...) } Stage and commit the changes: git add log_stderr.go git commit -m "log into stderr func" Now send your patch to the project lead for review: git send-email --to="project-boss@hostman-example.com" HEAD^ The --to argument can accept multiple addresses separated by commas. This way, you can send your patch to all project members. You can also use --cc (carbon copy) to send the patch to additional email addresses separated by commas. This is useful when you want to send patches for review to the entire team or specific interested parties. To avoid specifying recipients every time on the command line, you can add them to your Git config: git config sendemail.to "project-boss@hostman-example.com" git config sendemail.cc "user1@email.tld","user2@email.tld",…,"userN@email.tld" After that, just run: git send-email HEAD^ …And your patch will be sent to the configured addresses. In this example, we sent the current changes from our working copy (HEAD^). You can send any changes, for example, two commits before the current one, or by commit hash. More details are in the Git documentation. Git will generate the patch and try to send it via the SMTP server specified in the config. If the SMTP server requires authentication, you’ll need to enter your password. If you send many patches, this can be tedious. You can save the password in the config, but note it will be stored unencrypted: git config --global sendemail.smtpPass 'your password' A better option might be to configure Git to cache your password for some time: git config --global credential.helper 'cache --timeout 3600' More advanced solutions can use password managers and the git-credential extension, but we won’t cover that here. Receiving and Integrating Patches Your team members receive your patch as a plain text email message, and they can review it—and, imagine that, reject your changes with requests to “fix” or “rewrite.” This is natural and the core of collaborative software development. The freedom and manual patch management are what attract developers to create their own information exchange solutions. What if You Are Asked to Fix Your Patch? Suppose developers ask to reduce calls to the Fprintf function and add a logging severity level. The updated code will look like this: package main import ( "fmt" "time" "os" ) type LogSeverity string const ( ERR LogSeverity = "ERROR" WARN LogSeverity = "WARN" INFO LogSeverity = "INFO" DEBUG LogSeverity = "DEBUG" ) func LogStderr(message string, severity LogSeverity, args ...interface{}) { x := time.Now() fmt.Fprintf(os.Stderr, "%s - %s - ", x.Format(time.RFC822), severity) fmt.Fprintf(os.Stderr, message, args...) fmt.Fprint(os.Stderr, "\n") } Since we’re fixing our previous patch and haven’t released any newer patches, we can simply amend the current commit: git commit -a --amend Now send the patch again, remembering we already configured the recipients: git send-email --annotate -v2 HEAD^ The -v2 flag means this is the second version of the patch. If you need another fix, use -v3, and so on. The --annotate flag allows you to add comments to your email message. Git will open a text editor showing something like: Subject: [PATCH v2] Logging function to stderr --- Added log level, reduced fmt.Fprintf calls Add your notes, save, and close the editor; the patch will then be sent again to the recipients. Always add annotations to your patches—it makes life easier for both you and your colleagues. Typing --annotate every time can get tedious, so you can automate it: git config --global sendemail.annotate yes How to Receive and Apply Patches? Receiving patches is a bit trickier. Git sends specially formatted patches in plain text email messages. There can be many such patches, and Git does not restrict the transport method (email, FTP, etc.), so it doesn’t handle how to receive patches—that’s up to the developer. Just use your mail client’s capabilities. After receiving approved annotated patches, save one or more email messages containing patches in an mbox file (Unix mailbox format). This format stores one or more email messages in a single file. Then run: git am <path_to_patches.mbox> All patches will be incorporated into your working copy. You can continue working and impressing your team. Email-based Git workflows can be as simple or sophisticated as you want. The main thing is that it suits the team and does not create unnecessary inconvenience. It seems there is nothing simpler, neater, or more elegant than working with Git over email. However, there is one major problem: distributing the working copy to new developers joining the project. If the project is large and has a rich history, the repository size might be many megabytes or even gigabytes. Sending that over email is impossible—it’s simply not designed for that. How to Provide a Newcomer with the Entire Project History? Git has an interesting feature called a bundle. It’s a snapshot of the working copy or the entire repository in a binary format of Git changes. Bundles are much more compact than a set of text patches; history and data inside the bundle are compressed, and the format allows transmitting both text and binary data. Project leads or other responsible persons can upload the current project bundle to a file-sharing service—for example, an FTP server or an S3-compatible object storage. The newcomer downloads the project bundle and clones it: git clone project.bundle <new_place> Now <new_place> contains a new working copy ready to work with email patches. However, to be honest, bundles are somewhat of an alternative to the patch email exchange workflow described above. Collaborative work using bundles is a different story.
07 July 2025 · 12 min to read
Git

Working with Git Tags

Git has been around for almost 20 years, yet it remains the most popular distributed version control system. It is best known for GitHub, the largest remote Git repository where developers store their code, document changes, and save previous versions. To help manage versions efficiently, Git provides special markers called tags. This article will explore what Git tags are and how to use them. What Are Git Tags? To understand Git tags, let's first clarify some related concepts. Commit: A commit is a saved version of a project. Branch: A collection of commits that visually represents the history of changes in a project. Multiple branches can exist simultaneously. Now, let’s define tags. Git tags are markers used to highlight important commits. They help track version history, as responsible developers often tag each new version. Like branches, Git tags point to a specific commit, but unlike branches, they do not have a history of commits. Now, let's see how to work with Git tags—create, view, publish, replace, switch, and delete them. How to Create Git Tags Git has two main types of tags: annotated and lightweight. Each is created differently. Creating Annotated Tags Annotated tags store complete version information, including developer names, emails, and timestamps. They are created using special Git flags, -a and -m, as shown in the example below: git tag -a ver-2.5 -m "beta version 2.5" git tag Output: ver-0.1 ver-1.6 ver-2.5 git tag is the main command for working with tags. -a creates an annotated tag with a specified identifier. -m adds a message. If omitted, a text editor will open for message input. To view details of an annotated tag along with its commit, use: git show ver-2.5 Output: tag ver-2.5 Tagger: Marianne Smith <m.smith@company.com> Date: Fri Mar 28 11:02:35 2025 beta version 2.5 commit bf93b7eaa928fd77a55453118313701b04874051 Author: James Brown <j.brown@company.com> Date: Mon Jan 6 09:41:02 2025 This displays the tagger's information, the commit hash, the author, and the creation date. To verify that the tag was created successfully, use: git tag -n Creating Lightweight Tags Lightweight tags are simple pointers to commits, typically used for temporary markers. They store only the commit’s hash. Here’s how to create one: git tag ver-2.5a git tag Output: ver-0.1 ver-1.6 ver-2.5 ver-2.5a ver-2.6 To view a lightweight tag's commit information: git show ver-2.5a Output: commit bf93b7eaa928fd77a55453118313701b04874051 Author: James Brown <j.brown@company.com> Date: Mon Jan 6 09:41:02 2022 -0300 Unlike annotated tags, lightweight tags do not store additional metadata. Adding and Deleting Git Tags in Remote Repositories To push a tag to a remote repository: git push origin ver-2.5 Here, origin refers to the default remote repository. To push all tags at once: git push origin --tags To delete a tag from a remote repository: git push origin --delete ver-2.5 To delete a tag locally (not on the remote repository): git tag -d ver-2.5 Switching Between Tags To switch to a specific tag: git checkout ver-2.5 However, this detaches the HEAD pointer, meaning any subsequent changes will not be associated with any existing branch. If you make changes, create a new branch to keep them: git checkout -b new-branch Viewing a List of Git Tags To list all available tags: git tag Output: ver-0.1 ver-1.6 ver-2.5 ver-2.5a ver-2.6 To filter tags using a pattern: git tag -l *xyz* If you have tags like ver-1.6xyz, ver-2.5xyz, and ver-2.6xyz, this command will output: ver-1.6xyz ver-2.5xyz ver-2.6xyz Reassigning or Replacing Tags To update an existing tag, use the -f flag for forced replacement: git tag -a -f ver-2.5a bf93b7eaa928fd77a55453118313701b04874051 This reassigns the tag to a specific commit hash. However, this will delete the old tag information, so use it carefully. Summary Git tags make version control more flexible and manageable. The commands covered here are simple yet powerful, making them easy to learn even for beginners. 
03 April 2025 · 4 min to read

Do you have questions,
comments, or concerns?

Our professionals are available to assist you at any moment,
whether you need help or are just unsure of where to start.
Email us
Hostman's Support