Keep your work safe with version control
Part 5 in a series of tutorials on modern app infrastructure:
Although the term itself is relatively recent, open source software has been around since software has been around – teams in different universities used to just mail each other their source code.
These days, most developers store their source code on GitHub, and as a result it has become the largest repository of open-source work in existence – it hosts over 80 million repositories are stored there, with thousands more added every single day. Even better, if you’re happy releasing your code under an open source license that allows others to use your code, GitHub doesn’t cost a penny – it’s free!
In this fifth part of our series on modern app infrastructure we’re going to look at how to save our work to GitHub then share it with others. This process won’t take long, so afterwards I’m going to provide you with a useful primer of some other helpful source control commands because it’s such a useful skill for developers.
GitHub is powered by Git, which is a version control system – software responsible for tracking changes to software projects in a safe way, so that all changes we make are stored for later reference. There are other version control systems available, but at this point Git is the overwhelming favorite – if you’re not using it you’re usually viewed with curiosity, distrust, or perhaps even pity.
So, Git is the standard tool for source control, which means you need to learn to use it. That, however, is easier said than done. In fact, learners usually get told various lies about Git, in a conversation that goes something like this:
...at which point their eyes glaze over as the Git-fiend goes off on multiple tangents trying to find metaphors that explain how “easy” Git is.
Here’s the blunt truth: Git isn’t simple. In fact, it’s the opposite: Git is really hard. Years ago I met a developer called Andrew Morton, who is one of the longest-serving and most senior developers of the Linux kernel. This is a guy who knows his stuff, and has years of proof of that. And yet here’s how he describes Git: “A version control system that is expressly designed to make you feel less intelligent than you thought you were.” Worse, the Git documentation is so desperately bad that someone wrote a generator to create fake Git man pages and they still look totally real. Seriously, try it yourself at https://git-man-page-generator.lokaltog.net.
So, when you’re using Git and think it’s taking up a lot more brainpower than you had expected, don’t fret: you’re not alone. And in fact even once you’ve mastered Git, there’s a chance you won’t like it. Many people do like it, and some people even love it, but most people would rather spend their time writing code rather than spending time working with complex tools to manage that code.
To make things easier, many IDEs – include our beloved Xcode – bundle visual implementations of Git commands, so that you can in theory handle source control without going near the command line. While I think these tools are extremely useful once you understand what’s going on, when you’re just learning I suggest you stick with Git on the command line. Once you master command-line Git, you can transfer those concepts to any Git user interface easily, but you’ll always understand what’s going on under the hood.
Now you have an idea of what Git is, let’s look at putting our Paraphrase project on GitHub. If you completed the previous tutorials you can use what you have there, but if you don’t you can get my original implementation from GitHub (ironic, huh?), by clicking here.
Warning: The example project has been written specifically for this tutorial series, and contains mistakes and problems that we’ll be examining over this tutorial series. If you’re looking for example code to learn from, this is the wrong place.
Now let’s take a look at the least you need to know to get this code safe on GitHub. We’ll look at more Git command soon, but this is the absolute minimum.
First, head to GitHub.com and make an account if you don’t already have one. The account creation form is usually right there on the GitHub homepage, but if it isn’t look for a Sign Up link.
Second, click the + button in the top-right of your GitHub window – this is where you create new repositories (projects) or gists (source code snippets). Please click New Repository to continue.
GitHub needs to know some basic details about your project to continue:
Now click Create Repository to have GitHub create your new repository and take you there.
The URL for your new GitHub repository will be something like https://github.com/twostraws/Paraphrase-Improved – obviously your username will be different from mine, but you might also have chosen a different name than “Paraphrase-Improved”. If your repository is public you can send this link to anyone else, and they’ll be able to read your code, download it, and even create their own project on GitHub based on that code – a process known as forking.
However, right now the project is empty, so rather than see any code you will instead see GitHub’s quick setup guide – a simplified set of instructions to help you add your initial code to the repository.
Before we add our own code, we need to take one precautionary measure: it’s possible your version of Paraphrase already has some Git information attached, because I stored the original project on GitHub. So, before we start we’re going to run one command that definitely removes any of my Git history so you’re starting from a clean slate – just like you would in your own projects.
Fire up your Mac’s Terminal app now, then change into your workspace directory – that’s the one that contains files such as Paraphrase.xcodeproj. Now run this command: rm -rf .git
That instructs macOS to delete the .git directory, which contains all of Git’s information about our work. It won’t delete our code, just Git’s record of it – its history of changes over time.
With the other data gone, we’re now going to create a new Git repository to store your changes going forward. This is done by running
git init in the current directory. Git will tell you that it has initialized an empty repository, which means it can start tracking changes as they happen.
To start with, we need to add all our source code to Git, so run this command:
git add .
The dot at the end means “everything from this directory,” which means it will include all files and all subdirectories. This command won’t send any feedback, but you can see what has been added by running another command:
The output from
git status will show you a lot of files in green, but at the top of its output you’ll see “Changes to be committed”. This is important: when you use
git add to add files to a Git repository, all you’re doing is adding the files to a temporary area before they are actually saved. This means your code isn’t safely saved in source control just yet.
This temporary area – where changes are queued up in preparation for being executed – is known as staging, and is useful for preparing a batch of changes all at once. When you’re ready, which is when you’ve added all the changes you want, you commit those changes, which is where Git saves all the changes to disk.
It’s important to run
git status before you commit changes, so you can see exactly what is going to be written to your version control system. If you read through the list of files, there are two things you might query:
The answer to the first question is “it depends” – some people like to add their CocoaPods to source control, but many don’t, as I learned recently…
Final results – I have to admit to being surprised! For reference, the CocoaPods docs do specifically recommend checking pods into source control, although they are pragmatic: “ultimately this decision is up to you.” pic.twitter.com/7NABzwEtoI— Paul Hudson (@twostraws) May 7, 2018
Regardless of what the internet thinks, both CocoaPods and GitHub recommend adding your Pods directory to your Git repositories – the .gitignore file that came with your Paraphrase project is from GitHub, and explicitly mentions they recommend storing Pods.
As for Fastlane’s screenshots, these are missing again because of the .gitignore file: it explicitly skips the screenshots because they change regularly and can be recreated easily.
Again, this isn’t me setting this up – when you create your own GitHub projects you can search for Swift in GitHub’s list of ignore files, to get much the same thing. The only change I made was to add .DS_Store to the list, because those things annoy me!
All in all, the output from
git status looks good, so let’s go ahead and save our changes into version control. Run this command:
That will launch a text editor where you can describe what has changed. If you’re working in a team this message will be helpful to others so they can see why you made your changes. Even if you’re working alone, this message is still useful for your own records, so try to write something useful.
By default you will probably be asked to use one of two text editors: Nano or Vim. If you’re using Nano, you’ll see lots of options across the bottom: “^G Get Help”, “^O Write Out”, “^X Exit”, and so on. If you’re using Vim you’ll just see a filename at the bottom.
Nano users: You can just start typing a message. For now, write “Adding initial source code.” When you’re finished, press Ctrl+O to trigger the “Write Out” command, then press return to use the suggested filename. Now press Ctrl+X to trigger “Exit”, and you’ll be returned to the terminal.
Vim users: Press the “i” key to enter insert mode. Now type “Adding initial source code.” When you’re finished, press Escape to exit insert mode, then type
:wq – Vim’s laconic way of saying “write then quit” – and press return.
Regardless of which text editor you choose, you’ll see output like this:
[master (root-commit) 4a6e7c1] Adding initial source code. 1 file changed, 1 insertion(+) create mode 100644 index.html
If you run
git status now you will see different output:
On branch master nothing to commit, working directory clean
That tells you that all your changes have been saved to disk, and nothing else is queued up.
However, your work is still not truly safe – it’s safe in Git, but that’s only stored locally. To be really safe you need to push your local Git repository to GitHub.
This takes two more commands. First, this command tells Git to connect our local repository to the remote GitHub URL. You’ll need to edit this to use your username and repository name, but my command is this:
git remote add origin https://github.com/twostraws/Paraphrase-Improved.git
Change “twostraws” and “Paraphrase-Improved” to your GitHub username and repository name. “Origin” is the name we’re giving to GitHub’s copy of our repository.
The final command is this:
git push -u origin master
That pushes your local copy (master) to GitHub (origin), and at the same time updates your configuration (-u) so that in the future you can just use
If this is your first time using Git, you’re likely to be asked for your GitHub username and password.
Warning: If you enabled two-factor authentication you’ll need to follow these instructions from GitHub instead of using your password.
Now you’ve pushed your code, you can go back to your repository homepage on GitHub and see it all – your code is now safe!
At this point you understand some of the basic Git commands, but there are several more you need to know if you want to be productive.
You’ve already seen that
git add doesn’t actually save your changes to the repository – it just stages those changes ready to be committed. We used
git add . previously to stage all changes from tracked files, but you can be as precise as you want:
git add main.swift will add just one file, for example. You can also use wildcards just like anything else on the terminal, meaning that
git add *.swift is perfectly fine.
You might wonder why
git add even exists as a separate command to
git commit – why would you add things unless you planned to commit them? To demonstrate this, while also giving you some more time working with Git, I want to walk through some Git commands with you.
The best way to learn Git is to create a local repository. You can manipulate these as much as you want without anyone else knowing – creating and destroying things, trying things out, making mistakes, and so on, all in a private environment.
So, for the rest of this article I suggest you make a new directory on your Desktop like this
cd ~/Desktop mkdir gittest
Now we’re going to change into that directory and make a local Git repository, like this:
cd ~/Desktop/gittest git init
We’re going to put some simple content in there in the form of some example Swift code, so run this command now:
echo "import Foundation let i = 10 print(i)" > test.swift
That creates a trivial Swift file and writes it to test.swift, so we have something to work with.
We want that file in our Git repository, so run this:
git add test.swift git commit -m "Adding test.swift"
That uses the -m parameter to
git commit, which lets us specify our commit message directly on the command line.
Now that change is safe in Git, run this command:
rm -rf test.swift
That will delete the file from our disk – it won’t go to the trash or somewhere else it can be restored, it’s just gone.
So, what now? We just made a change that destroyed our content. It’s time for Git to spring into action: how do we undo that silly mistake? Well, there’s a command just for that, and even with the best of intentions you’ll find you use it a fair amount. But before I write it here I want to add a big warning:
This is one of the few Git commands that can destroy your work. I mean literally render your work unrecoverable. In this case – in our current situation – using the following command is going to bring back the old test.swift file, because we screwed up. But if you have spent hours working on changes, this next command will throw them all away with no undo.
With that big warning out of the way, run this command now:
git reset --hard
You’ll see a message similar to this one:
HEAD is now at 972832b Adding test.swift
What that means is that Git has reverted all your changes so that the repository now looks like it did when you made your most recent commit. “HEAD” is the name Git uses to point to the branch you’re currently, and that in turn points to whatever commit was made most recently on that branch. So, “HEAD” is effectively the last thing you did, the current state of your work, and we just moved HEAD back to point to the previous commit – effectively reverting any changes we had made.
You’ve seen that
git reset --hard unstages everything then reverts your repository back to the most recent commit. Well, if you drop the
--hard option it stops being dangerous: it still unstages everything you had queued up, but it doesn’t revert the actual changes in your files.
Let’s try it now. Open test.swift in Xcode, then change this line:
let i = 10
let i = 20
Now save that change, and run
git status to see what changed”
On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: test.swift no changes added to commit (use "git add" and/or "git commit -a")
Go ahead and run
git add test.swift to add the change you just made. If you run
git status again now you’ll see the change has been staged, ready for a commit.
But wait! Maybe our original code was correct – perhaps staging that change was premature. Fortunately, we can unstage it in several ways, one of which is this:
git reset test.swift
When that completes, you’ll see this message:
Unstaged changes after reset: M test.swift
If you run
git status now you’ll see the repository is back to where it was before: we’ve left the changes in test.swift intact, but they are no longer staged for commit.
You can also reset everything at once – effectively clearing the stage without touching your content changes – by using
git reset just by itself.
Please run these two commands now:
git reset git status
You should see test.swift in the list of changes not staged for commit.
After all that adding and resetting, it’s possible you might have forgotten exactly what change you made to test.swift, or perhaps you’d like to review all your changes to make sure nothing else snuck through by accident. Git has a command just for this purpose, called
git diff, and it gives you a color-coded list of additions and removals in your unstaged files.
Please try it now: run either
git diff by itself or using
git diff test.swift to show differences in that one file – we only have one file so the commands are effectively identical.
You’ll see this output:
diff --git a/test.swift b/test.swift index 07796ba..2d9f1f6 100644 --- a/test.swift +++ b/test.swift @@ -1,3 +1,3 @@ import Foundation -let i = 10 +let i = 20 print(i)
That’s known as unified diff format, which is a fancy way of saying this is the complete set of changes between Git’s existing version of test.swift and your new version. It tells us – in compressed form – that the original version will be displayed as three lines counting from line 1, and the new version will be displayed as three lines count from line 1. The first
let i line has a
- in front of it because it’s been removed as a result of our changes, and the second
let i line has a
+ in front because it was added.
git add . to add test.swift to staging once more, then run
git diff again – what do you see now?
That’s right: you see nothing. This is because
git diff by default only shows you differences for unstaged, uncommitted changes. Once you stage or commit changes, they will no longer appear in the output from
git diff. If you want that to happen, you need to ask for it specifically:
git diff --staged
This was the fifth part of a short series on upgrading apps to take advantage of modern infrastructure. We’ve only looked at the absolute basics of Git here, but if you're looking for more you should read my book Beyond Code – it covers Git in a great deal more detail, along with other important skills such as regular expressions, the command line, and Scrum.
In the final articles we’ll look at one last way computer automation can help us write better code – stay tuned!
SPONSORED Instabug helps you identify and resolve severe crashes quickly. You can retrace in-app events and know exactly which line of code caused the crash along with environment details, network logs, repro steps, and the session profiler. Ask more questions or keep users up-to-date with in-app replies straight from your dashboard. Instabug takes data privacy seriously, so no one sees your data but you! See more detailed features comparison and try Instabug's crash reporting SDK for free.
Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and a speaker at Swift events around the world. If you're curious you can learn more here.