No matter what programming language you use, no matter what operating system you run, some software development tools are for everyone. Git falls squarely into that category. The open source distributed version control system gives every kind of developer all the power they need to manage the evolution of their code, and to experiment freely and non-destructively with their projects.
In this article we’ll walk through the basics of using Git: setting up a repository, working with local and remote repositories, and using features like branches and pull requests to manage workflow. Follow along, and see for yourself why Git has become by far the most popular choice for managing codebases, either for solo developers or development teams.
Downloading and installing Git
Setting up Git on one’s work system is different depending on what OS you run.
- Linux: On some breeds of Linux, Git is installed by default. Otherwise, you can follow the installation instructions for your variety of Linux to set it up.
- Windows: Git binaries for Windows can be downloaded from the official Git website. The portable or thumbdrive edition requires no installation—it unpacks into any directory where you have admin permissions—but will require you to add the ./bin subdirectory to your system
PATHto work reliably.
- MacOS: Mac users can install Git from HomeBrew with
brew install git, or use the copy provide with Xcode.
Setting up Git
After you’ve confirmed Git is installed and available from the command line, the first thing you want to do with Git “out of the box” is configure it with your personal information. This allows all of your commits to be “signed” with that info.
To do this, you will use the
git config command, like so:
PS D:Devreplicant> git config --global user.name Thomas Anderson PS D:Devreplicant> git config --global user.email email@example.com
Obviously, you will replace the username and user email with your own.
Another thing you may want to do is configure a default editor for Git:
PS D:Devreplicant> git config --global core.editor emacs
emacs is a valid command.)
On Windows, you may need to provide a full path, in quotes, to the executable file for your editor.
Lastly, you will want to set the name for the default branch used in your code. This is something like
master, although in this example we’ll use
PS D:Devreplicant> git config --global init.defaultBranch main
Initializing a Git repository
When you want to create a Git repository to go with a project, whether a new “repo” or an existing one, you initialize the repository with the
git init command.
PS D:Devreplicant> git init Initialized empty Git repository in D:/Dev/replicant/.git/
git init command creates a .git subdirectory in your project that holds all of the relevant files for the repo. Never place anything manually in this directory; let Git manage it.
If you are creating a repository that you will not interact with except through Git — e.g., it will be strictly an endpoint for code to be stored, like a GitHub repo — use
git init --bare. This flag creates a repo that is not designed to be edited directly, but only pulled from and pushed to. (More on this later.)
Next, you add files or directories to be tracked in the repo with the
git add command.
PS D:Devreplicant> git add readme.md
(If successful, a
git add command returns nothing.)
Wildcards can also be used to add files:
PS D:Devreplicant> git add **
This would add every file stored in the directory
replicant and in all of its children.
You can also create a .gitignore file in your project directory to indicate files or directories that should not be tracked, such as temporary files or build artifacts.
Cloning a Git repository
Another way to work with a repo is to clone, or copy, an existing repo. One does this with the
git clone command, which creates a full, separate copy of a repo in the current directory. You can then work on this copy freely, since it’s now your copy.
PS D:Devpp2> git clone https://github.com/syegulalp/pypacker Cloning into 'pypacker'... remote: Enumerating objects: 131, done. remote: Counting objects: 100% (131/131), done. remote: Compressing objects: 100% (64/64), done. Receiving objects: 41% Receiving objects: 100% (131/131), 23.16 KiB | 5.79 MiB/s, done. Resolving deltas: 100% (60/60), done.
A cloned repo already includes a .git subdirectory initialized for it, so you can start work right away.
Git’s working and staging areas
The files in the directory covered by the Git repo is called the working area. Changes made to these files aren’t immediately applied to the repo; you can edit files freely in the working area without changing anything in the repo.
When you want to apply the changes in the working area to the repository, you stage the changes for each file using
git add. If you have edited
README.md, for example, you would stage its changes like this:
PS D:devreplicant> git add .README.md
A fast way to stage all changes in the working area to your repo:
PS D:devreplicant> git add **
Again, when something is staged, there is no immediate feedback. But you can see the differences between the working and staging areas by using
PS D:devreplicant> git status On branch main No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: README.md
Saving changes with Git commit
When you’re satisfied with the changes to be made in the staging area, you commit them to the repository using
git commit. This command writes the staged files to the repository and creates a snapshot of the repo at that moment in time.
PS D:devreplicant> git commit -m "Initial commit" [main (root-commit) 9c6a751] Initial commit 1 file changed, 1 insertion(+) create mode 100644 README.md
-m flag lets you provide a commit message, or a short text description of the commit. If you just type
git commit, Git will open an instance of the text editor it uses by default to edit commit messages. This is overkill most of the time, because commit messages should be short.
Each commit is a snapshot of the changes made with a distinct ID associated with it, such as
9c6a751, also called the ref.
Note that any changes made to the working area that are not staged will not be committed to the repository, and will be at risk of being overwritten by Git actions like changing branches.
The most recent commit for a given branch is referred to as the head for that branch.
Using Git branches
So far all the work we’ve done with a repo has been to the main branch. Branches in a repository are essentially alternate timelines or paths for your code’s development. You can create a new branch from any commit, write commits to that branch that are isolated from other branches, switch freely between branches, and merge changes across branches.
Creating a new branch
Initializing a new branch is easy enough:
PS D:devreplicant> git branch alphatest
This creates a new branch named alphatest at the current commit in the current branch. (Again, there is no feedback to the console if this command is successful.)
Switching between branches
When you create a new branch, you don’t automatically switch to it. To switch to a different branch, use the
git checkout command:
PS D:devreplicant> git checkout alphatest Switched to branch 'alphatest'
Any commits you make from this point on will be recorded in the alphatest branch, until you change branches once again. Only one branch of a repo can be checked out at a time.
You can see which branch you’re currently on by typing
git branch (no options):
PS D:devreplicant> git branch * alphatest master
The asterisk indicates the currently selected branch.
Note that when you switch branches, any uncommitted changes you made in the previous branch will be overwritten by the new current branch. For instance, if we changed a file but didn’t commit the changes, and then tried to switch back to
main, we would get this warning:
PS D:devreplicant> git checkout main error: Your local changes to the following files would be overwritten by checkout: README.md Please commit your changes or stash them before you switch branches. Aborting
If you don’t want to commit your changes yet, you can stash them in a kind of temporary holding area. Or you could create a new temporary branch and commit your changes there.
When you want to merge changes from one branch into another, follow these steps:
- Commit any changes that need to be made to the branch you’re on.
- Make a note of the most recent ref for the branch (just the first seven digits, such as
128a7da). You can see this information by typing
git log -n 1.
- Switch to the branch you want to merge into.
- Use the command
git merge <ref>to merge the commit from the previous branch into the current one.
If the merge is successful, you will see a message indicating so:
PS D:devreplicant> git merge 128a7da Updating f109105..128a7da Fast-forward README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Reconciling merges in Git
By default, Git tries to reconcile the two branches by moving the head for the old branch to the head of the new one — a “fast-forward,” as it’s called. If no other commits have been made to the old branch, that’s the easiest and fastest approach, hence the name. The end result is that all of the commits made to the new branch now show up in the old branch as well.
Another way to merge branches is with
git merge --squash <ref>, which takes all the commits made to the new branch and flattens or “squashes” them into a single commit to the old branch. This is useful if you have a great many commits and you want to simplify them so that the history of the branch isn’t hard to read.
If branches cannot be merged cleanly, because each branch contains changes that might contradict the other, you need to reconcile the two. One common way to do this is to specify a merge strategy. For instance, one strategy is to have everything merged in take precedence over everything already in place.
Another strategy is to let Git flag what it cannot reconcile, and then perform the reconciliation yourself, by editing the files in question. See the “Basic Merge Conflicts” section of Chapter 3.2 of the Git book for more on how to do this.
Pulling changes from a remote repository
If you’re working on a clone of a repo and you want to synchronize your local copy with the remote copy, you can pull any changes from the remote copy.
When you clone a repository, the source you cloned it from is called a remote branch. Cloned repositories keep references to the remote branches they were cloned from, which you can list by running
git remote show.
The default name for a remote branch is “origin,” so you can synchronize changes from that remote branch by typing
git pull origin.
PS D:Devr2replicant> git pull origin Already up to date.
“Already up to date” means there are no differences between your local repo and the origin.
If there are changes, you will see a summary of those changes and how they were applied. For example:
PS D:Devr2replicant> git pull origin remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), 285 bytes | 1024 bytes/s, done. From d:devreplicant 128a7da..f1af831 main -> origin/main Updating 128a7da..f1af831 Fast-forward README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
Note that a
git pull is just like merging from another branch. If you’re pulling in changes that continue from the head of your codebase, you won’t have a problem. But if you’ve made changes locally, Git will have to reconcile them with the remote branch, and if it can’t, you’ll have to do that by hand — as we discussed in the last section.
Pushing changes to a remote repository
When you want to upload changes from your local copy of a repository to a remote one, you push the changes using
git push. For example:
PS D:Devr2replicant> git push origin Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 12 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 304 bytes | 304.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 To d:devreplicant e5b801d..db31abc main -> main
Note that you can only push changes to a repository that no one else has pushed to since you last pulled from it. Otherwise, you will have to
git pull and merge those changes with your local repo before you can push.
Pushing has a few limitations:
- You cannot push to a branch of a repository that is currently checked out.
- You cannot (by default) push safely to a “non-bare” repository, meaning a repo that was not created with
git init --bare.
Using pull requests in Git
As you probably gathered, pushing and pulling doesn’t cut it for active repositories being worked on by multiple developers. For collaborating development teams, the more common way to merge one’s changes to a repo is to perform a pull request.