A Crash Course in Subversion, Part 2
Editor's Note: This piece picks up where the article A Crash Course in Subversion left off.]
Adding, Copying, Renaming, and Removing Files
Well, you've learned the basics, the Subversion commands that fill 90% of your day to day routine, but sooner or later you'll need to hit that last 10%, so let's get on with it. So far we've mainly seen commands associated with making changes to the contents of files and committing them to the repository. But if you recall, Subversion doesn't just version files contents, it versions tree structures. This next section covers the commands that let you make changes to those tree structures, by adding, copying, renaming, and deleting files and directories.
Adding Files and Directories
Sooner or later, you're almost certainly going to want to add some new files or directories to your Subversion repository, so you'll have to become familiar with the svn add command. It works just as you would expect: You check out a working copy, create the file (or directory), and run svn add FILENAME. This schedules the file for addition, and later you can run svn commit to complete the process. The whole procedure looks like this:
$ ls Makefile bar.c foo.c main.c $ svn status ? Makefile $ svn add Makefile A Makefile $ svn status A Makefile $ svn commit -m "Added Makefile" Adding Makefile Transmitting file data . Committed revision 6. $ svn status -v 2 2 rooneg . 5 5 rooneg Makefile 3 3 rooneg bar.c 4 4 colonr foo.c 2 2 rooneg main.c $
So again, you have some new output to interpret. The first svn status has a ? in the first column of the line about Makefile, which means that Makefile isn't currently under Subversion's control. Next, you run svn add Makefile , and Makefile is scheduled for addition, which you can see from the output of the second svn status, where the first column is an A. You then commit the change, adding Makefile to the repository, and the final svn status -v shows it as any other versioned file.
If you're adding a new directory, there's no need to create and add it in two steps, because you can use Subversion's svn mkdir command, which you used earlier to create directories in the repository, in the working copy as well. Simply run svn mkdir DIRNAME, and it will create the new directory and schedule it for addition when you next commit, all in one step. If you created the directory before you decided to add it, then a regular svn add will schedule it, and all of its contents, for addition. If you don't want to add all of the directory's contents, you can pass the --non-recursive flag to svn add, and only the top-level directory will be scheduled for addition.
Copying and Moving Versioned Items
Adding a file or directory to your version control system isn't all that interesting, but where Subversion really shines is in its ability to copy and rename files and directories, while still retaining their revision history. Many other version control systems, specifically RCS, and the systems built on top of them, such as Perforce and CVS, don't make it easy to do this. In Perforce, you can work around the lack of a copy or move command by integrating from one file to another, and then possibly deleting the original, and in CVS you can fake things by poking around in the repository by hand and copying the RCS files around, but neither gives you a true copy or move command. In Subversion, the operation is as simple as svn copy FROM TO or svn move FROM TO. For simple copying or moving of a file or directory within your project, you generally do this within a working copy, and FROM and TO are the path to the source and destination. For some other operations, it's more convenient to perform the copy or move in the repository directly, but I'll hold off on covering that until I discuss branches and tags later in the chapter. For now, let's take a look at an example of how to use svn copy and svn move.
$ ls Makefile bar.c foo.c main.c $ svn status $ svn copy bar.c baz.c A baz.c $ svn status A + baz.c $ svn commit -m "copied bar.c to baz.c" Adding baz.c Committed revision 7. $ svn status -v 2 2 rooneg . 5 5 rooneg Makefile 3 3 rooneg bar.c 6 6 rooneg baz.c 4 4 rooneg foo.c 2 2 rooneg main.c $ svn move bar.c zot.c A zot.c D bar.c $ svn status D bar.c A +zot.c $ svn commit -m "renamed bar.c to zot.c" Deleting bar.c Adding zot.c Committed revision 8. $ svn status -v 2 2 rooneg . 5 5 rooneg Makefile 6 6 rooneg baz.c 4 4 rooneg foo.c 2 2 rooneg main.c 7 7 rooneg zot.c $
You've seen most of this output already, but the second svn status has something new. The fourth column of output for baz.c is a +, which means that the file has been added with history. When the change is committed to the repository, the fact that baz.c is a copy of foo.c is recorded, so the revision history of the file remains intact. Similarly, when you moved bar.c to zot.c, the output of svn status shows that foo.c has been deleted (and thus has a D in the first column of its line of status output), and zot.c has been added with history. Again, this fact is recorded in the repository at commit time.
Deleting Versioned Items
Now that you've mastered adding, moving, and copying files, the last of the basic tasks you're likely to perform in the working copy is removing files and directories. As you might expect, the command to do this is svn delete, and it works much as you would expect. svn delete PATH removes the file or directory at PATH and schedules it for removal from the repository. When you next use svn commit, the file or directory is removed from the repository itself. Let's take a look at the process.
$ ls Makefile baz.c foo.c main.c zot.c $ svn status $ svn delete baz.c D baz.c $ svn status D baz.c $ svn commit -m "removing baz.c" Deleting baz.c Committed revision 9. $ svn status -v 2 2 rooneg . 5 5 rooneg Makefile 4 4 rooneg foo.c 2 2 rooneg main.c 7 7 rooneg zot.c $
This output is quite similar to what you saw when you moved a file. When you've deleted something, it shows up in the output of svn status with a D in the first column.
Note that although the file has been removed from the HEAD revision of the repository, it still exists in the previous revisions, and you can easily get the file back either by using svn copy to copy the file from the previous revision into your working copy or by using svn merge to reverse the entire commit that removed it. I discuss the svn merge technique later, but let's now look at how you use svn copy to "undelete" a file.
$ ls Makefile foo.c m1a1in.c zot.c $ svn copy --revision 7 file:///path/to/repos/trunk/baz.c baz.c A baz.c $ svn commit -m "resurrect baz.c from the repository" Adding baz.c Committed revision 10. $
OK, so what happened here? You just copied baz.c as it existed in revision 7 (the last revision before you deleted it) directly from the repository into your working copy. Then you committed the change, and the process of restoring the deleted file completed. No matter what kind of change you make to your project in HEAD, all previous versions always exist in the repository, so you can always revert to the previous state if you make a mistake. You can even do this in a single command, without a working copy at all, by performing the copy within the repository itself, something you'll explore in the next section when you learn about using svn copy to work with branches and tags.