How To Use Git Source Control with Xcode 9

Richard Critz
Update note: Updated for Xcode 9 by Richard Critz. Original tutorial by Malek Trablesi and previously updated by Felipe Laso-Marsetti.

Whether you’re a solo developer or working on a team, if you’re not using source control for your projects, you should be. Source control is amazing because it helps you more easily revert to older versions of your code, add new features without risk to your working app, see how your code has changed over time, and work as a team. And one of the best source control systems is built right into Xcode – Git!

Git is a distributed version control system initially developed by Linus Torvalds, the principal force behind the development of the Linux kernel. The nice thing about Git is there doesn’t have to be any central repository – everyone can have his or her own view of the code, and pull in changes from other sources.

In this tutorial, you’ll get hands on experience with Git and learn how to use it directly inside Xcode.

Gitting Started

Rather than ramble on about the theory of Git, you’re going to dive right in and try it out. You’ll create a new Xcode project and try some tasks that you will typically do on a day-to-day basis with Git source control.

Fire up Xcode and create a new Single View Application project.

select project type

Fill in the template options as follows:

project setup options

  • Product Name: GitUseExample
  • Team: Your Apple Developer team if you have one, or None
  • Organization Name: Your name
  • Organization identifier: As the name indicates, it’s your organization’s identifier, if you have one. Otherwise, type whatever.
  • Language: Swift
  • Device family: iPhone
  • Use Core Data, Include Unit Tests, and Include UI Tests: not checked

Now click Next. The following dialog allows you to choose where to save your project. Choose a location and make sure Create git repository on My Mac is selected before proceeding. Once you do that, click Create.

GitUseExample Saving

Note: If you don’t see the checkbox, click the Options button.

Xcode will create your new project along with a new Git repository.

All source control systems, including Git, store their data into a repository so that they can manage your project versions and keep track of changes throughout the development cycle. Think of a repository as a database for versions.

versions database

In the course of working on your project, you’ll add files, modify code, and change your project many times.

After you make a big set of changes and your project is in a “known good” state (typically one or more times per day), it’s a good idea to check your changes into the repository. This gives you a record of “known good” states that you can always return to.

But what about the code that was created by the project template?

Your project still contains only the template files. There is nothing yet for you to commit because Xcode did it for you when you created your project. :]

To check that, open the Source Control navigator (Command-2 is the keyboard shortcut). Now, make sure the Source Control inspector (Command-Option-3) is open as well. Option-click the disclosure triangle next to GitUseExample in the left pane to reveal all of the Branches, Tags and Remotes configured in your repository. Click on the master branch, then click the Initial commit in the editor window and you will see the details of Xcode’s automatic commit.

initial commit view

Now, make some changes to your project. Open AppDelegate.swift and change the method application(_:didFinishLaunchingWithOptions:) to the following:

func application(_ application: UIApplication, 
        didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  print("Application did finish launching")
  return true
}

After you save the file, you will note that AppDelegate.swift now has an “M” badge next to the filename:

modified AppDelegate.swift file

The “M” badge stands for “modified.” It means you have modified the file but have not yet committed the changes to your local Git repository.

Next, open ViewController.swift and add the following code after viewDidLoad():

@IBAction func buttonClicked(_ sender: UIButton) {
  print("This is a Git tutorial")
}

Now, open Main.storyboard and drag a button to the screen from the Object library. Change the button’s displayed text to whatever you want as shown in the screenshot below.

Modified View Controller

The last thing to do is to connect the action to the button. Click the yellow View Controller icon in the View Controller scene. From the Connections Inspector (last tab on the right sidebar), click the open circle next to buttonClicked: in the Received Actions panel and drag it to the button in the Storyboard Editor. Choose Touch Up Inside from the pop-up menu.

Connecting Button Action

If you check the Project navigator, you will notice that all three of the files you have edited have an “M” badge.

modified Project Navigator files

Build and run to make sure the project works. Verify that when you click the button you see the “This is a Git tutorial” message logged to the console.

Button Action Console Log

w00t – your code is now in a “known good” state! It’s time to commit.

Making Some Commit-ments

Committing files is easy! Select Source Control\Commit… from the menu.

commit menu command

A new window will show, similar to the following:

commit window

As you can see, the screen is split into two panes. The left pane shows the file in its current state with all changes made since the last commit. Since this is your first commit, you will see all changes made since the creation of the project.

The right pane shows the file before you made your changes.

Annotated Commit Pane

Look more closely at what Xcode presents in the Commit window.

The left panel (1) shows all of the files in your project with uncommitted changes. By default, Xcode assumes you want to include all of them in this commit and checks their boxes. If you want to exclude a file from this commit, uncheck it. As you will see in a moment, you can also decide which individual changes within a file to include in this commit.

