Git basics for iOS developers

Published on: January 3, 2024

I’ll just say this right off the bat. There’s no such thing as git “for iOS Developers”. However, as iOS Developers we do make use of git. And that means that it makes a lot of sense to understand git, what it is, what it’s not, and most importantly how we can use it effectively and efficiently in our work.

In this post, I’d like to outline some of the key concepts, commands, and principles that you’ll need to know as an iOS Developer that works with git. By the end of this post you will have a pretty good understanding of git’s basics, and you’ll be ready to start digging into more advanced concepts.

Understanding what git is

Git is a so called version control system that was invented in the early 2000s. It was invented by Linus Torvalds who’s also the creator of the Linux operating system. It’s primary goal is to be a faster alternative to older version control systems like SVN and CVS. These older systems all relied on a single source of truth and made features like branching slow and hard to manage. And because everybody relied on a single source of truth, this meant that there was also a single point of failure. In practice this meant that if your server broke, the entire project was broken.

Git is a distributed system. This means that everybody that clones a project clones the entire git repository. Everybody has all code, all branches, all tags, etc. on their machine when they clone a repository.

The upside of this is that if anything goes wrong with any of the copies of the repository it’s always possible to replace that copy because there’s never a single point of failure.

However, in your day to day use it won’t matter much that git is faster and more reliable than what came before it. In your day to day work you’ll most likely be using git as a means to collaborate with your peers, and to make sure you always have a backup with proper history tracking for your project.

A common misconception amongst newer developers is that git is only relevant when a project needs to be shared amongst multiple developers. While it’s very useful for that, I can only recommend that you always use git to manage your personal projects too. Doing this will allow you to experiment with new features in separate branches, rewind your project to a previous point in time, and to tag releases so you always know which version of your code ended up shipping. If you’re not sure what a branch is, don’t worry. I’ll get to explaining that soon.

Using git is always recommended regardless of project size, team size, or project complexity.

In this post, I won’t explain how git works on this inside. My aim is to provide a much higher level overview for now, and to dig into internals in several follow up posts. Git is complicated enough as-is, so there’s really no need to make things more complicated than they need to be in an introductory post.

Now that you know that git is a version control system that allows you to keep track of your code, share it, create branches, tags, and more, let’s take a look at some of they terminology that’s used when working with git.

Key terminology

You have a vague sense about what git is so now I’d like to walk you through a bit of key terminology. This will help you understand explanations for concepts further in this series, and provide you with a first look at the most important git concepts.

Later in this post we’ll also look at some of git’s most important commands which will start putting things in context and give you some pointers to start using git if you aren’t already.

Repository

When you work with git, a project is typically called a repository. Your repository is usually your project folder that contains a .git folder which is created when you initialize your git repository. This folder contains all information about your project, your commits, history, branches, tags, and more. In the next section of this post we’ll go over how to create a new git repository.

Remote (Origin)

A git repository usually doesn’t exist only on your computer (even though it can!). Most repositories are hosted somewhere on a server so that you can easily access it from any computer, and share the repository with your team mates. While it is decentralized and everybody that clones your repository has a full copy of the repository, you’ll often have a single origin that’s used as your source of truth that everybody in your team pushes code to and pulls updates from.

Most projects will use an existing platform like GitHub, GitLab, or Azure as their remote to push and pull code. A project can use multiple remotes if needed but usually your primary / main remote is called “origin”.

Branches

In git, you make use of branches to structure your work. Every project that you place under version control with git will have at least one branch, this branch is typically called main. Every time you make a new commit in your repository you’re essentially associating that commit with a branch. This allows you to create a new branch that’s based off of a given version of your code, work on it, make changes, and eventually switch back to another branch that doesn’t contain the same changes you just made.

In a way, you can think of a branch in git as a chain of commits.

This is incredibly useful when you’re working on new features for your app while you’re also maintaining a shipping version of your app. You can make as many branches as you’d like in git, and you can merge changes back into your main branch when you’re happy with the feature you’ve just built.

Commits

Commits are what I would consider git’s core feature. Every time you make a new commit, you create a snapshot of the work you did in your project so far. After you’ve made a commit you can choose to continue working on your project, make progress towards new features, implement bug fixes, and more. As you make progress you’ll make more and more commits to snapshot your progress.

So why would you make commits?

