Git Essentials: A Pragmatic Approach to Version Control 🌿
Tags: git, github, webdev, tutorial
Published: 2023-11-12 19:24:50

## Introduction to Version Control

👋 To understand Git, having a foundational understanding of version control is essential. Version control, also known as source control, is the practice of tracking and managing changes to files over time. Version control systems are software tools that help software developers work in a team by enabling efficient collaboration and effectively managing changes to files in projects. By far, Git is the most widely used version control system in the world today.

**Introduction to Git** 

Git is an open-source distributed version control software originally developed in 2005 by Linus Torvalds. To get started with Git, download and install it from [git-scm.com](https://git-scm.com/). During installation, use the recommended settings and optionally select a default editor of your choice. Once installed, the next step is to create a Git repository.

**Creating a Git Repository**

A Git repository is a directory whose contents are tracked by Git.  As a good practice, always execute `git status` to confirm you are not currently within an existing repository before initializing one.  Utilize the `git init` command to create a new Git repository or convert an untracked project into one.

**Working with remote Repositories**

Git repositories can be either local or remote. When collaborating on an existing project hosted on a remote server, you can duplicate the repository in your local directory. The process of creating this local copy is known as cloning. Here's an example:

```git
git clone https://github.com/facebook/react.git
```

## Staging and Committing changes

Once your repository is cloned or initialized, add a file or make some changes to existing files to understand some of the most important concepts in Git, staging and committing.

**Committing: Saving a Snapshot of Your Codebase**

Staging is a step that comes before committing, but before diving into it, it's helpful to first understand what committing entails and what problems it addresses.

Imagine investing considerable time and effort into developing a feature, only to encounter an unexpected event just before completion, potentially resulting in the loss of your code changes. While periodic file-saving provides some protection, it may not be foolproof. This is where Git comes into play. Committing in Git serves as a mechanism to save and undo changes as well as offers the ability to restore previous code versions.

Committing essentially acts like taking a snapshot of your codebase at a specific moment. By committing changes, you create a record of your codebase's state, providing a reference point for future use. This practice ensures the safety and recoverability of your work, allowing you to navigate through different versions of your codebase with confidence

**Staging: The Pre-commit Step**

So, where does staging fit into this? 🤔 Staging plays a crucial role in the Git workflow, enabling you to specify which local changes should be included in the next commit. When you stage changes, you effectively add modified files to the staging area, preparing them for inclusion in a commit.

It's essential to understand that staging itself does not make any permanent changes to the repository—only committing records the changes permanently. <u>To maintain clear and organized commits, it's a good practice to group related changes together when committing.</u>

Here's an example of adding a file to the staging area and performing a commit with a message:


```git
// Add a file to the staging area
   git add <filename>

// To commit the changes to the repository with a message
   git commit -m "Added a new file to the project"
```

## Branching in Git

One of the most important features of a version control system is its capability to create branches, allowing developers to simultaneously work on different versions of the code. 

A branch represents a distinct version of the codebase derived from a specific point in the repository's history. This new version, essentially a snapshot of the code at the time of branching, enables developers to work on new features, experiment with code changes, or address bugs without impacting the main codebase until ready for integration.

The master branch is the default branch created upon initializing a repository. Typically, it functions as the primary branch where all changes eventually merge from other branches. While the master branch is commonly employed for production code, its usage can vary based on the team's development workflow and practices.

The `git branch` command offers various functionalities, including creating, listing, renaming, and deleting branches. To create a new branch based on the current working branch, simply execute the following command:

```git
git branch branch-name
```


## Navigating between branches

The `git checkout` command provides a way to switch between branches. However, it's worth noting that this command has multiple uses, and hence it is a good practice to use the newer, safer and appropriately named `git switch` command.

```git

git checkout feature-branch

git switch feature-branch

```

Now let's see how we can navigate between branches without having to discard local changes…

**Stashing**

Stashing is used to record the current state of the working directory and go back to a clean working directory. The command saves your local modifications away and reverts the working directory to match the `HEAD` commit.

> Quick note - (In Git, the HEAD typically points to the latest commit on the currently checked-out branch. In a detached HEAD state, it directly references a specific commit rather than a branch. To reattach the HEAD, simply switch to a branch. To discard changes made in this state, checkout to an existing branch. Commits in a detached HEAD state won't impact the existing branch and are archived by Git. To retain changes, create a new branch, and the HEAD will no longer be detached)

Stashing is handy when you need to quickly switch between branches and work on something else, but you're mid-way through a code change and aren't quite ready to commit.<u> A commit is part of the public Git history, meaning that other collaborators can see your commits when they fetch or pull changes from the repository. In contrast, a stash is stored locally on your machine and is not automatically shared with others.</u>

The most common git stash options include:

```git
git stash push // creates a new stash, rolling back file states.

git stash pop // restores stash files to the workspace and deletes the stash.

git stash apply // restores stash files to the workspace without deleting the stash.

git stash list // shows stash history chronologically.

git stash clear // removes all stash entries.
```

## Undoing and managing history

Git provides multiple ways to view and amend history. You might want to amend history in case something goes wrong or if you want to clean up unnecessary commits and have a clean history.

**Git restore**

The `git restore` command is used to unstage or discard uncommitted local changes. It can undo the effects of `git add` with the `--staged` argument, allowing you to unstage changes previously added to the Staging Area. Additionally, it can discard local changes in a file, reverting it to its last committed state."

**Git reset**

The `git reset` command resets the repository back to the specified commit. Changes from commits since the specified commit will be preserved as unstaged changes, allowing you to take further action as needed. It also offers a more forceful option, `--hard commit-hash`, which not only removes commit entries but also discards all changes made since the specified commit.

**Git revert**

Another useful command related to undoing history is the `git revert` command. It creates a brand new commit that reverses the changes from the provided commit. For this reason, git revert is commonly recommended for undoing changes on a public branch to avoid confusion and potential conflicts when pulling changes. On the other hand, `git reset` should be reserved for undoing changes on a private branch. 


## Collaborating with other developers

In a professional environment, your source code is likely to be stored in a remote repository that is connected to your local repository. Remote repositories are hosted on platforms like GitHub, GitLab etc. Developers work together by regularly pushing and pulling changes to and from the remote repository. This allows multiple people to work on the same codebase and share their contributions. The remote repository acts as a central location for the team to keep their code and collaborate efficiently.

**Git Push**

Pushing is the process of uploading the content of a local repository to a remote repository. It facilitates the transfer of commits from a local repository to a corresponding remote one, such as on GitHub. Git allows you to set the upstream of the branch being pushed, creating a linkage between the local branch and its counterpart on the remote repository. Once the upstream is established for a branch, you can conveniently use a shorthand command to push the current branch to its upstream, eliminating the need to specify the remote or branch explicitly.

Quick note - (Upstream refers to the original repository or branch from which a clone or fork was created. For example, when you clone a repository from GitHub, the remote GitHub repository is considered the upstream for the local copy. Pushing changes to a branch that doesn't exist remotely creates a new branch on the upstream repository. The terms 'origin' and 'upstream' are interchangeable, representing the repository from which the project was cloned. Typically, 'origin' is set as the default remote name during cloning, but it's a convention and can be customized to any preferred name)

Here is an example of how to push commits to a remote repository, set the upstream of the branch, and use the shorthand command:


```git
# Push local commits to the remote repository
git push <remote> <branch>

# Set the upstream of the branch
git push -u <remote> <branch>

# Push to the upstream using shorthand
git push
```

**Git Pull**

The `git pull` command is used to download commits and other changes from a remote repository and immediately update the local repository to match that content. It's a common task in collaborative Git-based workflows, as it allows you to synchronize your local repository with the remote one.

When you use `git pull`, git will try to merge the changes automatically. But if there are conflicts between the local and remote changes, it will stop the process and ask you to resolve the conflicts before proceeding with the merge.

<u>It's considered a good practice to pull the changes from the remote repository before pushing your own changes</u>. This way you'll avoid conflicts and ensure that your local repository is up-to-date with the remote one.

**Git Fetch**

The `git fetch` command downloads commits, files, and references from a remote repository. However, these changes are not immediately integrated into the working files. To view the downloaded content, you must explicitly switch to the specific remote branch.

Fetching provides a way to safely review commits without automatically merging them into your local repository, as opposed to the `git pull` command, which combines `git fetch` with `git merge` to automatically integrate changes into your local branch.

Here is an example of how to fetch commits from a remote repository and review the changes:


```git
# Fetch commits from the remote repository
git fetch origin feature

# Check out the fetched branch
git checkout origin/feature
```

## Merging and Rebasing branches

**Merging**

Merging serves as a method to combine branches and their respective histories, a common practice in Git, especially in workflows like the feature branch model. In this approach, developers create a new branch for each feature, enabling them to work on the feature independently of the main development line.

Throughout development, updates from the primary branch, often named main or master, are regularly merged into the feature branch, ensuring it stays current with the latest changes.

Upon completing the feature, the feature branch undergoes a merge back into the primary branch, integrating the changes into the main development line.

Merge commits are distinctive, having two parent commits—one from the current branch and the other from the branch being merged. Git attempts to automatically merge the separate histories during a merge commit. However, if a merge conflict arises, indicating conflicting changes in both histories, Git requires user intervention to resolve the conflict.

**Rebasing**

Rebasing, a specialized Git utility, enhances the integration of changes between branches, offering an alternative to merging.

The process involves moving or "replaying" a sequence of commits from one branch onto the tip of another. By updating the target branch with the latest commits from the source, it effectively "replays" these commits on top of the target branch, facilitating the incorporation of new changes while maintaining a linear history.

In the context of a feature branching workflow, git rebase is particularly valuable. It enables the integration of changes from the main branch into a feature branch, preserving a linear commit history. This choice over git merge aids in presenting a clearer development flow without generating additional merge commits.

It's essential to note that rebasing alters commit history, making it advisable to avoid if the branch has been shared or if changes have been pushed to a public branch.

Rebasing also provides the flexibility to add or drop commits before a specified commit hash. When executed in interactive mode, Git opens a text editor, displaying a list of commits within the specified range and a set of commands (pick, reword, edit, squash, and drop) for user selection.

## GitHub and contributing to open source

GitHub is a platform that hosts your repositories and aids in collaboration on software projects. GitHub provides you with a safe space to store your code so that even in a worst-case scenario such as a system failure, you will always have a backup of your code base.

**Pull Request**

A pull request is a feature provided by GitHub (and other web-based Git hosting services), it is not a native feature of Git. A pull request enables you to notify others about changes that you have pushed to a branch in a repository and request that they merge your changes into the main branch.

Upon opening a pull request, you can include a description of the changes and the rationale behind them. Collaborators on the repository have the opportunity to review the proposed modifications, engage in discussions, and leave comments. While the pull request is active, you can continue making additional commits to the branch to address any feedback received.

Pull request is a powerful feature that allows developers to collaborate and review code changes before they are merged into the main branch. It's often used in Git-based workflows to facilitate code review, testing and quality assurance processes.

**Forking**

A fork is a copy of a repository that you have control over, it can be thought of as your own personal version of the original repository. Forks allow you to make changes to a project without affecting the original repository.

In most cases, open-source projects do not allow contributors to push changes directly to the original repository. Instead, contributors are expected to fork the repository, make changes to their own copy, and then submit a pull request to the maintainers of the original repository.

When you fork a repository on GitHub, it creates a copy of the repository under your own account, which you can then clone to your local machine. Once you've cloned the repository, you will need to add another remote to your local copy, this remote should point to the original repository that you cloned from. This is necessary so that you can fetch the latest commits from the original repository and keep your fork up-to-date.

You can then make changes to your local copy of the repository, commit your changes, and push them to your fork on GitHub. Once you're ready to submit your changes, you can create a pull request to request that the maintainers of the original repository review and merge your changes.

The following example demonstrates how to add a new remote named upstream that points to the original repository:

```git
git remote add upstream https://github.com/facebook/react.git
```

PS - The cover image features a dog playing fetch with a branch (twig). Get it?

Resources :
https://git-scm.com/book/en/v2
https://nvie.com/posts/a-successful-git-branching-model/
https://www.atlassian.com/git/tutorials/comparing-workflows

Song of the day : Saturnus - I long

{% embed https://youtu.be/NlXENjvDsqc?si=QrBpuPFU7l3RFyiG %}