Notice the blue highlights in the code panes. These indicate changes you have made. Any change, even if it only adds or removes blank space, is tracked and will be marked with these blue highlights.

Try it for yourself. Click Cancel, open ViewController.swift, and add several new lines at the end of the file. Now select Source Control\Commit… again. Your results should be similar to the following:

Modified sections

As you can see, Git carefully tracks every single change you make throughout your development cycle.

In the area between the two source panes, all of your changes are enumerated by Xcode (labeled “2” above).

Each change is checked by default. To exclude a specific change from this commit, uncheck it.

For example, change 3 is not important since it consists only of the blank lines you added earlier. Uncheck it so that this change isn’t committed.

Excluded change

Notice the indicator has turned gray to make it clear this change is now excluded from the commit.

Another way to exclude an individual change is to click on the arrow next the change number. Two options will appear: Don’t Commit (or Commit in case the change is unchecked) and Discard Change. In this case, select Don’t Commit.

Don't Commit/Discard Change

Before you can complete a commit, Xcode requires you to enter a commit message in the bottom part of the screen (labeled “3” above). These messages help you better understand at a glance the purpose of each commit.

Commit message

Now click Commit 3 Files. Congrats, you have made your first commit! If you go back to the Source Control navigator and select the master branch as before, you’ll see it.

first commit complete

This simple process of making changes and committing them is what you’ll be doing 90% of the time. Pretty easy, right? Now you have no excuse not to do it! :]

Branching Out

Another feature of Git supported by Xcode is the ability to commit your changes to a specific branch.

But wait, what’s a branch?

A branch is way to keep a set of commits together. By working on different branches, you can keep features separated and reduce your risk of totally breaking your project.

Believe it or not, you’re already using a branch. When a repository is first created, Git also creates a branch named “master” within that repository. All of your work so far has been on the master branch.

The master branch should always keep the main copy of your project. You use other branches as a way to store work in progress that is not yet ready for release. You can also use them to store experiments that may never be released.

For example, let’s say you’re adding a new map feature into your app but it isn’t quite ready for production. To simulate this, create a new class derived from NSObject and name it MapForItinerary. At this point your project might look like this:

added MapForItinerary.swift

Notice the status “A” for the new file MapForItinerary.swift. This indicates this is a new file that has not yet been committed to the repository.

Select Source Control\Commit… from the menu.

Commit for new file

If you select the file with the A status, you will notice that Xcode doesn’t provide any earlier version to compare with. This is because the file hasn’t been committed to the repository yet so there is nothing to compare it with.

Adding the map feature to your app represents a big change in your code. This is a perfect situation to use a branch. This will help to isolate the risk in case there are problems with your new map code.

Instead of clicking the Commit 3 Files button, click Cancel instead. Switch to the Source Control navigator and click the gear icon in the lower left corner. Select Branch from “master”….

create new branch menu

Xcode asks you to name your new branch.

name your new branch

Name the branch map_feature and click Create.

Xcode creates the new branch and switches to it. You can verify this by looking for the current label in the Branches section of the Source Control navigator.

map_feature is current branch

Select Source Control\Commit…, enter a commit message, and click Commit 3 Files.

MapForItinerary commit message

Notice that all of the status letters next to the files in the Project navigator have been cleared. This means you no longer have any uncommitted changes.

no changes to commit

Backing Out

You’re working on the latest revision of your project, building the newest cool feature. You take a break for a snack and have a sudden inspiration for a better way to build it. At this point, you may want to recover the last revision from source control and start fresh.

badstableversion

Git makes it trivial to do just that! :]

Open Main.storyboard and drag a new view controller onto the canvas from the Object library.

Two view controllers

Open MapForItinerary.swift and add the method sayHello().

func sayHello() {
  print("Hello from MapForItinerary")
}

Notice that the status of the modified files has changed to “M”, meaning that the files are locally modified and are waiting to be committed.

At this point, you can selectively discard the changes you’ve made to the project. Select Main.storyboard in the Project navigator and then select Source Control\Discard Changes in “Main.storyboard”… from the menu.

discard changes in main.storyboard

Xcode will prompt you to confirm that you really wish to discard all changes in that file.

Discard prompt

Click Discard Changes. You should see the view controller (and the yellow warning icon) you just added vanish! This can be extremely useful when you’ve added some changes but they aren’t working, and you want to get back to the last known good state.

In addition to discarding an entire file’s changes, you can also discard individual changes.

MapForItinerary.swift still has the “M” badge on it. Select Source Control\Commit… from the menu. Click the down arrow next to the change number and choose Discard Change:

Selective discard

Poof! Your change is gone. Since there is nothing left to commit, just click Cancel to close the commit window.

Now that you’ve tried out Discard Change, you might wonder what the difference is between that and the Don’t Commit option you chose earlier.

