Apple's Xcode environment has the ability to work with source code control systems, such as CVS, or the more modern subversion (or "svn", as it's also known.) I recently started using subversion for my other programming work, so I was rather pleased to find that Xcode has the ability to work with it.
Apple's public documentation about setting up subversion and making Xcode work with it is rather dated- as I write this (2009-02-02) it was last updated on 2005-12-08. Because of its age, there are a few problems with it:
The directions for setting up subversion and Apache are written for somebody who's installing a subversion server on a Mac. I run my own Linux server with Apache and subversion, so I honestly didn't do much more than browse through those directions. All I can really say is that they look right to me.
Their procedure has you install and configure subversion and Apache, and create a repository in the directory where the subversion server expects to find it. So far so good. But then it has you create a work directory (well, they tell you to "cd/tmp/tmpsvn", I suppose they expect you to know that you need to "mkdir /tmp/tmpsvn" first) and then create the traditional "trunk", "branches", and "tags" directories and import them into the newly-created local repo, and then check them back out into the directory where Xcode will actually be working on code.
We just went through the process of setting up a subversion server running within Apache. Why are we not actually USING it? The whole point of setting up an Apache-based server is so we don't NEED to access the local repo directory... the access should be through Apache, using a URL like "http://localhost:8080/svn/", rather than using the "file://" URL pointing directly to the raw repo file. For many people (myself included) one of the advantages of using subversion is that you can work on the same project from multiple machines (i.e. from both a desktop and a laptop.)
For small projects, I don't normally bother with the "trunk" and other directories. I'm almost always the only programmer for a project, and it's very rare for me to create branches of my own code, so to me it's not really worth worrying about.
Xcode has evolved quite a bit since the page was written. This means the screenshots don't match what people actually have on their computers, and the procedures are a bit different anyway.
To be fair, Apple does have more up-to-date documentation about Xcode, which includes current screen shots and better procedures... however this other documentation is only available if you sign up as an iPhone developer (which you also have to do in order to download the iPhone SDK, so chances are you will have done this already.) However, I did also find one problem with that document as well... at least it seems like a problem to me, because I'm familiar with using the svn command-line program and know more about how to use it.
That document (link may require you to log in first) tells you to move the "build" directory out of the project directory in order to keep subversion from taking control of them. They are correct in saying that the directory shouldn't be under subversion control; however it's not necessary to move it- as you'll see below, it's very easy to tell subversion to ignore it.
As I noted above, Apple's public documentation has you install subversion and Apache, then create a temporary directory, copy the entire project into it, and then import that directly into the raw repo directory. If you have Apache configured as shown in that document, it will listen on port 8080 of every IP address on the machine- which means if the machine has (for example) the IP address 192.168.1.5, then any other machine can access the subversion repo using a URL like "http://192.168.1.5/svn/". Being able to access the repo from multiple machines is one of subversion's biggest advantages.
Apple's documentation also has you configure Apache using "SVNPath", which specifies exactly one repository. My own server uses "SVNParentPath", which specifies the parent directory of multiple repositories. I find this to be a better solution, since it allows me to create multiple repositories for different project or different clients. This page explains how I set things up on my own server (again, my server runs Linux rather than using a Mac as a server. I like the Xserve idea, but I don't have the Xserve money.)
I'm actually writing this section as documentation for myself to refer back to when I start a new project. It has specific information about my own server, however I will explain this when appropriate. Note that my server is set up using these directions.
One of the most common things I run into is that I'll start a project with Xcode, then want to add it to a subversion repository afterwards (either right after starting it, or after building part of it and realizing that I'm on the right track and that I'll want to keep this one.) Apple's directions have you manually copy the entire project to a temporary directory, import that into the repo, and then check it back out into the directory where Xcode expects to find it- but that's a lot more work than you really need to do.
What I do is this...
Create a repo for the project, or choose an existing repo and make an empty directory within the repo for the project. Note that if your subversion server is using Apache with mod_svn, and it's configured using "SVNPath", it can only serve a single repo and your only choice will be to create an empty directory within that repo.
If you're going to create a new repo, you will need to use the "svnadmin create" command on the server itself.
This is the parent directory for all of your subversion repos, and the
value of the SVNParentPath directive in Apache's config.
# cd /www/secure.jms1.net/svn
# svnadmin create HelloWorld
Apache's "anonymous" userid must have full access to the raw repo files
in order to do its job. The easiest way is to have this user "own" the
files. My server (CentOS 5) uses an "apache" userid, check for the "User"
and "Group" directives in your "httpd.conf" file to verify the user and
group IDs you need in this next command.
# chown -R apache:apache HelloWorld
If you have implemented access controls on your repositories, you should add a block to your ".authz" file which defines your access policies for the new repository. It might look like...
[HelloWorld:/]
@admin = rw
* =
Creating an empty directory within an existing repo is VERY easy, and you don't even need to be on the server itself. Using the "localhost:8080" server from Apple's documentation, it would look like this:
$ svn mkdir http://localhost:8080/svn/HelloWorld
... or on my own server, in a repo called "iPhoneCode" (which is not an actual repo on my server, just an example name) it might look like this:
$ svn mkdir https://secure.jms1.net/svn/iPhoneCode/HelloWorld
If you want to set up the traditional "trunk", "branches", and "tags" directories, you can do this using "svn mkdir" commands, as shown here:
$ svn mkdir http://localhost:8080/svn/HelloWorld/trunk
$ svn mkdir http://localhost:8080/svn/HelloWorld/branches
$ svn mkdir http://localhost:8080/svn/HelloWorld/tags
Again, I don't normally bother with these directories, however if you're planning on a large project, or one in which multiple people might be working on their own "branches" of the code, it might make sense for you to create them.
The next step sounds a bit strange, and it took me a few times to get used to the idea, but it does work and it actually makes a lot of sense. The idea is to check out the empty directory where you want the project to exist within the repo, into the existing project directory. Since you're checking out an empty directory, this will not delete any existing files on your local system. Once you've checked out the empty directory, you then add your local files and then commit your changes (which uploads them to the repository.)
I found this tip in the book Version Control with Subversion, which is recommended by the authors of subversion. It's published by O'Reilly and can be purchased as a physical book, and it's also available online for free in HTML, PDF, and DocBook XML formats.
The process looks like this. First, go into the project directory created by Xcode, and "check out" the newly created empty directory in the repo where you want the project to end up. Note that there is a "." by itself at the end of the command - this makes it check out the indicated item AS the current directory, rather than creating a new directory within the current directory.
I keep all of my iPhone projects under "/Users/jms1/src/iPhone"
on my machine. This is a project called "HelloWorld".
$ cd /Users/jms1/src/iPhone/HelloWorld
$ svn co https://secure.jms1.net/svn/iPhoneCode/HelloWorld .
← don't forget the dot!
Checked out revision 0.
If you examine the project directory now, you will find that it created a directory called ".svn" which contains the information needed by svn to keep track of what's been changed and how to commit and revert changes. When you add other items to source control (below) you may see these directories within the project's other directories. Do not touch these directories - making changes within a ".svn" directory can cause problems for svn, and can also cause issues in the repository.
At this point, the project directory is a "working copy", meaning that svn knows which repo and directory it corresponds to, and is able to tell which files have been changed since the last time the repo was consulted. Since we checked out an empty directory, everything else within the directory is unknown to subversion, so if you run "svn status", you will see a "?" next to each item in the current directory, which means that svn doesn't have any knowledge about those files.
$ cd /Users/jms1/src/iPhone/HelloWorld $ svn status ? build ? ControllerView.xib ? main.m ? HelloWorld_Prefix.pch ? Info.plist ? HelloWorld.xcodeproj ? Classes ? MainWindow.xib
The next step is to "add" the existing project files, so svn knows what to do with them. This is actually pretty easy.
$ cd /Users/jms1/src/iPhone/HelloWorld
$ svn add *
Lots of stuff flying by...
The "svn add" command shows you what it's actually doing- adding every file within every sub-directory. Depending on the size of the project, this may be several pages of output. After it's done, we can run another "svn status" command, and now instead of "?", the items will now say "A", which means they will be added to the repository the next time you do a "check-in".
We don't want the "build" directory, or its contents, to be under source control, because they change every time you compile or build a new version, even for testing, and there's no need to keep them as long as you have the source code. Apple's documentation has you change the project settings to do the building in some other directory, but that's more work that it's really worth. All we need to do is remove the "build" directory from source control before it ever gets commited to the repository.
$ cd /Users/jms1/src/iPhone/HelloWorld $ svn revert --recursive build Reverted 'build' $ svn status ? build A ControllerView.xib A main.m A HelloWorld_Prefix.pch A Info.plist A HelloWorld.xcodeproj A HelloWorld.xcodeproj/jms1.pbxuser A HelloWorld.xcodeproj/jms1.mode1v3 A HelloWorld.xcodeproj/project.pbxproj A Classes A Classes/HelloWorldAppDelegate.m A Classes/HWViewController.h A Classes/HWViewController.m A Classes/HelloWorldAppDelegate.h A MainWindow.xib
As you can see, the "build" directory has returned to "?" status, which is what we want- it means that the directory will not be copied to the repository when we commit.
The next step is to "commit", or "check in", the files. This actually uploads their contents to the repository on the server.
$ cd /Users/jms1/src/iPhone/HelloWorld $ svn -m 'Initial project check-in' commit Adding Classes Adding Classes/HWViewController.h Adding Classes/HWViewController.m Adding Classes/HelloWorldAppDelegate.h Adding Classes/HelloWorldAppDelegate.m Adding ControllerView.xib Adding HelloWorld.xcodeproj Adding HelloWorld.xcodeproj/jms1.mode1v3 Adding HelloWorld.xcodeproj/jms1.pbxuser Adding HelloWorld.xcodeproj/project.pbxproj Adding HelloWorld_Prefix.pch Adding Info.plist Adding MainWindow.xib Adding main.m Transmitting file data ............ Committed revision 1.
The svn "-m" option specifies a message which is stored in the repository, describing what changes are being made and/or why you're making them. These messages are shown by the "svn log" command. Each check-in should have a message, and in fact if you forget to specify one (and don't explicitly tell it "no message") svn will refuse to commit your changes to the repository. If you set an SVN_EDITOR, VISUAL, or EDITOR environment variable to point to an editor, svn will use that editor to ask for your message (which is how I normally do it.)
We're actually done, however there is one minor issue- the "svn" command still doesn't know what to do with the "build" directory.
$ svn status ? build
Subversion allows each directory or file to have "properties" assigned to them. A property is a piece of meta-data with a name and zero or more values, stored in the repository but not actually part of the data used by other programs (i.e. setting a property doesn't change the file itself.) Some programs, such as the svn command-line program, recognize certain property names. For example, if a property called "svn:ignore" is attached to a directory, then whenever svn looks through the contents of that directory, it ignores anything whose name matches a pattern contained in a value of that property.
So what we're going to do is set an "svn:ignore" property on the project directory (the directory which contains the "build" directory) which tells it to ignore an entry called "build". The process looks like this:
$ cd /Users/jms1/src/iPhone/HelloWorld $ svn propset svn:ignore build . property 'svn:ignore' set on '.' $ svn status M .
As you can see, it's now ignoring "build", and the "M" status for "." means that a property was modified. When we commit that change to the repository...
$ cd /Users/jms1/src/iPhone/HelloWorld $ svn -m 'set property to ignore build directory' commit Sending . Committed revision 2. $ svn status $
... the "svn status" command produces no output, which means that our "working copy" on the local system is in sync with what the contents of repository looked like at the last time a "commit" or "update" was done.
It looks like a long process on the web page, but that's because I've taken the time to explain each step and show every command. When I actually do it, it only takes a few seconds:
$ cd ~/src/iPhone/HelloWorld
$ svn mkdir https://secure.jms1.net/svn/iPhoneCode/HelloWorld
$ svn co https://secure.jms1.net/svn/iPhoneCode/HelloWorld .
← don't forget the dot!
$ svn add *
$ svn revert --recursive build
$ svn ps svn:ignore build .
$ svn ci
Apple's documentation actually covers this part of things rather well. The only reason I mention it here is because if you've been following along with the page to set things up for yourself, you need to make sure you don't forget to do this. I may or may not add more to this in the future, but for now I don't really think it's needed.
When working with Xcode, remember that there are two separate commands for committing changes. On the SCM menu, the "Commit Changes..." item will only commit changes you've made to the current file, while "Commit Entire Project..." will do just that, commit any changes made to any files in the entire project. I didn't see that on my first iPhone project, and didn't realize the problem until I checked the project out to work on it using the laptop and most of my changes weren't there...