Well, there are a few key reasons. One of them is that a commit allows you to see the changes that you’ve made from one step to the next. For example, when you’ve completed a big refactor you might not completely remember which files you’ve worked on and what you’ve changed. If you’ve made one or more commits during the refactoring process you can retrace every step that you took during your refactor.

Another reason to make a commit is so you can branch off of that commit to work on different features in isolation. You’ll most commonly do this in teams but I’ve done this in single-person projects too.

Git is all about commits so if there’s one git concept that you’ll want to focus on first if you’re new to git than it’s probably going to be commits.

Merging and rebasing

For now, I’m going talk about merging and rebasing under a single header. They’re both different concepts with very different implications and workflows but they usually serve a similar purpose. Since we’re focussing on introducing topics, I think it’s fair to talk about merge and rebase under a single header.

When we have a series of commits on one branch, and we have another branch with some more commits, we’ll usually want somehow bring the newer commits into our source branch. For example, if I have a main branch that I’ve been committing to, I might have created a feature-specific branch to work from. For example, I might have branched off of the main branch to start working on a design overhaul for my app.

Once my design overhaul is complete I’ll want to update my main branch with the new design so that I can ship this update to my users. I can do this by rebasing or merging. The end result of either operation is that the commits that I made (or the final state of my feature branch) end up being applied to my main branch. Merge and rebase each do this in a slightly different way and I’ll cover each option in more depth in a follow up post.

Git’s most important commands

Alright, I know this is a long post (especially for a blog) but before we can wrap up this introduction to git, I think it’s time we go over a few of git’s key commands. These commands correspond to the key terminology that we just covered, so hopefully the commands along with their explanations help solidify what you’ve just learned.

Because the command line is a universally available interface for git I’ll go ahead and focus my examples only on running commands in the command line. If you prefer working with a more graphical interface feel free to use one that you like. Fork, Tower, and Xcode’s built-in git GUI all work perfectly fine and are all built on top of the commands outlined below.

Initializing a new repository

When you start a new project, you’ll want to create a git repository for your project sooner rather than later. Creating a repository can be done with a single command that creates a .git folder in your project root. As you’ve learned in the previous section, the .git folder is the heart and soul of your repository. It’s what transforms a plain folder on your file system into a repository.

To turn your project folder into a repository, navigate to your project folder (the root of your project, usually the same folder as where your .xcodeproj is located) and type the following command:

git init

This command will run quickly and it will initialize a new repository in the folder you ran the command from.

When creating a new project in Xcode you can check the “create git repository on my mac” checkbox to start your project off as a git repository. This will allow you to skip the git init step.

Creating a repository for your project does not put any files in your project under version control just yet. We can verify this by running the git status command. Doing this for a folder that I just created a new Xcode project in yields the following output:

❯ git status
On branch main

No commits yet

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

nothing added to commit but untracked files present (use "git add" to track)

As you can see, there’s a list of files under the untracked files header.

This tells us that git can see that we have files in our project folder, but git isn’t actively tracking (or ignoring) these files. In this case, git is seeing our xcodeproj folder and the GitSampleProject folder that holds our Swift files. Git won’t pro-actively dig into these folders to list all files that it’s not tracking. Instead, it lists the folder which indicates that nothing in that folder is being tracked.

Let’s take a look at adding files to a git next.

Adding files to git

As you’ve seen, git doesn’t automatically track history for every file in our project folder. To make git track files we need to add them to git using the add command. When you add a file to git, git will allow you to commit versions of that file so that you can track history or go back to a specific version of that file if needed.

The quickest way to add files to git is to use the add command as follows:

git add .

While this approach is quick, it’s not great. In a standard Xcode project there are always some files that you don’t want to add to git. We can be more specific about what we need to be added to git by specifying the files and folders that we want to add:

# adding files
git add Sources/Sample.swift

# adding folders
git add Sources/

For a standard Xcode project we typically want to everything in our project folder with a couple of exceptions. Instead of manually typing and filtering the files and folders that we want to add to git every time we want to make a new commit, we can exclude files and folders from git using a a file called .gitignore. You can add multiple ignore files to your repository but most commonly you’ll have one at the root of your project. You can create your .gitignore file on the command line by typing the following command:

❯ touch .gitignore
❯ open .gitignore

This will open your file in the TextEdit app. A typical iOS project will at least have the following files and folders added to this file:

.DS_Store
xcuserdata/

