Let’s talk about source/version control, why it’s so important, and how you can easily put all your iOS code under source/version control management (SCM). I’m going to show you the manual steps involved in putting your code into a Git SCM “repository” (repo) so you fully understand how source/version control works. Jump straight to the tutorial if you’re already familiar with the concept of source control. I can’t explain everything about SCM in one blog post, but I’ll get you started and provide many online resources for you to reference. Why am I using Git? Like it or not, Git has become the de facto standard in SCM systems, mainly because it “is a free and open source [and] distributed version control system.” I don’t buy into the “Git is easy to learn” argument. I find Git to be overly complicated, cryptic, and generally requiring more steps to accomplish source control tasks than say centralized SCM systems like TFS or Subversion/SVN. Git does have some advantages over other SCM products, and it even becomes quite efficacious once you pay your dues learning how to use it properly.
One example: With Git, you can make a local project directory structure into a completely self-contained SCM “local” repo with no need for a network connection to a server. You do have the option to connect to and synchronize with a “remote” version of your repo on some far-off, network-connected server. You can work on your local repo whether or not you have a network connection.
You’ve probably noticed in my previous posts that I’ve provided Objective-C and Swift source code, and not just code fragments, but entire projects you can download, reuse, modify, and enhance (just be sure to abide by my licensing agreements). Notice that I haven’t just given you directories full of code files. I haven’t just provided zip files containing project code files in a directory structure. I’ve provided my source code via links to repositories in the source/version control management site GitHub.com. When beginning the design and development of a new iOS software project, there’s something I do before I start programming. Pause now to think about where I’m going with this. What do you do after you’ve created a new Xcode project?
The first thing I do after creating a new Xcode project, and before writing one line of code, is to put my project into a source/version control management (SCM) system like Git. Note that you’ll often hear the term “Version Control System” (VCS), so SCM and VCS are synonymous in the context of this blog post. I’ll show you all the steps involved — all the gory details — in putting you code under SCM using Git later in this article, but let’s first discuss why this is so important. You should start by reading this article, but it’s too long for me to quote here. Here’s a definition of of SCM from the good people who created Git:
What is “version control”, and why should you care? 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. For the examples in this book you will use software source code as the files being version controlled, though in reality you can do this with nearly any type of file on a computer.
If you are a graphic or web designer and want to keep every version of an image or layout (which you would most certainly want to), a Version Control System (VCS) is a very wise thing to use. 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, who introduced an issue and when, and more. Using a VCS also generally means that if you screw things up or lose files, you can easily recover. In addition, you get all this for very little overhead.
Let’s flesh out the definition of SCM in more detail:
A source control management system (SCM) is software that provides coordination and services between members of a software development team. At the most basic level, it provides file management and version control so that team members don’t write over each other’s changes, and only the newest versions of files are identified for use in the workspace. But that’s only the beginning. SCMs also give developers the ability to work concurrently on files (in branches that may or may not converge), to merge changes with other developers’ changes, to track and audit changes that were requested and made, to track bug-fix status and to perform releases. In some cases, SCMs may include other components to assist in managing a software process throughout the entire lifecycle. The difference between source control management systems and application lifecycle management (ALM) systems is really a matter of semantics and reflects the completeness of the tools provided in the system.
And then let’s add one more level of detail. With SCM, you have:
- Backup and Restore. Files are saved as they are edited, and you can jump to any moment in time. Need that file as it was on Feb 23, 2007? No problem.
- Synchronization. Lets people share files and stay up-to-date with the latest version.
- Short-term undo. Monkeying with a file and messed it up? (That’s just like you, isn’t it?). Throw away your changes and go back to the “last known good” version in the database.
- Long-term undo. Sometimes we mess up bad. Suppose you made a change a year ago, and it had a bug. Jump back to the old version, and see what change was made that day.
- Track Changes. As files are updated, you can leave messages explaining why the change happened (stored in the VCS, not the file). This makes it easy to see how a file is evolving over time, and why.
- Track Ownership. A VCS tags every change with the name of the person who made it. Helpful for
blamestorminggiving credit.- Sandboxing, or insurance against yourself. Making a big change? You can make temporary changes in an isolated area, test and work out the kinks before “checking in” your changes.
- Branching and merging. A larger sandbox. You can branch a copy of your code into a separate area and modify it in isolation (tracking changes separately). Later, you can merge your work back into the common area.
Spend some time reviewing the links I’ve provided, especially this one. You need to internalize the SCM/VCS concept to be one of the best of the best iOS developers. Let’s get started. We’ll assume that you’ve just created a new Xcode project. Note that you can let Xcode add your project to SCM automatically, as shown if Figure 1 below, but we’re not going to use this Xcode feature; we’re going to perform the steps manually so that you start understanding how SCM/VCS — specifically Git — works (click to enlarge):
I. Creating your new remote repository
By using a website/service like GitHub (or Atlassian BitBucket), you can automatically store and backup all your local work on an offsite secure server. If your development Mac dies, you can always get your code back by going to GitHub and “cloning” all your code (repos). In this tutorial, I’ll demonstration Git’s support of local (the code on your Mac) and remote (web server based) repositories, which are integrated, but which also give you the flexibility to work with distributed or offline repos.
NOTE: Before we begin the tutorial, make sure you’ve already created, or can quickly create, a new Xcode project on your local Mac.
1) Obviously, you’ll need to create a GitHub account. Once you have an account, create a “New repository” (click to enlarge):
2) Now configure your remote repo. Fill in the “Repository name,” a “Description,” make your repo “Public” — but do not “Initialize this repository with a README,” do not “Add .gitignore,” and do not “Add a license.” We will add these files to your repo later. Trust me on this for now and I’ll explain later. Click “Create repository” (click to enlarge):
You’ll get a confirmation page regarding the remote repo you just created. Make sure you save a copy of the URL to your remote repo. If you’re using SSH like me, then it’s git@github.com:iosbrain/Blocks-in-Objective-C.git. If you’re using HTTPS, it’s https://github.com/iosbrain/Blocks-in-Objective-C.git. Check these SSH and HTTPS screenshots out (click to enlarge):
Note there are instructions on how to proceed. If you don’t want .gitignore, README, and license files, GitHub’s instructions are OK, but I believe you’ll prefer the methodology I’m setting forth herein. If you do want .gitignore, README, and LICENSE files, you’ll avoid a NIGHTMARE by following my steps. (Welcome to Git hell.)
There’s nothing for GitHub to show you at this point. If you were to click on the URL to your new repo on the confirmation page as shown in Figures 4a and 4b, you’ll just stay on the same confirmation page.
II. Creating your new local repository
The remote repo we just created is basically just a placeholder now. But we needed to create it first so that you could link your local source files to the remote repo. Now we’ll add your Xcode project to a local Git repo.
1) You don’t want your Git repo to track a bunch of transient, scratchpad, and intermediate files — and there can be a whole lot of them. I’m talking about things like library archives (.a files), intermediate object code files (.o files), cache files, and all sorts of transient under-the-hood Xcode stuff (like “dgph”, debug symbols, scratchpad files, the positioning of Xcode windows [.xcuserstate], etc.). You can tell Git what not to track in SCM using a “.gitignore” file.
Feel free to copy my .gitignore file for Objective-C into your Xcode project’s top-level directory. You can copy the file’s contents from the text window on GitHub into a text editor like TextWrangler and then save it with name “.gitignore” into your project directory.
2) Open a macOS command line (bash) Terminal window, change directory to your Xcode project’s top-level directory, and list the directory contents:
1 |
$ cd "/path/to/your/iOS projects directory/Blocks in Objective-C" |
1 2 3 4 5 6 7 8 9 |
$ ls -al total 16 drwxr-xr-x 6 andrewjaffee staff 204 Feb 11 09:43 . drwxr-xr-x 83 andrewjaffee staff 2822 Feb 10 16:44 .. -rw-r--r--@ 1 andrewjaffee staff 6148 Feb 11 06:36 .DS_Store drwxr-xr-x 10 andrewjaffee staff 340 Feb 10 17:23 Blocks in Objective-C drwxr-xr-x 5 andrewjaffee staff 170 Feb 10 17:00 Blocks in Objective-C.xcodeproj drwxr-xr-x 5 andrewjaffee staff 170 Feb 10 17:04 DerivedData |
3) Use git-init to “Create an empty Git repository,” i.e., initialize a new local Git repo for your project. The dot (.) indicates to Git that we want to create the new repo here in the current directory.
1 2 3 |
$ git init . Initialized empty Git repository in /path/to/your/iOS projects directory/Blocks in Objective-C/.git/ |
4) Do another listing of your current Xcode project’s directory. You’ll notice that Git SCM was started. All the meta data needed for SCM is stored in that “.git” directory (highlighted line 7), which is an advantage of Git:
1 2 3 4 5 6 7 8 9 10 11 |
$ ls -al total 24 drwxr-xr-x 8 andrewjaffee staff 272 Feb 11 10:16 . drwxr-xr-x 83 andrewjaffee staff 2822 Feb 10 16:44 .. -rw-r--r--@ 1 andrewjaffee staff 6148 Feb 11 10:16 .DS_Store drwxr-xr-x 10 andrewjaffee staff 340 Feb 11 10:16 .git -rw-r--r--@ 1 andrewjaffee staff 1418 Feb 5 17:42 .gitignore drwxr-xr-x 10 andrewjaffee staff 340 Feb 10 17:23 Blocks in Objective-C drwxr-xr-x 5 andrewjaffee staff 170 Feb 10 17:00 Blocks in Objective-C.xcodeproj drwxr-xr-x 5 andrewjaffee staff 170 Feb 10 17:04 DerivedData |
5) Make sure your local Git repo has the same credentials as the remote you created on GitHub:
1 2 |
$ git config user.name "Andrew Jaffee" $ git config user.email "emailAddress@host.com" |
6) Do a git-add to literally add your project’s core files to Git’s “index.” In other words, you’re putting those core files — minus all the junk on the .gitignore list — under source control management or SCM. The dot (.) means all files except those .gitignore-d:
1 |
$ git add . |
7) Perform a git-commit to “Record changes to the repository.” You say “huh?” Remember that the initial git-add of files we just performed was considered the first change to the repo! We can add a comment for the changes we made with the commit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ git commit -m "Initial commit to new repo" [master (root-commit) 30d595f] Initial commit to new repo 11 files changed, 1008 insertions(+) create mode 100644 .gitignore create mode 100644 Blocks in Objective-C.xcodeproj/project.pbxproj create mode 100644 Blocks in Objective-C/AppDelegate.h create mode 100644 Blocks in Objective-C/AppDelegate.m create mode 100644 Blocks in Objective-C/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Blocks in Objective-C/Base.lproj/LaunchScreen.storyboard create mode 100644 Blocks in Objective-C/Base.lproj/Main.storyboard create mode 100644 Blocks in Objective-C/Info.plist create mode 100644 Blocks in Objective-C/ViewController.h create mode 100644 Blocks in Objective-C/ViewController.m create mode 100644 Blocks in Objective-C/main.m |
8) Now it’s time to connect the local repository to GitHub (which is the remote server in this distributed SCM environment). We do this using the git-remote command, with that GitHub SSH path we saved earlier (git@github.com:iosbrain/Blocks-in-Objective-C.git) Note that if I were using HTTPS, I’d use https://github.com/iosbrain/Blocks-in-Objective-C.git:
1 |
$ git remote add origin git@github.com:iosbrain/Blocks-in-Objective-C.git |
9) Finally, we synchronize our local repo with our remote repo. In this case, we’ll be copying/transferring (“pushing”) all the local files to the remote server. As the project gets on a roll, you’re usually only going to be pushing one or two (several) files at a time from your local to the remote. Let’s use git-push to “Update remote refs along with associated objects:”
1 2 3 4 5 6 7 8 9 10 11 |
$ git push -u origin master Counting objects: 18, done. Delta compression using up to 8 threads. Compressing objects: 100% (15/15), done. Writing objects: 100% (18/18), 11.04 KiB | 0 bytes/s, done. Total 18 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), done. To github.com:iosbrain/Blocks-in-Objective-C.git * [new branch] master -> master Branch master set up to track remote branch master from origin. |
Take a look at your GitHub account to see your remote repo now filled with the same core files that are in your local repo — excluding stuffing in .gitignore. Thar she blows:
III. Adding meta data to your repo (README, LICENSE)
At GitHub, you have to pay for the luxury of maintaining private repos. If you’re pushing out some public code you’re willing to share with other people, you want your repos to look professional and be SEO-friendly with a “README” (or “README.md”). You can put a nice description of your code, including catch phrases and keywords, into a README. You can make your README pretty with boldface and italics fonts, etc., using GitHub’s own “markdown.”
Today’s world is awfully litigious. Someone might claim that using your code caused them to become possessed by evil spirits — or caused them to spill scalding hot coffee in their laps. There are despicable cretins out there who will steal your code and claim credit for it. So you can make anyone who uses your code sign off on a licensing agreement before they use your code. You do this by adding a “LICENSE” (or “LICENSE.md”) file to your repo.
You can click on these links — README, LICENSE, and .gitignore — to get templates from GitHub.
1) When on the webpage for your remote repo, use the GitHub “Create new file” button to write up and/or copy and paste “README” (or “README.md”) and “LICENSE” (or “LICENSE.md”) files (click to enlarge):
2) When done adding/editing/deleting one of these files, remember that you’re under SCM, so you need to commit and push your changes (click to enlarge):
So here is my remote repo after adding a LICENSE file and a README file (click to enlarge):
IV. Syncing your remote and local repos
I just made some changes to the remote repo (added a README and LICENSE), so my local repo is out of sync with the remote.
Once you get on a development roll, you’ll need to synchronize your remote and local repos. Most Git-based developers work in their local repo(s). They need to be very careful about keeping up with changes that other people on the same development team, pointing at the same remote repo(s), make to their local repo(s) and then commit and push to the remote(s). Before you push changes to the remote(s), remember that other developers may have pushed changes to the remote(s). You can check the status of the remote(s) first. Whether you check status or not, always pull all changes from the remote repo(s) before you push.
Here’s how you can check if there have been changes at the remote(s). Since I added a README and LICENSE at my remote, I should find that my local is out of sync (see the highlighted line 12) — and indeed it is:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ git remote show origin * remote origin Fetch URL: git@github.com:iosbrain/Blocks-in-Objective-C.git Push URL: git@github.com:iosbrain/Blocks-in-Objective-C.git HEAD branch: master Remote branch: master tracked Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (local out of date) |
I need to do a git-pull which “Incorporates changes from a remote repository into the current branch,” and look at what changes came down (see highlighted lines 11, 12):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ git pull remote: Counting objects: 6, done. remote: Compressing objects: 100% (6/6), done. remote: Total 6 (delta 2), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (6/6), done. From github.com:iosbrain/Blocks-in-Objective-C 30d595f..42c7c42 master -> origin/master Updating 30d595f..42c7c42 Fast-forward LICENSE.md | 11 +++++++++++ README.md | 12 ++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md |
V. Removing Git SCM from a local repo
Sometimes Git can get really hosed up. There’s the whole “rebase” thing, but that’s beyond the scope of this tutorial. For now, just remember that Git’s SCM tracking meta data is compartmentalized into directories in your project structure named “.git”. You can remove all Git tracking information from a local Git repo with this simple command:
1 |
$ rm -rf .git |
VI. Git resources
If you’re going to use Git, study up on it and make use of some of the graphical user interfaces to Git. Sometimes you have to use the command line to work with Git, but several of the Git frontends are shaping up nicely.
Git knowledge bases
Git GUI Tools
I hope y’all enjoyed exploring the wild world of source/version control for iOS development using Git. Please leave comments if you have questions or feedback. Thanks!