While it’s true that both of these options result in the change not being recorded in the repository, there is a big difference:

  • Don’t Commit lets you skip the change so that it won’t be committed with other changes, but it will remain in the local source code.
  • Discard Changes not only skips the change, but also deletes it from the local source code.

Time Travel

Discarding changes is a good way to revert back to working code and save you time. However, it can be a little limiting in some cases.

Git allows you to save multiple revisions for your project where each revision has specific changes. These are all stored into a repository managed for you by Git.

If you choose to discard changes made to a file, Git will restore the last committed version of the file and only the last. And that’s actually where the limitation lies.

Over time, your project repository will grow to contain multiple revisions reflecting the history of your development. Suppose you wish to revert to the first or second version of a particular file. There is no way to do that just by discarding changes. Don’t despair, however, as Xcode and Git make this easy to do.

Select ViewController.swift in the Project navigator. Now select View\Version Editor\Show Comparison View from the menu. Alternatively, you can click the third button in the Editor section on the toolbar at the top right of the Xcode window.

Editor selection buttons

The version editor is split into two panes as shown below:

Comparison view

This allows you to compare two revisions of the selected file and works exactly like the comparison view in the Commit window. By default, your current source file is shown on the left and the most recent revision stored in the repository – Git calls this the HEAD – is shown on the right.

To display earlier versions from your repository, click on the clock icon at the bottom of the right pane (marked in red below) and select an earlier revision.

Revision list icon

Select the revision just prior the HEAD revision as shown below. The exact information you see will be different from the screenshot.

Previous revision list

Now, to revert to that file version, just click the arrow next to the change number in the comparison pane and select Discard Change. It’s that easy! :]

Discard Changes

Once you finish reverting to an earlier version, you will need to commit this “new” version of the file as the most recent. Go ahead and do that now.

How do you know which earlier commit is the one you want? While you can certainly use the Source Control navigator as you learned earlier, there is another way. Click and hold on the Version Editor button and select Log. You can also choose View\Version Editor\Show Log View from the menu.

Select Log View

Xcode will list the commits that contain changes to the current file. Notice that the listing for each commit contains a commit identifier.

Revision history

These identifiers match the ones shown in the revision history list you were using earlier.

Annotated Revision history list

You can also click Show modified files to explore the differences in more detail. Try it now!

Another incredibly useful view into your project is called the Blame View. This view shows you which commit contributes each line of your file.

Switch to the Blame View. Click and hold on the Version Editor button and select Blame. You can also choose View\Version Editor\Show Blame View from the menu.

Select Blame View

Your screen will look something like this:

Blame View

To see more details about a committed change, press the info button next to the date. The resulting pop-up shows you who made the commit, when it was made, the commit message, and the commit identifier. It also has a button to show all of the files modified in the commit and a button to open the current file in Comparison View, comparing the this commit with the HEAD commit.

Show more blame info

Merging Branches

You learned earlier that Git allows you to work on multiple streams of revisions known as branches. You also learned that it is good practice to do all of your development on a branch other than the master branch. What, then, are you to do when you finish development of a feature and want to release it? Simple! You merge your development branch into your master branch.

Your new map feature is not yet finished but your designer has asked for another label on the main interface. To implement this, you will need to leave your map_feature branch behind for now and create a new branch from the “known good” state that is your master branch.

Switch to the Source Control navigator, right-click on the master branch and select Branch from “master”….

branch from master

Name the branch new_label and click Create.

create new_label branch

Notice that you are still working on the map_feature branch. Right-click on new_label and select Checkout….

checkout new_label branch

Xcode will prompt you to confirm that you want to switch branches. Click Checkout.

confirm branch checkout

You can see that you’re now on the new_label branch by checking the (current) label in the Source Control navigator.

on new_label branch

Now, it’s time to add that new label your designer requested.

Switch back to the Standard Editor view, open Main.storyboard, and drag a UILabel on to the main view.

My new label

Build and run to make sure that all is OK and then commit your changes to the repository. Be sure to add a commit message.

Now switch to (checkout) the master branch and run the app again. As expected, the new UILabel you added in the branch is not there. The final job is to merge the new branch back to master.

In the Source Control navigator, right-click new_label and select Merge “new_label” into “master”….
merge new_label into master

Xcode will ask you to confirm the action. Click Merge.

confirm merge

The merge window will appear allowing you to control the merge process. The merge source (the “merge from” branch) will appear on the right. Your current source, as modified by the merge, will appear on the left. Use the buttons at the bottom of the screen to control the direction of the merge. For a simple merge such as this one, Xcode’s default setting will be the correct one.

Merge window

Finally, click the Merge button to start the process :]

If all goes well, you should see the changes (the UILabel) from the new branch appear in the user interface when you click on Main.storyboard or when you run your application. Now your changes are in the master branch because of the merge! Use one of the methods of viewing your commit history that you learned to verify that this change appears in the history.

