Master Git & GitHub for Collaborative Development
This comprehensive course takes you from the absolute basics of Git to advanced workflows, including branching, remote repositories, and effective collaboration using GitHub. You will learn to track changes, manage your project's history, and work seamlessly with a team. Git is an essential tool for every developer, and this course will give you the skills to use it with confidence.
Module 1: Git Fundamentals
Lesson 1: Version Control Concepts
Before diving into Git, it's crucial to understand why version control is so important. Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. It allows you to revert files back to a previous state, revert the entire project back to a previous state, compare changes over time, see who last modified something that might be causing a problem, and much more. It also provides a way to collaborate with other developers without overwriting each other's work.
There are two primary types of version control systems (VCS): Centralized and Distributed.
Centralized Version Control Systems (CVCS)
In a CVCS, a single server contains all the project files and historical versions. Developers "check out" files from this central repository, make changes, and then "check in" their new versions. The main drawback is the single point of failure: if the central server goes down, no one can collaborate or save versioned changes. Examples include Subversion (SVN) and Perforce.
Distributed Version Control Systems (DVCS)
Git is a DVCS. In a DVCS, every developer has a full copy of the entire repository, including its complete history. This means every local repository is a full backup of the entire project. This offers several key advantages:
- Redundancy: If the central server fails, any developer's local repository can be used to restore the project.
- Speed: Most operations, like committing and Browse history, are performed locally, making them significantly faster.
- Flexibility: You can work offline and sync your changes later. It also allows for more complex workflows like branching and merging without affecting the main codebase until you're ready.
Git's design is fundamentally different from older systems. It doesn't just store the differences between files; it stores a snapshot of what all your files look like at a given point in time. This makes operations like reverting and comparing versions much more efficient.
Why Git?
Git has become the de facto standard for version control in modern software development. Its benefits are numerous:
- Performance: Git is incredibly fast.
- Strong Branching Model: Git's lightweight branching makes it easy to experiment with new features without disrupting the main project.
- Data Integrity: Git's use of cryptographic hashing ensures that the repository history cannot be corrupted or altered without Git detecting it.
- Flexibility: It supports both simple and complex, non-linear workflows.
- Community: With platforms like GitHub, GitLab, and Bitbucket, Git is at the center of a massive ecosystem of tools and services.
This lesson sets the stage for understanding Git's power and how it will transform the way you manage and collaborate on your projects. By the end of this course, you will be proficient in leveraging these concepts to become a more effective developer.
Module 1: Git Fundamentals
Lesson 2: Git Installation and Configuration
To begin our journey with Git, the first step is to install it on your system and perform a basic configuration. Git is available on all major operating systems, and the installation process is straightforward. We'll cover the steps for Windows, macOS, and Linux to ensure you're ready to go.
Installing on macOS
The easiest way to install Git on macOS is to install Xcode Command Line Tools. If you don't have Git installed, opening a terminal and typing git --version
will prompt you to install them. Alternatively, you can use the package manager Homebrew:
brew install git
Installing on Windows
The most popular method for Windows users is to download the Git for Windows installer from the official Git website. This installer includes Git Bash, a terminal emulator that gives you a Unix-like environment, which is highly recommended for following along with this course. During the installation, you can accept most of the default options, but make sure to select "Use Git from the Windows Command Prompt" if you plan to use other terminals like PowerShell or CMD.
Installing on Linux
For Debian-based systems like Ubuntu, you can use apt
:
sudo apt update
sudo apt install git
For Fedora and other Red Hat-based systems, use dnf
or yum
:
sudo dnf install git
Initial Configuration
After installation, you must configure your user name and email. Git uses this information to identify who made each commit. These settings are stored globally for all your projects. To set your name and email, open your terminal and run the following commands, replacing the example values with your own:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
The --global
flag ensures these settings apply to all Git repositories you work with. You can verify your settings by running:
git config --list
This command will display all your Git configuration settings, including your user name and email. With these steps completed, you now have a fully functional Git environment and are ready to start creating your first repository. This foundational setup is a one-time process and is essential for all future Git operations. Getting this right from the start prevents a lot of headaches down the line, as Git will now correctly attribute your work to you.
Try It Yourself: Verify Installation
Open your terminal or command prompt. Type git --version
and press Enter. This should display the Git version you just installed. Then, configure your global user name and email. Verify the settings using git config --list
.
Module 1: Git Fundamentals
Lesson 3: Repository Initialization
A Git repository, or "repo," is a special folder where Git stores all the information about your project's history, including every file change, commit message, and branch. It's the core of version control. The process of creating a new repository is called initialization. You can initialize a new repository in an existing project folder or create a new one from scratch.
Initializing a New Repository
To initialize a new repository, navigate to your project folder using your terminal. For example, if you have a folder named "my-awesome-project," you would use the following command:
cd my-awesome-project
Once inside the project directory, run the git init
command. This command creates a hidden .git
subdirectory inside your project folder. This is where all of Git's magic happens—it contains the repository history, branches, and configuration. You won't typically need to interact with files inside this directory directly.
git init
You can verify that the .git
folder was created by listing all the files, including hidden ones, in your directory. In most terminals, the command for this is ls -a
(on macOS/Linux) or dir /a
(on Windows).
Understanding the Working Directory and the Staging Area
When working with Git, you operate in three main states:
- Working Directory: This is the folder on your computer that you're currently working in. It contains the files you are actively creating and modifying. When you first initialize a repository, all the existing files are considered "untracked" by Git.
- Staging Area (or Index): The staging area is a middle ground between your working directory and your repository. It's where you prepare a snapshot of changes before you commit them. You use the
git add
command to move changes from the working directory to the staging area. This allows you to select which changes you want to be part of your next commit. - Git Repository: This is the local database where Git permanently stores the snapshots of your project's history. When you commit, Git takes the changes from the staging area and permanently records them as a new version.
This three-stage process is one of Git's most powerful features. It gives you fine-grained control over what gets included in each commit. This is crucial for creating clean, logical, and meaningful commits, which is a cornerstone of good version control practice. By understanding this workflow now, you'll be well-equipped to manage your changes effectively in the upcoming lessons.
Try It Yourself: Initialize a Repo
Create a new empty folder named my-first-repo
. Navigate into it using your terminal. Run git init
to initialize a new repository. Then, create a new file, for example, README.md
, and add some text to it. The next lesson will show you how to start tracking this new file.
Module 1: Git Fundamentals
Lesson 4: Staging and Committing Changes
Now that you have a Git repository, it's time to start tracking your project's progress. Git's core function is to take snapshots of your project at different points in time. These snapshots are called "commits." A commit is like a save point in a video game—a moment you can always return to. Before you can commit, you need to stage your changes.
Staging Your Changes
As we learned in the previous lesson, the staging area is where you prepare changes for your next commit. To add files to the staging area, you use the git add
command. This command is very flexible. You can add a single file, a directory, or all modified files at once.
To add a single file, for example, index.html
:
git add index.html
To add multiple specific files, you can list them:
git add index.html style.css
A common shortcut to stage all modified and new files in your project is to use the dot (.
):
git add .
It's important to remember that git add
doesn't permanently save your changes; it just moves them to the staging area, preparing them for the next commit. This gives you the power to create very specific, logical commits. For example, you might make a change to a JavaScript file and a separate change to a CSS file. You could choose to commit the JavaScript changes first, and then the CSS changes later, to keep your commit history clean and easy to follow.
Committing Your Changes
Once your changes are in the staging area, you can permanently record them in your repository using the git commit
command. Every commit requires a commit message. A good commit message is a short, meaningful description of the changes you've made. It helps you and your team understand the history of the project at a glance.
To create a commit, use the -m
flag to provide a message:
git commit -m "Add initial project files"
If you forget the -m
flag, Git will open your default text editor, allowing you to write a more detailed, multi-line commit message. Best practice is to have a short summary on the first line, followed by a blank line, and then a more detailed description of the changes.
After a successful commit, the staging area is cleared, and the new snapshot is permanently saved in your repository's history. You have now created your first save point. You can repeat the cycle of modifying files, staging them with git add
, and committing them with git commit
as often as needed to track your project's development.
Try It Yourself: Create Your First Commit
In your my-first-repo
, create a new file named hello.txt
with some content. Add it to the staging area with git add hello.txt
. Then, commit it with the message "Add hello.txt file". After that, modify the content of hello.txt
. Now, add and commit the changes with a new message like "Update content in hello.txt".
Module 1: Git Fundamentals
Lesson 5: Git Status and History
As you work on a project, it's essential to know the current state of your files. Git provides powerful tools to help you do just that. The git status
command is your go-to for checking the status of your working directory and staging area, while the git log
command lets you browse your project's commit history.
Using git status
The git status
command is one of the most frequently used Git commands. It tells you which branch you're on, which files have been changed, which are staged for the next commit, and which are untracked. Here's a breakdown of the different outputs you might see:
- On branch master/main: This indicates the current branch you're working on.
- Changes to be committed: This section lists the files that have been added to the staging area with
git add
. These files are ready to be included in your next commit. - Changes not staged for commit: These are files that you have modified in your working directory but have not yet staged with
git add
. - Untracked files: These are new files in your working directory that Git is not yet tracking. You can add them to the staging area using
git add
.
Running git status
frequently is a good habit. It helps you stay organized and ensures you only commit the changes you intend to. It also serves as a reminder of what work you've done since your last commit.
Exploring History with git log
The git log
command shows a chronological history of your commits. By default, it displays each commit's unique ID (a SHA-1 hash), the author, the date, and the commit message. This provides a clear record of the project's development. A typical output looks like this:
commit a1b2c3d4e5f67890...
Author: Your Name
Date: Mon Jan 1 10:00:00 2025 -0500
Add initial project files
There are many useful flags you can use with git log
to customize the output:
git log --oneline
: This shows a concise view of the history, with one commit per line.git log --graph
: This creates an ASCII art graph of the branch and merge history.git log -p
: This shows the full diff (the exact changes) for each commit, which is incredibly useful for reviewing what was changed.git log --author="Your Name"
: This filters the history to show only commits made by a specific author.
These commands are your key to understanding the past of your project. Being able to quickly check your status and review your history is fundamental to using Git effectively. It allows you to confidently move forward with new changes, knowing you can always look back or revert if necessary.
Try It Yourself: Check Status and Log
In your repository, create a new file, modify an existing one, and then run git status
. Observe how Git categorizes each change. Then, add one of the files to the staging area with git add
and run git status
again to see the difference. Finally, run git log
and git log --oneline
to see your commit history.
Module 2: Branching and Merging
Lesson 1: Creating and Switching Branches
Branching is one of Git's most powerful and essential features. It allows you to diverge from the main line of development and continue to work on something new without affecting the main codebase. Think of a branch as a lightweight, movable pointer to one of your commits. When you create a new branch, you're essentially creating a new timeline for your project, allowing you to experiment freely.
The Master/Main Branch
By default, your repository has a single branch, usually named master
or main
. This is considered the primary, stable version of your project. As a best practice, you should rarely work directly on this branch. Instead, you create new branches for features, bug fixes, or experiments.
Creating a New Branch
To create a new branch, you use the git branch
command followed by the name of your new branch. For example, to create a branch called feature-login
:
git branch feature-login
This command only creates the branch; it doesn't switch you to it. You'll still be on your original branch (e.g., main
). To see all the branches in your repository, you can simply run git branch
without any arguments. The currently active branch will be highlighted with an asterisk (*
).
Switching Between Branches
To switch to the new branch you just created, you use the git checkout
command. This command changes your working directory to match the state of the branch you are switching to. To switch to the feature-login
branch, you would run:
git checkout feature-login
Now, any new commits you make will be added to the feature-login
branch, leaving the main
branch untouched. This is the core concept of Git branching: a safe and isolated environment to work in.
A Common Shortcut
There's a very convenient shortcut to both create a new branch and switch to it in a single command. The -b
flag with git checkout
does this for you:
git checkout -b feature-login
This command is a staple in a developer's workflow. It ensures that you create a new branch and immediately start working on it, preventing accidental commits to the main branch. Once you're on a new branch, you can make your changes, stage them, and commit them just like before. This allows you to develop features independently, which is the foundation of modern team collaboration.
Try It Yourself: Branching
Starting from your main
branch, create a new branch called add-contact-form
using the shortcut command. Add a new file named contact.html
and commit it. Then, switch back to the main
branch. You will notice the new file disappears from your working directory, demonstrating the power of isolated branches.
Module 2: Branching and Merging
Lesson 2: Merging Strategies
After you've successfully developed a feature on its own branch, the next logical step is to integrate those changes back into the main branch of your project. This process is called merging. Git offers different ways to merge, but the most common one is a "fast-forward" merge or a "three-way" merge. Understanding these will help you manage your project's history effectively.
The Fast-Forward Merge
A fast-forward merge happens when the target branch (e.g., main
) has not had any new commits since you created your feature branch. In this case, Git simply moves the pointer of the target branch forward to the same commit as your feature branch. It's a "fast-forward" because Git doesn't have to resolve any changes; it just moves the pointer. The history remains a straight line.
Here's the process:
- First, make sure you are on the branch you want to merge into. In this case, the
main
branch. - Then, use the
git merge
command with the name of the branch you want to merge.
# Make sure you are on the main branch
git checkout main
# Merge the feature branch into the main branch
git merge feature-login
If a fast-forward is possible, Git will simply move the main
branch pointer to the latest commit on the feature-login
branch.
The Three-Way Merge
A three-way merge occurs when the target branch has new commits that the feature branch does not have. In this scenario, Git needs to find a common ancestor of the two branches and then combine the changes from both timelines. The result is a new "merge commit" that has two parents: the last commit of the main
branch and the last commit of the feature-login
branch. This creates a non-linear history.
The commands are the same as for a fast-forward merge:
git checkout main
git merge feature-login
However, the outcome is different. Git will create a merge commit to tie the two histories together. This type of merge is the most common and results in a more descriptive and accurate history of your project's branching. After a successful merge, you can safely delete the feature branch, as its changes have been fully integrated.
Deleting a Branch
Once a branch has been successfully merged, it's good practice to delete it to keep your repository clean. You can do this with the -d
flag:
git branch -d feature-login
Git will prevent you from deleting a branch that has unmerged changes, which is a helpful safety feature. The -D
flag can be used to force the deletion of an unmerged branch, but this should be used with caution.
Try It Yourself: Perform a Merge
Following the previous exercise, ensure you are on the main
branch. Create a new commit on the main
branch. Then, switch to your add-contact-form
branch and make another commit. Now, switch back to main
and merge add-contact-form
. Observe the commit history with git log --oneline --graph
to see the new merge commit.
Module 2: Branching and Merging
Lesson 3: Resolving Merge Conflicts
While merging is generally a smooth process, it's common to encounter a "merge conflict." This happens when Git cannot automatically combine changes from two different branches because the same line of code in the same file has been modified in both branches. Git pauses the merge process and leaves it to you to manually resolve the conflict.
Identifying a Conflict
When you run git merge
and a conflict occurs, Git will inform you in the terminal with a message similar to this:
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Git also modifies the conflicted file to show you where the problem is. It will insert special markers into the file, indicating the different versions of the code. The markers look like this:
<<<<<<< HEAD
<h1>Welcome to My Website</h1>
=======
<h1>Welcome to My Awesome Website</h1>
>>>>>>> feature-login
The code between <<<<<<< HEAD
and =======
is the version on your current branch (HEAD
is a pointer to the tip of the current branch). The code between =======
and >>>>>>> feature-login
is the version from the branch you're trying to merge (feature-login
). Your task is to edit this file, choose the correct version (or a combination of both), and remove all the conflict markers.
Resolving the Conflict
To resolve the conflict, you must manually edit the file. In the example above, you might decide that you want to keep the change from the feature-login
branch, so you would edit the file to look like this:
<h1>Welcome to My Awesome Website</h1>
Once you've made your decision and removed all the conflict markers from all the conflicted files, you need to tell Git that you're finished. You do this by staging the resolved file with git add
and then committing the merge.
# After manually editing all conflicted files
git add index.html
# Now commit the merge
git commit -m "Merge branch 'feature-login' after resolving conflict"
Git will automatically provide a default commit message for the merge, but you can always edit it to be more descriptive. After the commit, the merge is complete, and your repository history reflects that a conflict was resolved during the merge. The ability to handle merge conflicts is a fundamental skill for any developer working in a team environment. With practice, you will find that these conflicts are a normal part of the development process and can be resolved efficiently.
Try It Yourself: Create and Resolve a Conflict
On your main
branch, edit a line in a file and commit it. Switch to your add-contact-form
branch, edit the exact same line differently, and commit it. Now, switch back to main
and try to merge add-contact-form
. You will get a conflict. Manually resolve the conflict, stage the file, and commit the merge.
Module 2: Branching and Merging
Lesson 4: Rebasing and Cherry-picking
While merging is the most common way to combine branches, Git offers alternative methods that can result in a cleaner, linear project history. Two of these advanced techniques are rebasing and cherry-picking. These commands give you more control over your commit history, but they should be used with caution, especially in a team setting.
What is Rebasing?
Rebasing is the process of moving or combining a sequence of commits to a new base commit. The primary goal of rebasing is to create a clean, linear history. Instead of using a merge commit to tie two branches together, rebasing rewrites the history of your feature branch to make it look like you started from the latest commit of the main branch.
The command to rebase a feature branch onto the main
branch looks like this:
# Make sure you are on your feature branch
git checkout feature-branch
# Rebase the feature branch on top of main
git rebase main
This command takes all the commits on your feature-branch
that are not on main
, "unapplies" them, updates your branch to the latest commit from main
, and then "reapplies" your feature commits one by one on top. The result is a history that looks like you did all your work after the most recent main
commit.
The golden rule of rebasing is: **never rebase a public branch.** Rebasing rewrites history, which can cause significant problems for collaborators who have already fetched the original history. Only rebase on your own, local branches that no one else has seen.
What is Cherry-picking?
Cherry-picking is the act of picking a specific commit from one branch and applying it to another. This is useful when you only need a single commit from a branch, rather than the entire branch's history. It allows you to selectively apply changes without merging the entire branch.
The command to cherry-pick a commit requires you to know the commit's unique ID (hash). You can find this hash using git log
.
# Get the commit hash from the source branch
git log --oneline
# Switch to the target branch
git checkout target-branch
# Cherry-pick the specific commit
git cherry-pick a1b2c3d
Cherry-picking creates a new, identical commit on your current branch. This can be useful for things like hotfixes, where you need to apply a single bug fix to a release branch without merging the entire feature branch it came from. Both rebasing and cherry-picking are powerful tools for managing your commit history, but they require a solid understanding of Git and should be used with purpose and care.
Try It Yourself: Rebasing vs. Merging
Create two branches, rebase-demo
and merge-demo
, both from the latest main
commit. On main
, make a new commit. Now, on the merge-demo
branch, make a commit and merge it back to main
. Observe the merge commit in git log --graph
. Next, on the rebase-demo
branch, make a commit and rebase it onto main
. Observe the linear history. This will clearly illustrate the difference between the two approaches.
Module 2: Branching and Merging
Lesson 5: Branch Management Best Practices
Effective branch management is crucial for keeping a project organized and for facilitating smooth collaboration, especially in a team environment. Adopting a clear strategy for naming, creating, and deleting branches can prevent confusion and errors. This lesson covers some of the most widely accepted best practices for working with branches.
Clear Branch Naming Conventions
A good branch name should be descriptive and immediately tell you what the branch is for. Generic names like "my-work" or "bug-fix" are unhelpful. A common convention is to use a prefix that indicates the type of work being done, followed by a short, descriptive name.
- Feature branches:
feature/new-homepage-design
orfeat/user-auth
- Bug fix branches:
bugfix/fix-login-error
orhotfix/critical-api-bug
- Release branches:
release/v1.2.0
- Refactoring branches:
refactor/restructure-css
Using a consistent naming scheme makes it easy for all team members to understand the purpose of each branch without needing to ask. It also helps to prevent naming collisions.
Short-Lived Branches
Long-lived branches that exist for weeks or months often lead to complex merge conflicts. It is a best practice to create branches for small, self-contained features or bug fixes. These branches should be merged into the main codebase as soon as they are complete and stable. This approach keeps the main branch healthy and up-to-date, minimizing the risk of a developer getting "stuck" on an out-of-date branch and having to deal with massive merge conflicts later on.
Deleting Merged Branches
Once a feature branch has been successfully merged, it no longer serves a purpose. It's good practice to delete it. Keeping a cluttered list of old branches makes it harder to find what's important and can slow down some Git operations. The git branch -d <branch-name>
command is the standard way to do this. Git will prevent deletion if the branch has not been merged, acting as a safety net.
# Check all branches, including remote ones
git branch -a
# Delete a local branch after it has been merged
git branch -d feature/new-homepage-design
The Role of a Main Branch
Whether you call it master
, main
, or develop
, the primary branch of your repository should always be kept in a working, deployable state. All new work should be done on a separate branch and only merged into the main branch after it has been thoroughly tested and reviewed. This prevents broken code from getting into the production environment. These best practices are fundamental to building a reliable and efficient workflow with Git, especially when collaborating with others.
Try It Yourself: Clean Up Your Branches
List all your current local branches. Then, go back to the main
branch and run the git branch -d
command on the add-contact-form
branch you created and merged earlier. Observe how the branch is removed from your local repository. Practice this process on any future branches you create and merge.
Module 3: Remote Repositories
Lesson 1: Remote Repository Concepts
Up until this point, we have been working with Git entirely on our local machines. This is great for personal projects, but the true power of Git is its ability to facilitate collaboration among multiple developers. This is where remote repositories come in. A remote repository is a version of your project hosted on the internet or a network, such as on GitHub, GitLab, or Bitbucket. It acts as a central hub where all developers can share their changes and stay synchronized.
What is a Remote?
A "remote" is simply a bookmark for a remote repository. The name you use for a remote is just a convenient alias for a long URL. By default, when you clone a repository, Git automatically names the remote "origin." This is a convention, and you can add or change remote names as needed.
Using a remote allows you to do the following:
- Share your work: You can push your local commits to the remote repository, making them available to others.
- Retrieve others' work: You can pull or fetch changes that other developers have pushed to the remote.
- Have a backup: The remote repository serves as a reliable backup of your project's history.
Adding a Remote
To add a new remote to your local repository, you use the git remote add
command. You need to provide a name for the remote (e.g., origin
) and the URL of the remote repository.
git remote add origin https://github.com/user/repo-name.git
You can see a list of your configured remotes by running git remote -v
. The -v
flag stands for "verbose" and will show you the URLs for both fetching (downloading) and pushing (uploading) changes.
git remote -v
This command will output something like this:
origin https://github.com/user/repo-name.git (fetch)
origin https://github.com/user/repo-name.git (push)
Understanding remote repositories is the gateway to collaborative development. In the upcoming lessons, we will explore the key commands for interacting with these remotes, such as pushing your local changes, pulling updates from others, and fetching the latest history without merging.
Try It Yourself: Create and Add a Remote
Go to GitHub, create a new empty repository, and copy its URL. In your local my-first-repo
, use git remote add origin <your-repo-url>
to link it. Then, run git remote -v
to verify the connection.
Module 3: Remote Repositories
Lesson 2: Push, Pull, and Fetch Operations
With a remote repository configured, you can now send and receive changes, making collaboration possible. The three most fundamental commands for interacting with a remote are push
, pull
, and fetch
. Each serves a specific purpose in a collaborative workflow.
Pushing Your Changes
The git push
command is used to upload your local commits to the remote repository. When you push, Git sends your committed changes to a specified remote branch. The general syntax is:
git push <remote-name> <branch-name>
For example, to push your main
branch to the origin
remote:
git push origin main
The first time you push a new local branch, you might need to use the --set-upstream
flag (or its shorter form, -u
) to tell Git to link your local branch to the remote one. This sets up a tracking relationship so that subsequent pushes and pulls can be done with a simple git push
or git pull
.
git push -u origin feature-login
Fetching Changes
The git fetch
command downloads new data from a remote repository without integrating it into your local working copy. It retrieves all the branches and commits from the remote that you don't have yet. This allows you to see what changes others have made without touching your local work. It's a "safe" way to check for updates.
git fetch origin
After a fetch, the remote branches will be available in your local repository as origin/main
, origin/feature-login
, etc. You can then use git diff
or git log
to compare your local branch with the remote one.
Pulling Changes
The git pull
command is a shortcut that combines two actions: a git fetch
followed by a git merge
. It downloads all the changes from the remote repository and then automatically merges them into your current local branch. It's the most common way to get up-to-date with a team's progress.
git pull origin main
If there are no conflicts, the pull will be a fast-forward merge. If there are conflicts, Git will stop and require you to resolve them manually, just like with a local merge. Because it automatically merges, it's generally a good practice to run git fetch
first to inspect the changes before running a git pull
, especially if you've been working on the same files as others.
Try It Yourself: Push and Pull
After setting up your remote, make a new commit on your local main
branch. Push these changes to the remote with git push origin main
. Now, pretend you're another user and go to the remote repository on GitHub to make a small change directly (e.g., edit the README). Go back to your local repository and run git pull origin main
. You will see the changes from the remote appear in your local files.
Module 3: Remote Repositories
Lesson 3: Tracking Remote Branches
In a collaborative environment, you'll often need to work on branches that were created by other developers and pushed to the remote. You'll also need to manage the relationship between your local branches and their counterparts on the remote repository. This is known as tracking.
What is a Tracking Branch?
A tracking branch is a local branch that is linked to a remote branch. This link tells Git which remote branch to push to and pull from by default. When you clone a repository, Git automatically sets up a local main
branch that tracks the remote's main
branch (usually named origin/main
).
When you run git push
or git pull
without any arguments, Git uses this tracking information to know what to do. If you're on a local branch that doesn't track a remote branch, you'll get an error telling you to specify the remote and branch name.
Creating a Local Tracking Branch
The most common way to create a local branch that tracks a remote one is to use the git checkout
command. When you have a new branch on the remote (e.g., origin/feature-login
) that you want to work on locally, you can create a new local branch with the same name that automatically tracks the remote one.
# Make sure to run `git fetch` first to get the remote branch info
git fetch origin
# Create a local branch that tracks the remote branch
git checkout feature-login
Git is smart enough to see that a remote branch with that name exists and will automatically set up the tracking relationship. You can then use git push
or git pull
without any arguments, and Git will know exactly what to do.
Changing a Tracking Branch
You can also manually set or change the upstream tracking branch for your current local branch using the git branch --set-upstream-to
command:
git branch --set-upstream-to=origin/feature-login
This is useful if you want to change which remote branch your local branch is tracking, or if you created a new local branch and want to link it to a remote branch that already exists. You can see the tracking information for all your local branches by running git branch -vv
. This command shows the tracking information as well as the last commit hash and message for each branch, which is very useful for getting a quick overview of your project's state relative to the remote.
Try It Yourself: Create a Tracking Branch
Create a new branch on GitHub directly (e.g., a branch named new-feature
). On your local machine, run git fetch origin
. Then, run git checkout new-feature
to create a local tracking branch. Use git branch -vv
to confirm the tracking relationship is set up correctly.
Module 3: Remote Repositories
Lesson 4: Handling Remote Conflicts
Remote conflicts are a common occurrence in team-based development. They happen when you try to push your changes to a remote branch, but someone else has already pushed their own changes to the same branch, and your local history has diverged from the remote's. Git will prevent you from pushing and inform you that you need to integrate the remote changes first.
The Push Failure
When you try to push your local changes and the remote has new commits, you'll see an error message like this:
To https://github.com/user/repo-name.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/user/repo-name.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes...
This message is a clear signal that you need to update your local branch with the remote changes before you can push your own. The correct workflow is to first pull the latest changes from the remote, resolve any conflicts that arise, and then push your changes.
Resolving Remote Conflicts
The process of resolving a remote conflict is very similar to resolving a local merge conflict. The steps are as follows:
- Pull the remote changes: Run
git pull origin <branch-name>
. This will download the new commits from the remote and attempt to merge them into your local branch. - Identify the conflicts: If there are conflicts, Git will pause the pull and mark the conflicting files with the familiar
<<<<<<<
,=======
, and>>>>>>>
markers. - Manually resolve: Open the conflicting files in your code editor. Edit the files to choose the desired version of the code and remove all the conflict markers.
- Stage the resolved files: After resolving all conflicts, stage the changes with
git add
. - Commit the merge: Run
git commit
. Git will automatically provide a merge commit message, which you can accept or edit. - Push your final changes: Now that your local branch is up-to-date and the conflicts are resolved, you can push your changes, including the new merge commit, to the remote with
git push origin <branch-name>
.
A more advanced technique to avoid a merge commit on your local history is to use git pull --rebase
. This command pulls the changes and then rebases your local commits on top of them, resulting in a cleaner, linear history. However, this should be used with caution, as rebasing rewritten history can be complex if not managed properly.
Try It Yourself: Simulate a Remote Conflict
Have your local main
branch and your remote main
branch perfectly synced. Make a commit on your local branch. Now, go to GitHub and make a change to the same file, in the same area of code, and commit it directly on the remote. Back on your local machine, try to push your commit. It will be rejected. Now, run git pull origin main
, resolve the conflict, stage the file, commit the merge, and then push the final result.
Module 3: Remote Repositories
Lesson 5: Multiple Remotes Management
While most projects will only have a single remote named "origin," Git is designed to handle multiple remotes. This is particularly useful in open-source projects or when you need to interact with different repositories, such as a main project and your own personal fork. Understanding how to manage multiple remotes gives you the flexibility to contribute to and synchronize with a variety of sources.
Why Use Multiple Remotes?
The most common scenario for multiple remotes is in the context of an open-source project. You typically have:
- The main project remote: This is the official repository where the project lives. You'll likely name this remote
upstream
. You will pull from this remote to get the latest changes from the community. - Your personal fork remote: This is a copy of the project in your own GitHub account. You'll likely name this remote
origin
. You will push your changes to this remote, and then create a pull request from here to the main project.
Having two remotes allows you to separate the source of the official project (upstream) from your personal contribution stream (origin). It prevents you from accidentally pushing your experimental changes to the main project's repository.
Adding a Second Remote
The process for adding a second remote is the same as adding the first one. You use the git remote add
command with a new name and the remote URL.
# Assume 'origin' is your personal fork
# Add the official project as 'upstream'
git remote add upstream https://github.com/official-project/repo.git
After adding the remote, you can check your list of remotes with git remote -v
. You will now see two remotes configured:
origin https://github.com/your-username/repo.git (fetch)
origin https://github.com/your-username/repo.git (push)
upstream https://github.com/official-project/repo.git (fetch)
upstream https://github.com/official-project/repo.git (push)
Interacting with Multiple Remotes
With multiple remotes, you can now specify which remote you want to interact with for each command. For example, to get the latest changes from the official project, you would use fetch
or pull
with the upstream
remote:
# Get all the new branches and commits from the official project
git fetch upstream
# Pull the latest changes from upstream's main branch into your local main branch
git pull upstream main
When you're ready to share your work, you would push your changes to your own fork using the origin
remote. This workflow is a standard for open-source development and showcases the flexibility of Git to handle complex project structures. You can even remove a remote with git remote remove <remote-name>
if it's no longer needed.
Try It Yourself: Add a Second Remote
Go to a popular open-source project on GitHub and fork it. Then, clone your fork to your local machine. Now, add the original project as a new remote named upstream
. Use git fetch upstream
to get the latest changes from the official project. This sets you up for a standard open-source contribution workflow.
Module 4: GitHub Mastery
Lesson 1: GitHub Account and Repository Setup
GitHub is the most popular platform for hosting Git repositories. It provides a collaborative web-based interface that extends the functionality of Git, offering features like pull requests, issue tracking, and project management tools. To get started with GitHub, you'll need to create an account and a new repository.
Creating a GitHub Account
First, navigate to the GitHub website and sign up for a new account. The process is straightforward and requires you to choose a username, provide an email address, and create a password. Once your account is verified, you will have access to the GitHub dashboard, which is your central hub for all your projects and activities.
Creating a New Repository
After logging in, you can create a new repository by clicking the "New" button on your dashboard. When creating a new repository, you'll be prompted to provide some information:
- Repository name: Choose a clear and descriptive name for your project. This name will also be part of the repository's URL.
- Description: A short description of what your project is about. This is helpful for others who might discover your project.
- Public vs. Private: You can choose whether your repository is publicly visible or private. Private repositories are only accessible to you and the people you grant access to.
- Initialize with a README: It is a best practice to initialize your repository with a README file. This file will contain information about your project, how to use it, and how to contribute.
- Add .gitignore and license: You can also choose to add a
.gitignore
file (to specify files and folders Git should ignore) and a license (to define how others can use your code).
After filling out this information and clicking "Create repository," your new GitHub repository will be live. You will be taken to its page, which will provide you with the HTTPS or SSH URL needed to clone it to your local machine.
Cloning a Repository
To get a copy of the repository on your local machine, you use the git clone
command. This command not only downloads the project files but also initializes a local Git repository and automatically sets up the "origin" remote to point to your GitHub repository.
git clone https://github.com/your-username/your-repo-name.git
This single command gets you set up to start working on your project and synchronizing your changes with GitHub. You now have a local version of the project, a Git repository initialized, and a remote configured. This is the starting point for all of the powerful collaboration features that GitHub offers, which we will explore in the next lessons.
Try It Yourself: Create and Clone a Repo
Go to github.com and create a new public repository. Initialize it with a README. Copy the HTTPS URL for the repository. In your terminal, navigate to a new directory and use git clone
to download a local copy of your new repo. Then, use git remote -v
to verify that the origin
remote is set up correctly.
Module 4: GitHub Mastery
Lesson 2: Pull Requests and Code Reviews
The pull request (PR) is the centerpiece of the collaborative workflow on GitHub. A pull request is a request to merge your changes from a feature branch into a target branch, typically the main
branch. It serves as a formal review process, allowing other team members to review your code before it becomes part of the main project.
Creating a Pull Request
After you've completed a feature on a new branch, committed your changes locally, and pushed the branch to your remote repository on GitHub, you're ready to create a pull request. On your GitHub repository page, you'll see a banner indicating that you've recently pushed a new branch, and it will give you the option to "Compare & pull request."
When you click this, you'll be taken to a page where you can fill out the details of your pull request:
- Title: A concise summary of the changes (e.g., "Add user authentication feature").
- Description: A more detailed explanation of what you did, why you did it, and any other relevant information. This is where you can mention which issues the PR addresses.
- Reviewers: You can request specific team members to review your code.
After filling out the form, click "Create pull request."
The Code Review Process
Once the pull request is open, it becomes a hub for discussion and review. Team members can view all the changes in the "Files changed" tab and leave comments on specific lines of code. This is an invaluable opportunity to catch bugs, improve code quality, and share knowledge. The reviewer can approve the changes, request further changes, or leave general comments. As the author of the PR, you can respond to comments, make additional commits to your branch (which will automatically update the PR), and push them back to GitHub.
Merging the Pull Request
When all discussions have concluded, the code has been approved by the required number of reviewers, and all automated checks (like CI/CD) have passed, the pull request can be merged. A pull request is typically merged into the target branch using one of three options on GitHub:
- Merge commit: Creates a merge commit to tie the two branches together. This preserves the branch history.
- Squash and merge: Combines all the commits from the feature branch into a single new commit on the target branch. This creates a clean, linear history.
- Rebase and merge: Rebases the feature branch onto the target branch and then fast-forwards the target branch to the end of the feature branch.
The merge option you choose depends on your team's workflow and preferences. Once the PR is merged, the feature branch can be safely deleted, and the new changes are officially part of the project. Pull requests and code reviews are a fundamental part of modern, professional software development. They promote a culture of collaboration, quality, and continuous improvement.
Try It Yourself: Create a Pull Request
In your cloned repository, create a new branch, make a few commits, and push it to your GitHub remote. Go to the GitHub website and navigate to your repository. Follow the on-screen prompts to create a pull request. Go through the review interface, and then, if you're working alone, merge the pull request. Once merged, delete the feature branch.
Module 4: GitHub Mastery
Lesson 3: Issues and Project Management
Beyond code hosting and pull requests, GitHub provides robust project management tools to help teams organize, track, and prioritize their work. The core of this is the "Issues" feature, which allows you to track bugs, enhancements, tasks, and questions. These issues can then be organized into projects and linked to your code changes.
Using GitHub Issues
An issue is a single unit of work. Anyone with access to the repository can create an issue. A well-written issue typically includes:
- A clear title: A short, descriptive summary (e.g., "Bug: Login button not working on mobile").
- A detailed description: Information about the problem, steps to reproduce it, expected behavior, and screenshots or links if applicable.
- Labels: You can add labels like
bug
,enhancement
,documentation
, orhelp wanted
to categorize the issue. - Assignees: You can assign an issue to a specific person on your team.
- Milestones: You can group issues together for a specific goal or release.
Issues are a central point of communication for the project. Team members can comment on issues, ask questions, and share progress updates. This keeps all discussions related to a specific task in one place.
Linking Issues to Commits and Pull Requests
A powerful feature of GitHub is the ability to link your code changes directly to an issue. You can reference an issue in your commit message or pull request description using a special syntax. For example, if you're fixing bug number 42, you can write "Fix login bug #42" in your commit message. GitHub automatically creates a link to the issue. Even better, you can use special keywords to automatically close an issue when a pull request is merged.
# A commit message that references and closes issue #42
git commit -m "Fix login button on mobile. Closes #42"
When the commit is pushed and merged into the main branch, issue #42 will be automatically closed. This creates a seamless and traceable workflow from identifying a problem to resolving it with code.
GitHub Projects
For more advanced project management, GitHub Projects provide a Kanban-style board to visualize your workflow. You can create columns like "To do," "In progress," and "Done." You can then add issues and pull requests to this board and drag them between columns as your team works on them. This gives you and your team a high-level overview of the project's status and helps with planning and prioritization.
Try It Yourself: Create an Issue and Link a Commit
Go to your GitHub repository and click on the "Issues" tab. Create a new issue with a descriptive title and description. Note the issue number. Go back to your local repository, create a new branch, and make a commit that says "Resolve issue #[issue-number]". Push the branch to GitHub and create a pull request. You will see that GitHub automatically links the PR to the issue, showing the seamless integration of these tools.
Module 4: GitHub Mastery
Lesson 4: GitHub Actions and CI/CD
GitHub Actions is a powerful automation platform that allows you to automate tasks directly within your GitHub repository. It's most commonly used for Continuous Integration and Continuous Deployment (CI/CD), which are practices that automate the building, testing, and deployment of your code. GitHub Actions turns your repository into a full-fledged development pipeline.
What are GitHub Actions?
GitHub Actions are workflows defined in YAML files inside the .github/workflows
directory of your repository. A workflow is a set of automated steps that run when triggered by an event, such as a commit being pushed to a branch or a pull request being opened. Each workflow consists of one or more "jobs," and each job contains a series of "steps" that execute commands.
A Simple CI Workflow
A typical Continuous Integration workflow involves running automated tests every time new code is pushed. This ensures that new changes haven't broken existing functionality. Here is a simple example of a GitHub Actions YAML file that runs a Node.js project's tests:
# .github/workflows/ci.yml
name: Node.js CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
- run: npm ci
- run: npm test
This workflow file instructs GitHub to:
- Trigger: Run on every
push
andpull_request
to themain
branch. - Jobs: Define a single job named
build
. - Environment: The job will run on an Ubuntu virtual machine.
- Steps: It will check out the code, set up a Node.js environment, install dependencies with
npm ci
, and finally run the tests withnpm test
.
When this workflow runs, you can see the progress of the jobs in the "Actions" tab of your GitHub repository. If any step fails (e.g., a test fails), the workflow will fail, and GitHub will notify you. This provides immediate feedback on the health of your codebase and is a critical practice for maintaining code quality in a team.
Continuous Deployment (CD)
Beyond CI, GitHub Actions can also be used for Continuous Deployment. For example, after a successful build and test on the main
branch, a separate job can be configured to deploy the application to a web server. This automates the entire process from code change to production, allowing for faster and more reliable releases.
Try It Yourself: Create a Simple Workflow
In a repository, create a new directory named .github
and inside it, a directory named workflows
. Create a file named ci.yml
and paste the Node.js example code above into it. Push this new file to GitHub. Now, make a simple change to a file and push it. You'll see the CI workflow run in the "Actions" tab of your repository, demonstrating the power of automated workflows.
Module 4: GitHub Mastery
Lesson 5: Open Source Contribution
GitHub is a thriving ecosystem for open-source software, and understanding how to contribute is a valuable skill for any developer. Contributing to open source allows you to learn from experienced developers, build your portfolio, and help improve software used by millions. The process is a combination of the Git and GitHub skills you've learned so far.
The Fork and Pull Request Workflow
The standard workflow for contributing to an open-source project is as follows:
- Fork the repository: Instead of cloning the original repository, you first "fork" it. This creates a full copy of the repository under your own GitHub account. This is your personal copy where you can make changes without affecting the original project.
- Clone your fork: Clone your new fork to your local machine using
git clone
. - Add an upstream remote: Add the original repository as a new remote, typically named
upstream
. This allows you to easily pull the latest changes from the official project into your local copy. - Create a new branch: Create a new, descriptive branch for your changes (e.g.,
feat/add-new-feature
orbugfix/fix-typo
). - Make your changes: Make your code changes, commit them locally, and push your new branch to your personal fork (the
origin
remote). - Create a Pull Request: On your GitHub fork's page, you'll see an option to create a pull request to the original project. Fill out the pull request form with a clear title and description, explaining your changes.
Once you've created the pull request, the project maintainers and other community members can review your changes, offer feedback, and eventually merge your contribution. If they request changes, you simply make new commits to your branch, push them to your fork, and the pull request will automatically update.
Keeping Your Fork Updated
As you work on your contribution, the original project (upstream
) might get new commits. It's important to keep your fork up-to-date to avoid merge conflicts. You can do this by first fetching from upstream
and then merging those changes into your local branch.
# Make sure you are on your local main branch
git checkout main
# Fetch the latest changes from the original project
git fetch upstream
# Merge the upstream changes into your local main branch
git merge upstream/main
# Now, push the updated main branch to your fork
git push origin main
After updating your local main
branch, you can merge it into your feature branch to ensure your work is based on the latest version of the project. This workflow ensures that your contribution is always compatible with the main project. Open-source contribution is a fantastic way to grow as a developer and give back to the community.
Try It Yourself: Simulate a Contribution
Choose an open-source project on GitHub that interests you. Fork the repository and clone your fork. Add the original project as the upstream
remote. Create a new branch, make a very small change (like fixing a typo in the README), commit it, and push it to your fork. Then, create a pull request to the original project to practice the full workflow.
Module 5: Advanced Workflows
Lesson 1: Git Flow and GitHub Flow
As projects grow and teams get larger, it's important to adopt a consistent workflow to manage the development process. A workflow defines how branches are created, what they are used for, and how they are merged. Two of the most popular and influential workflows are Git Flow and GitHub Flow. Understanding these can help you structure your projects for success.
Git Flow
Git Flow is a highly structured workflow designed for projects with a planned release cycle. It uses two main long-lived branches:
main
: This branch is always in a production-ready state.develop
: This branch integrates all the completed features for the next release.
In addition to these, it uses several short-lived, supporting branches:
feature
branches are created fromdevelop
, and when a feature is complete, it's merged back intodevelop
.release
branches are created fromdevelop
to prepare for a new release. Bug fixes are made directly on this branch.hotfix
branches are created frommain
to quickly fix critical bugs in production. Once fixed, the hotfix is merged back into bothmain
anddevelop
.
Git Flow is a great fit for projects with strict release schedules, like software that gets released in versions (e.g., v1.0, v2.0). It provides a clear structure, but its complexity can be a drawback for small or continuously deployed projects.
GitHub Flow
GitHub Flow is a simpler, more agile workflow that is ideal for projects with continuous delivery. It is built around a single, central branch, typically named main
. The core principles are simple:
- Anything in the
main
branch is deployable. - To work on something new, create a new branch from
main
with a descriptive name. - Commit to your new branch frequently and push to the remote.
- Open a pull request to merge your branch into
main
. - Get a code review.
- Once approved, merge your changes into
main
and deploy immediately.
This workflow is much less complex than Git Flow. It's focused on getting changes into production as quickly and safely as possible. It is a fantastic choice for web applications and other projects that practice continuous deployment. The simplicity of GitHub Flow makes it easy to learn and implement, and it's the de facto standard for many modern development teams.
Try It Yourself: Explore the Workflows
Think about a project you might work on. Would a more structured approach like Git Flow be better for it, or would the simplicity and agility of GitHub Flow be a better fit? Consider a mobile app with version releases versus a continuously updated web application. Explore the different branching strategies in your mind or on paper and decide which one you would recommend to a team.
Module 5: Advanced Workflows
Lesson 2: Team Collaboration Strategies
Effective collaboration is the cornerstone of any successful software project. Git and GitHub provide the tools, but a team needs to adopt a few key strategies to work together efficiently and avoid common pitfalls. This lesson focuses on the communication and synchronization habits that make for a productive team.
Stay Up-to-Date
One of the biggest causes of merge conflicts and integration headaches is working on an old version of the code. It is a best practice to frequently pull the latest changes from the main branch into your local repository. This can be done with a simple git pull
on the main branch before you start your work, and then occasionally merging the main branch into your feature branch as you work on it.
# Go to main branch to get the latest changes
git checkout main
git pull origin main
# Switch to your feature branch and merge the new changes from main
git checkout feature/your-feature
git merge main
This simple habit keeps your feature branch in sync and makes the final merge much smoother. It's often better to deal with a small merge conflict now rather than a massive one at the end of a long-lived branch.
Communicate Your Intentions
Before you start working on a new feature or bug fix, make sure to check if anyone else is already working on something similar. A quick message in a team chat, or assigning yourself to an issue on GitHub, can prevent two developers from wasting time on the same task. The code is only half of the story; the other half is communication.
Atomic Commits
A good commit is a single, logical unit of work. This is often referred to as an "atomic commit." Avoid lumping multiple, unrelated changes into a single commit. A good commit message should clearly describe what was changed, and why. For example, a commit message that says "Update CSS and fix login bug" is bad, while two separate commits, "Fix: login bug" and "Refactor: update CSS for new button," are good. Atomic commits make it much easier to revert a specific change if something goes wrong, and they make the project history far more readable.
Leverage Pull Requests for Code Reviews
Pull requests are not just a tool for merging; they are a tool for learning and collaboration. Use the code review process as a chance to get feedback from your peers. Don't be afraid to ask for help or suggest improvements. Engaging in this process helps maintain code quality and fosters a culture of shared responsibility. Team collaboration is a skill in itself, and using Git and GitHub effectively is a big part of mastering it.
Try It Yourself: Merge from Main
Create a new feature branch and make one commit. Switch back to your main
branch and make a new commit there. Now, switch back to your feature branch and run git merge main
. You will see that the new commit from the main branch is now part of your feature branch, keeping it up-to-date with the rest of the project.
Module 5: Advanced Workflows
Lesson 3: Git Hooks and Automation
Git hooks are scripts that Git executes automatically before or after certain events, such as committing, pushing, or receiving commits. They are a powerful way to automate tasks and enforce policies within your repository. Hooks allow you to customize your Git workflow, from linting code before a commit to automatically deploying your code after a push.
What are Git Hooks?
Git hooks are simply executable files located in the .git/hooks
directory of your repository. By default, this directory contains sample hook scripts with a .sample
extension. To enable a hook, you just need to remove the .sample
from the filename. For example, pre-commit.sample
becomes pre-commit
.
There are two main categories of hooks:
- Client-side hooks: These run on your local machine. They are useful for local automation, like linting code, formatting, or running tests before you commit or push. The most common client-side hooks are
pre-commit
andpre-push
. - Server-side hooks: These run on the remote repository server. They are useful for enforcing policies, such as making sure a commit message follows a specific format or preventing a push from a certain branch. The most common server-side hook is
pre-receive
.
A Simple pre-commit
Hook
A very useful and common use case for a pre-commit
hook is to run a linter or formatter on your code before a commit is created. This ensures that all code in your repository adheres to a consistent style. A simple shell script for a pre-commit
hook might look like this:
#!/bin/sh
# Stash uncommitted changes
git stash -q --keep-index
# Run linter
npm run lint
# Check if the linter exited with an error code
RESULT=$?
# Restore stashed changes
git stash pop -q
# Exit with the linter's result
[ $RESULT -ne 0 ] && exit 1
exit 0
This script first saves any unstaged changes, runs a linter (e.g., an npm script), and then restores the unstaged changes. If the linter fails (returns a non-zero exit code), the commit is aborted. This prevents you from committing poorly formatted code. Git hooks are not copied when a repository is cloned, so a common practice is to use a tool like Husky, which makes it easy to share and manage hooks across a team.
Hooks provide a way to automate and enforce important aspects of your workflow, but remember that client-side hooks can be bypassed (e.g., with git commit --no-verify
), so server-side hooks are necessary for truly strict policy enforcement.
Try It Yourself: Create a pre-commit
Hook
In your local repository, navigate to the .git/hooks
directory. Rename the pre-commit.sample
file to pre-commit
. Add a simple script to it (e.g., one that just prints "Pre-commit hook is running..."). Make it executable with chmod +x .git/hooks/pre-commit
. Now, make a commit. You will see your script's output, proving the hook works. You can then add more advanced logic to it.
Module 5: Advanced Workflows
Lesson 4: Large File Storage (LFS)
Git is designed to handle text-based files, and it's highly efficient at storing and tracking their changes. However, it's not well-suited for large binary files like images, videos, audio clips, or archives. When a large file changes, Git stores the entire new version, which can quickly bloat your repository's size and slow down operations like cloning and fetching. Git Large File Storage (LFS) is a Git extension that solves this problem.
How Git LFS Works
Git LFS replaces large files in your Git repository with small text pointers. These pointers are then committed to your repository as normal. The actual large files are stored on a separate LFS server (e.g., a service provided by GitHub or GitLab). When you clone a repository or switch to a branch with LFS files, Git LFS automatically downloads the actual files for you behind the scenes.
This means your Git repository remains small and fast, while the large files are managed separately. The experience for the developer is seamless—they work with the large files as if they were in the repository all along, but the underlying mechanism is much more efficient.
Setting up Git LFS
First, you need to install Git LFS on your machine. The installation process is different depending on your operating system, but you can find the instructions on the official Git LFS website. Once installed, you need to set up Git LFS in your repository.
- Initialize LFS in your repo: Run the
git lfs install
command once to set up the necessary hooks in your local repository. - Track file types: Use the
git lfs track
command to tell Git which file types to manage with LFS. This command adds a line to your.gitattributes
file. For example, to track all PNG files:
git lfs install
git lfs track "*.png"
The .gitattributes
file is a plain text file that you commit to your repository, so all team members will use LFS for the same file types. You can track multiple file types, such as *.mp4
or *.zip
, by running the command for each type.
After tracking is set up, you can add, commit, and push your large files as you normally would. Git LFS takes care of the rest, replacing the files with pointers and uploading the actual files to the LFS server. This simple addition to your workflow can make a huge difference in the performance and manageability of repositories that contain large binary assets, such as game development projects, machine learning models, or design projects.
Try It Yourself: Use Git LFS
Create a new repository and add a large file to it (e.g., a large image or a video file). Try to commit and push it without LFS. Notice how long it takes. Now, initialize Git LFS in that repository and track the file type. Add, commit, and push the file again. Notice the speed improvement and how Git LFS handles the file transparently. You can also view the content of the file in your repository with a text editor to see the pointer file that Git commits.
Module 5: Advanced Workflows
Lesson 5: Security and Access Management
When working with private repositories, it's essential to manage access and ensure the security of your codebase. This involves controlling who can read and write to your repository and securing your connection to remote servers. This lesson covers key security practices for Git and GitHub.
Using SSH for Authentication
By default, Git can use HTTPS for authentication. With HTTPS, you're prompted to enter your username and password every time you push or pull. While convenient for quick use, it's not the most secure or efficient method. A better approach is to use SSH (Secure Shell). SSH uses a pair of cryptographic keys—a public key and a private key—to authenticate your identity. Your private key is stored securely on your machine, and you upload your public key to GitHub. When you connect, the two keys are used to verify your identity without needing to enter a password.
To use SSH, you must first generate an SSH key pair. You can find detailed instructions on GitHub's help pages, but the general command is:
ssh-keygen -t ed25519 -C "your_email@example.com"
After generating the keys, you copy the contents of the public key file (id_ed25519.pub
) and add it to your GitHub account settings. You then use the SSH URL of your repository when you clone or add a remote. This provides a much more secure and seamless authentication experience.
Repository Access and Permissions
On GitHub, you can control who has access to your private repositories and what permissions they have. Permissions are typically broken down into several levels:
- Read: Can clone and pull from the repository. This is suitable for observers or non-technical stakeholders.
- Triage: Can manage issues and pull requests, but cannot write code.
- Write: Can push code to the repository. This is the standard permission for most developers on a team.
- Maintain: Can manage the repository's settings and permissions, but not the entire organization.
- Admin: Has full control over the repository and its settings.
It is a best practice to grant the minimum level of access required for a user to perform their job. This principle of "least privilege" minimizes the risk of accidental or malicious changes. You can manage these permissions in the "Settings" tab of your GitHub repository, under "Manage access."
Branch Protection Rules
To prevent bad code from being merged into your most important branches (e.g., main
), GitHub allows you to set up branch protection rules. These rules can enforce several requirements, such as:
- Requiring pull requests to be reviewed by a certain number of people.
- Requiring all automated status checks (e.g., from GitHub Actions) to pass before a merge.
- Preventing force pushes to the branch.
Branch protection rules are a key part of maintaining a stable and secure codebase. By enforcing these rules, you add a safety net that protects your project from common errors and ensures a high level of code quality. These security and access management features are essential for any professional team using Git and GitHub.
Try It Yourself: Set up Branch Protection
Go to your GitHub repository's settings and navigate to "Branches." Click "Add rule" and enter "main" as the branch name pattern. Enable "Require a pull request before merging" and "Require status checks to pass before merging." Try to push directly to your main
branch. You will see that Git rejects your push, enforcing the rule you just created.