Inspired by coworker Nicholas Bergson-Shilcock’s personal brand, I decided to spend some time this weekend working on a brand of my own.
The result became www.timothyjcoulter.com.
I’m pretty happy with it. At first, I was simply going to link to my blog, put up some contact information and make my resume available. Instead, I got excited about syndication, and decided to parse the RSS feed in Javascript while making the whole thing work cleanly without a page refresh.
If you turn Javascript off, you’ll see my original idea.
Now that this brand is there, timothyjcoulter.com will be where I point people to for information about me. oneofthewolves.com will stay my blog — there’s no way I’m giving it up — but timothyjcoulter.com will contain more general information that’s not available on oneofthewolves.com.
The photo is courtesy of Chris Tanner.
Update, 10/6: It’s now functional in Internet Explorer 6 & 7 (though I gotta fix that darned loading icon.
)
The world is spinning when it comes to the Acts as Subversioned plugin. Yesterday, I was about to release version 0.2; I couldn’t, but it may not have been a bad thing.
So far (this includes v. 0.2), the plugin does two things: Lets you revision ActiveRecord objects using a Subversion repository, and lets you call Subversion-only functions that add value to your Ruby on Rails project. What’s really implied in that statement is this: The plugin alters the MySQL adapter for ActiveRecord at runtime, and adds in code that “does stuff” to the repository. Since, in “doing stuff,” the repository becomes the heavy-hitter for data within the models that are subversioned, the MySQL database simply turns into a cache holding the data that is the most up-to-date. Although this sounds like a good thing, is it really needed?
Through some emails with Bill Horsman, I stumbled upon a new direction that I think (and want) the plugin to move toward. Instead of “adding code” to the MySQL adapter at runtime (which, unfortunately, makes this a MySQL-and-similar only plugin), I would make my own ActiveRecord adapter that would connect directly to Subversion. What this means is that, along with the MySQLAdapter, and the PostgresSQLAdapter, and the OracleAdapter… there would be a SubversionAdapter. There would be no more of this “adding code” mantra; instead, it would be Subversion through and through.
To give you more of an idea what I’m talking about, instead of saying “adapter: mysql” inside of your database.yml, you’d now say “adapter: subversion”. The model for programming using a Subversion repository would be different, but I’m pretty sure I can replicate all the “find_by_X” functions as well as the id-based model structure, in (hopefully) O(1) time.
The problem with doing things using the Subversion adapter is that you’d only have access to one type of data store — this means you have to scrap the MySQL database and use Subversion only. This could lead to speed decreases (I’d guess that read/write speeds for Subversion are slower than that of MySQL), as well as a loss in functionality (complicated queries are most likely out the window). On the other hand, the benefits this would give to wiki-based programs would be enormous, as would the ability to see (and possibly measure) how data changes in your program over time.
How does this affect you/change your expectations of what this program is, and/or was? Is this “adapter style” more to what you were expecting in the first place, or less? Does it come with too many side effects?
My inclination is to go along this adapter-route, and see where it leads. Choosing this path would most likely 1) totally change the face of the plugin, and 2) affect what I release now, because #1 would make releasing less worthwhile.
Any thoughts? I don’t claim to be an expert at either Subversion, MySQL, or ActiveRecord, so any insight you can give me would be greatly appreciated.
PS: I’m still working things out with RubyForge. Even if things are going to change drastically, I’ll still release the new code as soon as I am able to.
Update, 4/23/2007: There’s no files on the RubyForge project site yet. I think I triggered a pretty nasty bug; now I’ve got to talk to support…
Okay, okay. I know I told all of you that I wasn’t releasing something for the next couple weeks. Well… I am. I thought about it for a while, and realized (as most programmers do) that I was pretty ambitious and promised too much for a single release. So, instead of giving you one big release packed full of features, I figured what the heck? I’ll give you what I have.
There’s some big things in this release, although they weren’t what I was expecting. First, the wrapper (and the plugin) now supports BDB filesystems. Personally, I don’t think BDB is better than FSFS (there’s some debate on that), but if you like it, you can use it. Second, the wrapper has undergone some big changes. I’d take a look at the changelog to get all of them.
The plugin has gotten some new stuff as well, but arguably not as much. It has gotten some new parameters, as well as some basic diffing support. Its internal structure has also changed to reflect changes in the wrapper.
You can find the new release at the RubyForge project page.
Overall, I would call this release an intermediate release. Although this isn’t everything I want it to be (yet), it should give you a bit more to play with. Enjoy!
UPDATE, 12/22/07: This page is obsolete. Please see my new post here.
So, I’m visiting my parents in Atlanta, and I finally have some free time. (You know how the parents are — they go to bed early.
). I’ve been wanting to get at this blog post for some time, and now I’ve finally found time to do it.
A while back, I found myself wanting to write something like a wiki in Ruby on Rails. Instead of reinventing the revision/diffing wheel needed to support a wiki, I wanted to use the Subversion Version Control system as a backend to my program. I searched far and wide for some documentation, but found nothing; all I found were sites asking for what I’m about to write. Since I couldn’t find any documentation, I looked to the unit tests. This turned out to be exactly what I needed.
From looking at the unit tests, and playing around with Subversion for some time, I found there are two states in which you can access a repository. One state is the client side, where you have a directory of files that should match up with the files in the repository. The other state is the server side; this is the side that receives commands from the client and executes them. Since I wanted to use Subversion as a backend to a program — and I didn’t want to maintain a separate list of files — I had to figure out how to send direct commands to the server. The following is a list of what I learned, and shows how to control a repository through the Ruby programming language.
Installing the SVN Bindings:
There are many sites that tell you how to install the bindings; I won’t be covering that here. This might help: http://collaboa.org/docs/svnbindings/install
(For Ubuntu users, just ‘apt-get install libsvn-ruby’)
Creating and opening a repository through code:
This is easier than printing “Hello World” in assembly. It’s that easy:
Svn::Repos.create(“/path/where/repsitory/should/be/created”)
If you think about the way Ruby works, then the above command shouldn’t be that complicated. Both Svn and Repos are modules; Repos is a child module of Svn. The create method is a method inside the Svn::Repos module.
Opening a repository is just as easy:
Svn::Repos.open(“/path/to/existing/repistory”)
Opening a repository is like opening a file for reading and writing.
Now, both the open and create commands return a repository object (this is not the official name) that you can interact with. To do anything useful, you should store the returned object into a variable, like this:
repos = Svn::Repos.create(“/path/where/repsitory/should/be/created”)
repos = Svn::Repos.open(“/path/to/existing/repository”)
We will use the repository object returned from the above two methods throughout the rest of this post.
Commiting a file:
When I first started writing my wiki-like program, the thing I was most interested in was commiting a file. This isn’t that easy to do, but it’s not hard.
The internals of Subversion are just guesswork to me, but generally, most transfers are done through transactions and streams. I’m not positive what a transaction is to the system (I know what the word means) but it seems to be that, whenever we open a new transaction, Subversion’s internal revision counter increases by one (this is important, as I’ll explain later). A stream, on the other hand, is much like a stream that you’d find in most programming languages; it lets us write to the repository as if we were writing to a file system.
Here’s how you’d commit a file using transactions and streams:
repos.fs.transaction do |txn|
checksum = MD5.new(data).hexdigest
stream = txn.root.apply_text(“/internal/path”, checksum)
stream.write(fileData)
stream.close
end
Note that it’s important we think of a Subversion repository as its own separate file system. All paths within the filesystem start with a forward slash (“/”) and are very similar (i.e., the same) as UNIX filesystem paths.
Getting contents of a file within the repository:
It’s great that we can commit files to the repository, but it doesn’t do us any good if we can’t get that data back out. Here’s how you’d do that:
stream = repos.fs.root(revision_id).file_contents(path)
stream.read
There are two things interesting about the above command: 1) We’re using streams, like before, but not within transactions, and 2) there’s a revision_id variable. Basically, the above command gives you the contents of a file for any revision, just as long as you tell it which revision you want. If you don’t specify a revision id, or you pass in the value of nil, the repository will give you the data corresponding to the most recent revision.
Note that the revision_id variable has some exactness to it, which may bite you in the butt later. If you pass in a revision id, you must pass in a revision id where the file you want was specifically edited. What does this mean? Say you have two files in a new repository, and each one was edited once. The first edit would push Subversion’s internal revision count to 1, and the second edit would push the count to two. If you use a revision_id of 2 to get the data within the first file, nothing will be returned because the first file was not edited on the second revision. If you want the data from the first file, you have to be specific; in this case, you’d use a revision_id of 1.
So how do you know which revision id’s correspond to a given file? Read on.
Figuring out the revisions of each file:
All we need to do is run this command:
repos.file_revs(path, 0, repos.fs.youngest_rev)
All this says is “Give us all the revisions between the initial revision (0) and the most recent revision (repos.fs.youngest_rev) corresponding to the file at path path.” This returns a lot of data.
What you get back is an array of arrays; each of the inner arrays represents one revision within the repository. Each revision has three parts: the path, the revision id, and the date the revision was made. The path is the same path we passed in (it seems redundant, but it’s actually quite handy), and the revision id and date are String representations of what their names imply. Since the revision is represented as an array, these values are at index 0, 1 and 2 respectively.
Note: The date value returned may not be what you expect. Read the next section to figure out why.
Getting the date of a revision (or, getting any property for that matter):
Whenever you commit something — or more importantly, whenever you create a new transaction — Subversion automatically stores the time at which the transaction occurred. It’s easy to get by calling this command:
date = repos.prop(Svn::Core::PROP_REVISION_DATE, revision_id)
The prop() function returns any stored property for the revision specified by revision_id; we’re using a Subversion-specific constant to ask for the date.
Note that the date returned is a String representation of the date; the prop() function only returns strings. However, we can turn it into a Ruby Time object by doing this:
Time.parse(date)
(For you advanced Ruby programmers, and all those who read the last section, the Subversion bindings actually override the Time.parse function to handle the output from the Subversion repository. Cool isn’t it?
)
Setting the author for each revision:
Sometimes it’s nice to tell the repository which author made which revision — like, say, when you’re committing files. Since a new revision is created each time we create a new transaction, we can put the following statement into the transaction code given above:
txn.set_prop(Svn::Core::PROP_REVISION_AUTHOR, author)
We again use a Subversion specific constant, but this time, we’re setting the data instead of getting it. The author variable holds the author’s name as a string.
Note that if we wanted to get the author back out later, we’d do the exact same thing we did to get the date, except we’d use the author constant instead:
author = repos.prop(Svn::Core::PROP_REVISION_AUTHOR, revision_id)
Getting the diffs from one revision to the next:
Sometimes, especially in a wiki, it’s handy to show the user the differences from one revision to the next. It’s easy to get the diffs from the repository, but it’s a little complicated to understand what comes back.
To get the diffs, do this:
info = Svn::Info.new(“/path/to/repository”, revision_id)
diffs = info.diffs
Subversion has a separate construct for getting specific revision information. This is the Info construct. We create a new Info construct to get the diffs for a certain revision. The thing that’s different about doing things this way is we totally bypass the repository; or, at least, we don’t use the repository variable we created before. Instead, we pass the repository path and a revision id into the Info constructor, and we get what we want back out.
Well, actually, I lied — we don’t get what we want back out. Instead, we get a lot more than what we wanted. The diffs variable above will contain a hash of all the diffs for the given revision, where the key represents a file path within the repository, and the value is another hash of data corresponding to that file. This second hash — the “value hash” — contains either an :added or a :modified key. These keys are Ruby symbols, and, depending on which one is present, the file was either added or modifed. The value associated with each of these keys is a Subversion DiffEntry object. We don’t care about this object, per se, but we do care about its body instance variable; this variable will give us a unified diff of a certain file showing the differences from one revision to the next.
Can it get any easier?
Now, I know some of the above functions may seem complicated or tedious, and that’s because they are — there’s no getting around that. However, I’ve written a wrapper class that does some of the tedium for us. Download it here: svn_repos.rb
EDIT, 4/2/07:Â The wrapper class linked above is old. A newer one can be found within the acts_as_subversioned plugin project, located on RubyForge.
Note to Ruby on Rails users:
My next post will be a howto describing how to use the wrapper class within a Ruby on Rails project. I’ll be giving a Rails plugin for download, which includes a test harness to test Subversion repositories like you test MySQL databases; that is, using fixtures! Believe me, it’s awesome. (Because I’m busy, I have no guarantee when I can get this out. However, I hope it to be soon.)
I’d love any comments you readers can give me on this. It feels great to finally post again.