verify merge complete

Ignoring generated files

Way back at your first commit you saw that, in addition to your source files, Git tracks revisions to files managed by Xcode. This is important because those files are just as necessary to your project as your source files. You need them to rebuild your app or to collaborate with others.

However, as it does its work, Xcode also generates other files that change with each build. It is not important to save these as Xcode can automatically regenerate them. In fact, saving them causes Git to do unnecessary work and makes it harder to find the significant changes in your commit history.

Git provides a mechanism to ignore these files: the aptly-named .gitignore file. The period at the beginning of its name causes macOS to treat it as a hidden file, so it doesn’t normally appear when you look at your project in Xcode or Finder. Never fear, though, because Git will find and use it without a problem.

Rather than working out for yourself everything to put in your .gitignore file, you can download one from gitignore.io.

First, open Terminal and enter the following command. You only need to do this step once, not for every project.

$ git config --global alias.ignore '!gi() { curl -L -s https://www.gitignore.io/api/$@ ;}; gi'

Now, for any project using Git, do the following in a Terminal window:

cd <directory where your project is stored>
git ignore swift,macos >.gitignore
git add .gitignore
git commit -m "Add .gitignore file"

This downloads the most current .gitignore configuration for writing Swift code on macOS. Your terminal session should look similar to this:

adding a .gitignore file

You added the .gitignore file to your repository for Git to track since the filtering it provides is also an important part of your project.

Note: It’s a good idea to create and add .gitignore to your project as soon as you create it, before you make any changes.

Xcode and GitHub

All the work you’ve done so far has been using a local repository saved on your computer. The GitHub website allows you to publish your project to a remote repository saved on the GitHub servers. This is great because it allows you to easily share your code with others and work on a project as a group.

If you don’t already have a GitHub account, go to GitHub and sign up for one.

Once that’s done, you need to add your GitHub credentials to Xcode. Open Xcode Preferences and select the Accounts tab. Press the + in the lower left corner and select GitHub as the account type. Xcode will prompt for your GitHub credentials.

GitHub credentials

If you have two-factor authentication enabled on your GitHub account, Xcode will prompt for your one-time password.

GitHub OTP

Xcode will show you your completed GitHub account configuration. Feel free to switch to SSH for cloning if you have your SSH keys configured.

GitHub account complete

In the Source Control navigator, right-click on the project name (GitUseExample) at the top of the left-hand pane and select Create “GitUseExample” Remote on GitHub….
create remote command

Xcode will select good defaults. Click Create.

create remote dialog

In the Source Control navigator, you will see that Xcode has added a Remote named origin which contains the master branch.

master branch pushed

In order to push the map_feature branch, check it out and select Source Control\Push…. Xcode will prompt you for the remote branch name, offering to create origin/map_feature which is just what you want! Click Push.

push map_feature

Repeat these steps to push new_label to GitHub.

all 3 branches pushed

Check your GitHub page to verify that your files are there. If you don’t already have GitHub open in your browser, you can right-click any of the objects in the Source Control navigator and select View on GitHub….

view on github

Verify on GitHub

Now it’s time to make one final change to your project. Make sure you’re on the master branch and open ViewController.swift. Change the buttonClicked() method as follows:

@IBAction func buttonClicked(_ sender: UIButton) {
  print("You finished!")
}

Select Source Control\Commit… from the menu. Enter a commit message and then check the Push to remote: box in the lower left corner. Again, since you only have one remote configured, the default will be correct.

Commit and Push

Click Commit 1 File and Push. After a few seconds, Xcode will complete the commit and the push. Look for your new commit on your GitHub page.

GitHub push confirmation

Success! :]

Where To Go From Here?

Congratulations, you now know how to use Git source control from Xcode, use branches, merge branches, work with GitHub, and more!

At this point you have most of the tools you’ll need on a day-to-day basis to work with Git source control in Xcode. If you’d like to learn more, here is a list of great resources to check out:

I hope you enjoyed this tutorial, and I look forward for your comments! :]

Team

Each tutorial at www.raywenderlich.com is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Richard Critz

Richard is the iOS Team Lead for RayWenderlich.com.

He is on career number three as a professional photographer (after first being a software engineer doing mainframe O/S development for 20+ years and then a stint as a corporate pilot) doing contract iOS development on the side. Some would say he just can't make up his mind. Actually, he just likes diversity!

When he's not working on either of those, he's probably playing League of Legends (with or without the rest of his family).

On Twitter, while being mainly read-only, he can be found @rcritz. The rest of his professional life can be found at www.rwcfoto.com

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

iOS Team

... 74 total!

Android Team

... 30 total!

Unity Team

... 12 total!

Articles Team

... 14 total!

Resident Authors Team

... 25 total!

Podcast Team

... 7 total!

Recruitment Team

... 9 total!