Introduction
Suppose your supervisor or customer gives you a new project to work on: “I want you to start helping out on my iOS project, ‘Blocks-in-Objective-C.’ Please get a copy of the code, make these changes, test, and then check your new code into the repo. By the way, we’re using Git for source control.” Have you used Git before? Do you know it well? (Does anyone truly understand Git?) Today, I’ll show you how to accomplish that task just assigned by your supervisor or customer.
Requirements
Since this is an iOS blog, I’m assuming you’ve installed Xcode and therefore Git is installed on your Mac. (If you need to install Git, click here.)
Background
Git can be totally obtuse, confusing, overwhelming — and there always seems to be 13 different ways of accomplishing the same task. We’ll look at Git from the command line instead of using some client software like SourceTree or GitKraken. The best Git users are the ones who understand what’s happening “under the hood.”
Today, we’re going to look at at normal, daily Git workflow. I’ll to expand upon an article I posted in February, “Creating a new Git/GitHub repository for your Xcode project — a detailed tutorial.” Read that article to get a good overview of Git and source/version control management (SCM). I left out some details regarding Git repository (“repo”) configuration that most developers will need as they use Git more and more. Instead of creating a new local Git repo and pushing it up to a new GitHub remote repo as we did last time, I’m going to go in the opposite direction. I’ll show you how to “clone” an existing GitHub remote repo to your Mac, thus creating a local repo. This new local repo will be a snapshot of the remote at the time of cloning. We’ll make some changes to the local and then push them to the remote on GitHub, thus synchronizing your new local with the remote — and thus making your changes available to other developers who may be working on the same project stored in GitHub. Then another developer will make changes and we’ll retrieve those from the remote and put them into our local.
Working with Git
Now we start working with Git based on the customer’s statement, “I want you to start helping out on my iOS project, ‘Blocks-in-Objective-C.’ Please get a copy of the code, make these changes, test, and then check your new code into the repo. By the way, we’re using Git for source control.”
Git clone — getting the code repo
When you hear “please get a copy of the code” and you’ve been given a link to a GitHub repo, you know it’s time to “clone” the repo to your local Mac so you can work on the code in that repo. Pick a directory in which you’ll store the new repo, which will be created as a new directory. I’m going to store my new repo in the new directory “Code” in my standard “~/Documents” directory:
How do I know how to clone the repo? Your customer gave you the link to the homepage of his GitHub repo, so go there, find the green “Clone or download” button, click it, and choose the “Use HTTP” link on the pop-down:
Get a copy of the web URL (https) that you’ll need for cloning the repo:
Open the macOS “Terminal” app, change directory (cd) to the “~/Documents/Code” folder, and get ready to clone — so have that cloning URL handy. The clone command has the following form:
1 |
$ git clone https://github.com/iosbrain/Blocks-in-Objective-C.git |
Let’s walk through the process in Terminal. Remember that you hit the [return] key after typing each command to execute that command:
1 2 3 4 5 6 |
$ cd ~/Documents/Code $ git clone https://github.com/iosbrain/Blocks-in-Objective-C.git Cloning into 'Blocks-in-Objective-C'... remote: Counting objects: 39, done. remote: Total 39 (delta 0), reused 0 (delta 0), pack-reused 39 Unpacking objects: 100% (39/39), done. |
Notice lines 3, 4, 5, 6 highlighted in the code snippet above. That’s the output of the [git clone] command. You’ll now see that the entire Git repo containing an Xcode project has been copied from the remote server to a local repo on your Mac:
Notice that I’ve annotated the screenshot of the Finder window containing your new repo. I’ve put a red box around the repo/project name. I’ve drawn red arrows pointing to the “.git” directory and “.gitignore” configuration file — those two entities explain why Git is self-contained. A single directory and configuration file embody all the metadata that make up a Git repo. Underlined in red is your Xcode project file.
You can now open that “Blocks in Objective-C.xcodeproj” project file in Xcode and start development. BUT, before you do, read the following tutorial. I’ll cover a typical Git workflow — a workflow that will represent 95% of your time with Git. We’ll dive into configuring and customizing Git, getting some code, editing some code, checking it into Git, and then getting changes that another developer makes on another machine. And… away we go!
Configuring Git
Let’s assume you’re using Git for the first time. Git provides three different protocols for configuration (see “First-Time Git Setup” at this link). In this tutorial, we’re going to keep things simple and assume that you’re a lowly “Standard” user on a Mac. So your home directory has the path “~/” which is the same as “/Users/your_user_name” (leave a comment if you need help).
Your Git configuration files
We’re looking at a Git configuration from the perspective “specific personally to you, the user.” That means that your Git settings will be stored at the path “~/”. Settings will be stored in this file:
1 |
~/.gitconfig |
which is the same as
1 |
/Users/your_user_name/.gitconfig |
This file won’t exist at this point for users who haven’t used Git.
Your Git identity
You need to enter credentials that give you access to the remote repo. Remember that we’re using GitHub in this tutorial. Right now, you’ve got a local copy of the repo/code, but you won’t be able to interact with the remote without configuring your Git/GitHub identity.
Let’s start with the basics. These should be configured and provided by your customer, the owner of the remote repo. You’ll need:
- your username (use your real name so other developers can identify your Git activities) — here, we’ll use “James Developer;”
- your GitHub email address, the primary unique attribute Git uses to identify you — here, we’ll use “james@hostname.com;” and,
- your GitHub password — here, we’ll just assume you know your password (and please use a strong one).
Open up a macOS Terminal window and let’s set up your Git identity. Enter the following two commands:
1 2 |
$ git config --global user.name "James Developer" $ git config --global user.email james@hostname.com |
We’ll make use of the password later.
Your Git environment
Let’s configure a few things to make Git easier to use. You can always check your current Git configuration settings by entering the following command in Terminal:
1 2 3 4 |
$ git config --list credential.helper=osxkeychain user.name=James Developer user.email=james@hostname.com |
Notice that I’ve highlighted lines 2, 3, 4 — the output of the Git command on line 1. Line 2 simply means that macOS (OS X) can store your Git credentials for you so you don’t have to type them every time you perform a sensitive (e.g., write) operation with Git. The “user.name” and “user.email” entries are self explanatory. If you use Finder to look at your home directory, you’ll now see that .gitconfig file I mentioned previously:
Git output color-coding
It’s more easily readable to see Git output as color-coded. Just enter the following command in Terminal:
1 |
$ git config --global color.ui auto |
You can customize the color scheme in all sorts of ways, but for this article, I’ll just give you a link, and/or you can consult the documentation (here and here).
Git handling of line endings
Remember that one advantage of a source control/management system is to be able to track your changes as your software evolves. This often involves comparing two different versions of the same file from two different developers. If all the members of your development team work on the same platform, say all OS X or all Windows, then you won’t have problems comparing files. But in a mixed-platform environment, for example when some developers work in Windows and some work in OS X, this can become a problem. Why? Because Windows uses a CRLF (two invisible characters to mark the end of a line) while OS X uses one, an LF.
Git has ways of auto-magically trying to edit files for you as they go back and forth between say Windows and OS X, but I find letting Git into this process to be a nightmare. Discussing this is out of the scope of this tutorial. If for some reason you need to enter this nightmare world, here are some articles you can read: link, link, and link.
I’ve found that the IDEs, Xcode 9 and Visual Studio 2017, I currently use display both OS X and Windows files without problems. Because I only edit OS X files, I let everything alone. I turn all this auto-line-ending-handling stuff off and I work in a multi-platform environment:
1 |
$ git config --global core.autocrlf false |
But, if I were to edit both Windows and OS X files, I’d configure Git as follows:
1 |
$ git config --global core.autocrlf input |
Globally ignoring files from Git tracking
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.
Instead of having to create a .gitignore file for every repo you create, you can configure Git to use one global ignore file. If you work with multiple languages and IDEs, you can creative an all-inclusive ignore file using these samples.
To configure a global ignore file for all your repos, build your own .gitignore, rename it as “.gitignore_global,” copy it into your home directory (~/), and run the following Git command:
1 |
$ git config --global core.excludesfile ~/.gitignore_global |
Common Git workflow
Let’s have some fun with Git! Woo-hoo!
Seeing code changes tracked by Git
I’m going to make a few code changes and show you how Git tracks those changes. Before I do, I want to show you that the current repo has no tracked changes since I just cloned it from the repo. To check Git code-change-tracking capabilities, open Terminal and change directory into the top-level project folder that contains the .git directory and enter the following command:
1 |
$ git status |
Notice the sentence “Your branch is up-to-date with ‘origin/master’.”? That means no changes yet.
Let’s make some changes to the Objective-C code in project file “ViewController.m:”
1 2 3 4 5 6 7 8 9 10 11 |
/*! Proves that UI is responsive during background image download. Pressing button increments variable and displays value in text box. */ - (IBAction)pressButtonTapped:(id)sender { self.pressCounter++; // self.counterValueTextField.text = [NSString stringWithFormat:@"%li", (unsigned long)self.pressCounter]; // Drew 12/29/17: display counter as a double self.counterValueTextField.text = [NSString stringWithFormat:@"%1.1f", (float)self.pressCounter]; } |
I highlighted lines 8, 9, 10 to show you the edits I made to ViewController.m. Let’s see what Git tells us now:
Git told us that ViewController.m was “modified.” Note that Xcode integrates with Git automatically, and its IDE also shows us that ViewController.m was modified by placing an “M” next to it:
Synchronizing local code changes with the Git remote
In order for us to “Record changes to the [local] repository,” we must “stage” the changed file(s)* — Git calls this an “add:”
1 |
$ git add "Blocks in Objective-C/ViewController.m" |
and then “commit” them with a comment:
1 |
$ git commit -m "Changed counter from integer to floating point." |
*Notice that you must enclose any file/folder names that contain spaces inside of quotes when using Terminal and/or Git.
In order to share our changes with other team members, we must “push” them from the local that we just committed to the remote on GitHub (“Use git push to push commits made on your local branch to a remote repository”):
1 |
$ git push origin master |
(Remember I told you that you’d need that GitHub password. Now’s the time.)
Notice that the “M” is no longer besides the file ViewController.m in Xcode’s “Project navigator.”
GitHub now reflects the change you just made — and other team members will see it if they’re paying attention:
Those most alert team members can see exactly what changes you made in your commit and push (“Changed counter from integer to floating point.”) by clicking on the file name I’ve underlined in the image shown immediately above. Clicking displays the file’s current contents — and notice the “History” button:
Click “History” and you’ll see the commit history. There’s your commit (which I’ve highlighted in red). Click on the commit’s unique ID:
You’ll see code that’s been deleted (pink/red highlighted and with “-” [minus] signs) and added (green highlighted with “+” [plus] signs). You get line numbers and all sorts of other goodies, like “Split” view, where deletions and additions are shown side-by-side, git diff
, and much more:
There’s so much to learn about Git.
Getting remote code changes and synchronizing with the Git local
I’m going to make a simple change to ViewController.m in our project/repo on another Mac local, stage, commit, and then push to to our GitHub remote. We’ll then “pull” the change to our local like so:
1 |
$ git pull origin master |
Finding out if there are changes in the Git remote
Suppose you’d like to find out if some other developer made some code changes and committed and pushed them to your repo’s remote. I’m talking about a non-destructive status check. If you were to execute a git pull origin master
, and there are new changes in the remote, they’re going to be copied down to your local and files will be overwritten. I’ll describe one simple option, with the proviso that there are other ways of getting remote code without overriding local:
1 |
$ git remote show origin |
I just made changes to the remote under another account, so my local is now out of sync with the remote. Look what happens when I check for new code changes:
Notice the text stating “master pushes to master (local out of date).” That “local out of date” is pretty obvious, eh? Time to pull.
Summary
So there you have it: the general, basic workflow of Git. What I’ve shown you today is what you’ll be doing 95% of the time during coding with Git. You will have to learn about “merge,” “master,” “origin,” “stage,” “branch,” “diff,” “fast-forward,” “fetch,” “rebase,” “tree,” “head,” “fork,” “stash,” “tag,” and a bazillion other things. As an assignment, I suggest you look up some of these terms — and definitely go through some good tutorials. As usual, you will only really learn from experience. Cheers!
NOTE TO READERS: It’s been a long time since I posted, but I fully intend to keep this blog alive. I would like to grow a community of developers, get lively discussions going in the comments section, start letting other writers post on my blog, and start creating formal full-length courses in iOS and macOS development. Stay tuned — and thanks for everyone’s support!