You can use pattern matching to exclude or include files and folders using wildcards if you’d like. For now, we’ll just use a pretty simple ignore file as an example.

From now on, whenever git sees that you have files and folders in your project that match the patterns from your ignore file it won’t tell you that it’s not tracking those files because it will simply ignore them. This is incredibly useful for files that contain user specific data, or for content that’s generated at build time. For example, if you’re using a tool like Sourcery to generate code in your project every time it builds, you’ll usually exclude these files from git because they’re automatically recreated anyway.

Once you add files to git using git add, they are added to the staging area. This means that if you were to make a commit now, those files are included in your commit. Git doesn’t record a permanent snapshot of your files until you make a commit. And when you make a commit, only changes that are added to the staging area are included in the commit.

To make your initial commit you’ll usually set up your .gitignore file and then run git add . to add everything in your project to the staging area in one go.

To see the current status of files that have changes, files that aren’t being tracked, and files that are in the staging area and ready to be committed we can use git status again. If we run the command for our iOS project after adding some files and creating the .gitignore file we get the following output:

❯ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
    new file:   .gitignore
    new file:   GitSampleProject.xcodeproj/project.pbxproj
    new file:   GitSampleProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata
    new file:   GitSampleProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
    new file:   GitSampleProject/Assets.xcassets/AccentColor.colorset/Contents.json
    new file:   GitSampleProject/Assets.xcassets/AppIcon.appiconset/Contents.json
    new file:   GitSampleProject/Assets.xcassets/Contents.json
    new file:   GitSampleProject/ContentView.swift
    new file:   GitSampleProject/GitSampleProjectApp.swift
    new file:   GitSampleProject/Preview Content/Preview Assets.xcassets/Contents.json

This is exactly what we want. No more untracked files, git has found our ignore file, and we’re ready to tell git to record the first snapshot of our repository by making a commit.

Making your first commit

We can make a new commit by writing git commit -m "<A short description of changes>" you’d replace the text between the < and > with a short message that describes what’s in the snapshot. In the case of your initial commit you’ll often write initial commit. Future commits usually contain a very short sentence that describes what you’ve changed.

Writing a descriptive yet short commit message is an extremely good practice because once your project has been under development for a while you’ll be thanking yourself when your commit messages are more descriptive than just the words “did some work” or something similar.

Back to making our first commit. To make a new commit in my sample repository, I run the following command:

git commit -m "initial commit"

When I run this command, the following output is produced:

[main (root-commit) 5aa14e7] initial commit
 10 files changed, 443 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 GitSampleProject.xcodeproj/project.pbxproj
 create mode 100644 GitSampleProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata
 create mode 100644 GitSampleProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
 create mode 100644 GitSampleProject/Assets.xcassets/AccentColor.colorset/Contents.json
 create mode 100644 GitSampleProject/Assets.xcassets/AppIcon.appiconset/Contents.json
 create mode 100644 GitSampleProject/Assets.xcassets/Contents.json
 create mode 100644 GitSampleProject/ContentView.swift
 create mode 100644 GitSampleProject/GitSampleProjectApp.swift
 create mode 100644 GitSampleProject/Preview Content/Preview Assets.xcassets/Contents.json

This tells me that a new commit was created with a hash of 5aa14e7. This hash is the unique identifier for this commit. Git also tells me the number of files and changes in the commit, and then the files are listed. In this case, all my files are labeled with create mode. When I make changes to a file and I commit those changes that label will change accordingly.

Most git repositories are connected to a remote host like GitHub. In this post I won’t show you how to add a remote to a git repository. This post is already rather long as it is, so we’ll cover git and remote hosts in a separate post.

In Summary

In this post, you’ve learned a lot of basics around git. You now know that git is a so-called version control system. This means that git tracks history of our files, and allows us to work on multiple features and bug fixes at once using branches. You know that a git repository contains a .git folder that holds all information that git needs to operate.

I’ve explained git’s most important terms like commits, branches, merging, and more. We’ve looked at the key concepts here which means that for some of the terminology you’ve seen we could go way deeper and discover lots of interesting details. These are all topics for separate posts.

After introducing the most important terminology in git, we’ve looked at git’s most important commands. You’ve seen how to create a new git repository, how to add and ignore files, and how to make a commit.

Our next post in this series will focus on getting your repository connected to a remote like GitHub.

Categories

git

Subscribe to my newsletter