<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>install.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -12,12 +12,43 @@ h3. Features
 * Logic lives in the JavaScript (You may consider this a feature or a flaw. I
   consider it a feature.)
 
+* Support for text input, text area and select fields
+* Support for foreign key parameters for select fields
+* Customizable. One can set: 
+** class for edit element
+** class submit and cancel buttons
+** custom functions to be called on Ajax.Request onLoading and onComplete events 
+   i.e. to set up a spinner.
+
 The better-edit-in-place script makes it easy to create AJAX edit-in-place
 fields in a RESTful web app. Note that if your approach isn't RESTful, 
 then this script won't do much for you.
 
-To make an element editable, give it the class name @editable@. Specify 
-the element's resource url as the element's @rel@ attribute, then give it
+If you have not installed this plugin using @script/plugin install@ script,
+then copy src/editable.js file to your public/javascripts folder.
+To load this file with your Rails app simply add the following line to your layout:
+
+&lt;pre&gt;&lt;%= javascript_include_tag 'editable' %&gt;&lt;/pre&gt;
+
+If you're using the classic way of handling events:
+
+  document.observe('dom:loaded', function(event) {
+    Editable.setupAll();
+  });
+
+Another solution is to use event delegation:
+
+  document.delegators('click', {
+    '.editable': function(element) {
+      element.editable();
+    }
+  });
+
+To make an element editable, give it the class name @editable@. You can use different
+class name, but in this case you need to pass it to Editable.setupAll method 
+or change CSS selector if you're using event delegation.
+
+Specify the element's resource url as the element's @rel@ attribute, then give it
 an @id@ attribute that contains the name of the record's model, as well as
 the attribute to be edited. You can also add the record's id as well, so
 as not to have multiple elements with the same id in the same page. All in,
@@ -33,7 +64,7 @@ A Rails example:
 
   respond_to do |format|
     format.html # Whatever
-    format.js { render :json =&gt; @list }
+    format.json { render :json =&gt; @list }
   end
 
 h3. Using it with Rails
@@ -67,9 +98,96 @@ to JSON. To do so, put this line in an initializer, or environment.rb:
 
   ActiveRecord::Base.include_root_in_json = false
 
+h3. Customizations
+
+h4. Input types
+
+By default input text element is used. To change it to text area or select, you need to set
+editField type:
+
+  document.delegators('click', {
+    '.editable': function(element) {
+      element.editable({
+        editField: {'type': 'textarea'} // or 'select'
+      });
+    }
+  });
+
+h4. Class names
+
+Edit element class name can also be set:
+
+    element.editable({
+      editField: {
+        'type': 'textarea',
+        'class': 'editable-input'
+      }
+    });
+
+Class name can also be set for submit and cancel buttons:
+
+    element.editable({
+      submitButtonClass: 'editable-submit',
+      cancelButtonClass: 'editable-cancel'
+    });
+
+h4. Select box options
+
+For select elements you need to specify possible options:
+
+    element.editable({
+      editField: {
+        'type': 'select',
+        'options': [[&quot;black&quot;, &quot;1&quot;], [&quot;gray&quot;, &quot;2&quot;], [&quot;white&quot;, &quot;3&quot;]]
+      }
+    });
+
+To load data directly from Rails, you can use to_json method:
+
+    element.editable({
+      editField: {
+        'type': 'select',
+        'options': &lt;%= Colors.all.map{|c| [c.name, c.id.to_s]}.to_json %&gt;
+      }
+    });
+
+h4. Foreign keys
+
+If edited attribute is a foreign key, you need to explictly tell so:
+
+    element.editable({
+      editField: {
+        'type': 'select',
+        'options': &lt;%= Author.all.map{|c| [c.name, c.id.to_s]}.to_json %&gt;,
+        'foreignKey': true
+      }
+    });
+
+You also need to set #to_s method on the associated model i.e.
+
+   class Author &lt; ActiveRecord::Base
+     def to_s
+       name
+     end
+   end
+
+This will automatically send correct id to the server and then set value of
+edit-in-place element properly.
+
+h4. Ajax.Request callbacks
+
+You can set custom callbacks for Ajax.Request onLoading and onComplete events:
+
+    element.editable({
+      onLoading: function() {alert(&quot;Loading...&quot;)};
+      onComplete: function() {alert(&quot;Request completed&quot;)};
+    });
+
 h3. TODO
 
 * Escape key should cancel (simple event handler)
 * Maybe some more options (but not too many)
 
-Copyright (c) 2008 Pat Nakajima
\ No newline at end of file
+* Instead of adding .json to URL, set proper content-type
+
+Copyright (c) 2008 Pat Nakajima</diff>
      <filename>README.textile</filename>
    </modified>
    <modified>
      <diff>@@ -11,7 +11,7 @@ module Nakajima
       options.delete(:url) # Just in case it wasn't cleared already
 
       classes = options[:class].split(' ') rescue []
-      classes &lt;&lt; 'editable'
+      classes &lt;&lt; 'editable' if classes.empty?
       options[:class] = classes.uniq.join(' ')
 
       content_tag(options.delete(:tag), record.send(field), options)
@@ -19,4 +19,4 @@ module Nakajima
   end  
 end
 
-ActionView::Base.send :include, Nakajima::BetterEditInPlace
\ No newline at end of file
+ActionView::Base.send :include, Nakajima::BetterEditInPlace</diff>
      <filename>init.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,125 +1,224 @@
 // Editable: Better in-place-editing
 // http://github.com/nakajima/nakatype/wikis/better-edit-in-place-editable-js
+
 var Editable = Class.create({
-  editFieldTag: 'input',
-
-  initialize: function(element, options) {
-    Object.extend(this, options);
-    this.element = $(element);
-    this.elementID = this.element.identify();
-    this.field = this.parseField();
-    this.value = this.element.innerHTML;
-    this.setupForm();
-    this.setupBehaviors();
-  },
-  
-  // In order to parse the field correctly, it's necessary that the element
-  // you want to edit in place for have an id of (model_name)_(id)_(field_name).
-  // For example, if you want to edit the &quot;caption&quot; field in a &quot;Photo&quot; model,
-  // your id should be something like &quot;photo_#{@photo.id}_caption&quot;.
-  // If you want to edit the &quot;comment_body&quot; field in a &quot;MemberBlogPost&quot; model,
-  // it would be: &quot;member_blog_post_#{@member_blog_post.id}_comment_body&quot;
-  parseField: function() {
-    var matches = this.elementID.match(/(.*)_\d*_(.*)/);
-    this.modelName = matches[1];
-    this.fieldName = matches[2];
-    return this.modelName + '[' + this.fieldName + ']';
-  },
-  
-  // Create the editing form for the editable and inserts it after the element.
-  // If window._token is defined, then we add a hidden element that contains the
-  // authenticity_token for the AJAX request.
-  setupForm: function() {
-    this.editForm = new Element('form', { 'action': this.element.readAttribute('rel'), 'style':'display:none', 'class':'editor' });
-    this.editInput = new Element(this.editFieldTag, { 'name':this.field, 'id':('edit_' + this.element.identify()) });
-    this.editInput.value = this.element.innerHTML;
-    var saveInput = new Element('input', { type:'submit', value: Editable.options.saveText });
-    this.cancelLink = new Element('a', { href:'#' }); this.cancelLink.update(Editable.options.cancelText);
-    var methodInput = new Element('input', { type:'hidden', value:'put', name:'_method' });
-    if (typeof(window._token) != 'undefined') {
-      this.editForm.insert(new Element('input', {
-        type: 'hidden',
-        value: window._token,
-        name: 'authenticity_token'
-      }));
+    initialize: function(element, options) {
+        this.element = $(element);
+        Object.extend(this, options);
+
+        // Set default values for options
+        this.editField = this.editField || {};
+        this.editField.type = this.editField.type || 'input';
+        this.onLoading = this.onLoading || Prototype.emptyFunction;
+        this.onComplete = this.onComplete || Prototype.emptyFunction;
+
+        this.field = this.parseField();
+        this.value = this.element.innerHTML;
+
+        this.setupForm();
+        this.setupBehaviors();
+    },
+
+    // In order to parse the field correctly, it's necessary that the element
+    // you want to edit in place for have an id of (model_name)_(id)_(field_name).
+    // For example, if you want to edit the &quot;caption&quot; field in a &quot;Photo&quot; model,
+    // your id should be something like &quot;photo_#{@photo.id}_caption&quot;.
+    // If you want to edit the &quot;comment_body&quot; field in a &quot;MemberBlogPost&quot; model,
+    // it would be: &quot;member_blog_post_#{@member_blog_post.id}_comment_body&quot;
+    parseField: function() {
+        var matches = this.element.id.match(/(.*)_\d*_(.*)/);
+        this.modelName = matches[1];
+        this.fieldName = matches[2];
+        if (this.editField.foreignKey) this.fieldName += '_id';
+        return this.modelName + '[' + this.fieldName + ']';
+    },
+
+    // Create the editing form for the editable and inserts it after the element.
+    // If window._token is defined, then we add a hidden element that contains the
+    // authenticity_token for the AJAX request.
+    setupForm: function() {
+        this.editForm = new Element('form', {
+            'action': this.element.readAttribute('rel'),
+            'style':'display:none',
+            'class':'in-place-editor'
+        });
+
+        this.setupInputElement();
+
+        if (this.editField.tag != 'select') {
+            this.saveInput = new Element('input', {
+                type:'submit',
+                value: Editable.options.saveText
+            });
+            if (this.submitButtonClass) this.saveInput.addClassName(this.submitButtonClass);
+
+            this.cancelLink = new Element('a', {
+                href:'#'
+            }).update(Editable.options.cancelText);
+            if (this.cancelButtonClass) this.cancelLink.addClassName(this.cancelButtonClass);
+        }
+
+        var methodInput = new Element('input', {
+            type:'hidden',
+            value:'put',
+            name:'_method'
+        });
+        if (typeof(window._token) != 'undefined') {
+            this.editForm.insert(new Element('input', {
+                type: 'hidden',
+                value: window._token,
+                name: 'authenticity_token'
+            }));
+        }
+
+        this.editForm.insert(this.editField.element);
+        if (this.editField.type != 'select') {
+            this.editForm.insert(this.saveInput);
+            this.editForm.insert(this.cancelLink);
+        }
+        this.editForm.insert(methodInput);
+        this.element.insert({
+            after: this.editForm
+        });
+    },
+
+    // Create input element - text input, text area or select box.
+    setupInputElement: function() {
+        this.editField.element = new Element(this.editField.type, {
+            'name':this.field,
+            'id':('edit_' + this.element.id)
+        });
+        if(this.editField['class']) this.editField.element.addClassName(this.editField['class']);
+
+        if(this.editField.type == 'select') {
+            // Create options
+            var options = this.editField.options.map(function(option) {
+                return new Option(option[0], option[1]);
+            });
+            // And assign them to select element
+            options.each(function(option, index) {
+                this.editField.element.options[index] = options[index];
+            }.bind(this));
+
+            // Set selected option
+            try {
+                this.editField.element.selectedIndex = $A(this.editField.element.options).find(function(option) {
+                    return option.text == this.element.innerHTML;
+            }.bind(this)).index;
+            } catch(e) {
+                this.editField.element.selectedIndex = 0;
+            }
+
+            // Set event handlers to automaticall submit form when option is changed
+            this.editField.element.observe('blur', this.cancel.bind(this));
+            this.editField.element.observe('change', this.save.bind(this));
+        } else {
+            // Copy value of the element to the input
+            this.editField.element.value = this.element.innerHTML;
+        }
+    },
+
+    // Sets up event handles for editable.
+    setupBehaviors: function() {
+        this.element.observe('click', this.edit.bindAsEventListener(this));
+        if (this.saveInput) this.editForm.observe('submit', this.save.bindAsEventListener(this));
+        if (this.cancelLink) this.cancelLink.observe('click', this.cancel.bindAsEventListener(this));
+    },
+
+    // Event Handler that activates form and hides element.
+    edit: function(event) {
+        this.element.hide();
+        this.editForm.show();
+        this.editField.element.activate ? this.editField.element.activate() : this.editField.element.focus();
+        if (event) event.stop();
+    },
+
+    // Event handler that makes request to server, then handles a JSON response.
+    save: function(event) {
+        var pars = this.editForm.serialize(true);
+        var url = this.editForm.readAttribute('action');
+        this.editForm.disable();
+        new Ajax.Request(url + &quot;.json&quot;, {
+            method: 'put',
+            parameters: pars,
+            onSuccess: function(transport) {
+                var json = transport.responseText.evalJSON();
+                var value;
+                if (json[this.modelName]) {
+                    value = json[this.modelName][this.fieldName];
+                }
+                else {
+                    value = json[this.fieldName];
+                }
+                // If we're using foreign key, read value from the form
+                // instead of displaying foreign key ID
+                if (this.editField.foreignKey) {
+                    value = $A(this.editField.element.options).find(function(option) {
+                        return option.value == value;
+                    }).text;
+                }
+                this.value = value;
+                this.editField.element.value = this.value;
+                this.element.update(this.value);
+                this.editForm.enable();
+                if (Editable.afterSave) {
+                    Editable.afterSave(this);
+                }
+                this.cancel();
+            }.bind(this),
+            onFailure: function(transport) {
+                this.cancel();
+                alert(&quot;Your change could not be saved.&quot;);
+            }.bind(this),
+            onLoading: this.onLoading.bind(this),
+            onComplete: this.onComplete.bind(this)
+        });
+        if (event) {
+            event.stop();
+        }
+    },
+
+    // Event handler that restores original editable value and hides form.
+    cancel: function(event) {
+        this.element.show();
+        this.editField.element.value = this.value;
+        this.editForm.hide();
+        if (event) {
+            event.stop();
+        }
+    },
+
+    // Removes editable behavior from an element.
+    clobber: function() {
+        this.element.stopObserving('click');
+        try {
+            this.editForm.remove(); delete(this);
+        }
+        catch(e) {
+            delete(this);
+        }
     }
-    this.editForm.insert(this.editInput);
-    this.editForm.insert(saveInput);
-    this.editForm.insert(this.cancelLink);
-    this.editForm.insert(methodInput);
-    this.element.insert({after: this.editForm });
-  },
-
-  // Sets up event handles for editable.
-  setupBehaviors: function() {
-    this.element.observe('click', this.edit.bindAsEventListener(this));
-    this.editForm.observe('submit', this.save.bindAsEventListener(this));
-    this.cancelLink.observe('click', this.cancel.bindAsEventListener(this));
-  },
-
-  // Event Handler that activates form and hides element
-  edit: function(event) {
-    this.element.hide();
-    this.editForm.show();
-    this.editInput.activate();
-    if (event) { event.stop(); }
-  },
-
-  // Event handler that makes request to server, then handles a JSON response.
-  save: function(event) {
-    var pars = this.editForm.serialize();
-    var url = this.editForm.readAttribute('action');
-    this.editForm.disable();
-    new Ajax.Request(url, {
-      method: 'put',
-      parameters: pars,
-      onSuccess: function(transport) {
-        var json = transport.responseText.evalJSON();
-        if (json[this.modelName]) { this.value = json[this.modelName][this.fieldName]; }
-        else { this.value = json[this.fieldName]; }
-        this.editInput.value = this.value;
-        this.element.update(this.value);
-        this.editForm.enable();
-        if (Editable.afterSave) { Editable.afterSave(this); }
-        this.cancel();
-      }.bind(this),
-      onFailure: function(transport) {
-        this.cancel();
-        alert(&quot;Your change could not be saved.&quot;);
-      }.bind(this)
-    });
-    if (event) { event.stop(); }
-  },
-
-  // Event handler that restores original editable value and hides form.
-  cancel: function(event) {
-    this.element.show();
-    this.editInput.value = this.value;
-    this.editForm.hide();
-    if (event) { event.stop(); }
-  },
-  
-  // Removes editable behavior from an element.
-  clobber: function() {
-    this.element.stopObserving('click');
-    try { this.editForm.remove(); delete(this); }
-    catch(e) { delete(this); }
-  }
 });
 
+// Editable class methods.
 Object.extend(Editable, {
-  options: {
-    cancelText: 'Cancel',
-    saveText: 'Save'
-  },
-  
-  create: function(element) {
-    new Editable(element);
-  },
-  
-  setupAll: function() {
-    $$('.editable').each(Editable.create);
-  }
+    options: {
+        saveText: 'Save',
+        cancelText: 'Cancel'
+    },
+    create: function(element) {
+        new Editable(element);
+    },
+
+    setupAll: function(klass) {
+        klass = klass || '.editable';
+        $$(klass).each(Editable.create);
+    }
 });
 
-Event.observe(document, 'dom:loaded', Editable.setupAll);
\ No newline at end of file
+// Helper method for event delegation
+Element.addMethods({
+    editable: function(element, options) {
+        new Editable(element, options).edit();
+    }
+});</diff>
      <filename>src/editable.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 require 'rubygems'
-require 'activesupport'
 require 'sinatra'
+require 'activesupport'
 
 TEST_ROOT = File.join(File.dirname(__FILE__), '..')
 
@@ -12,13 +12,17 @@ get '/src/editable.js' do
   File.read(File.join(TEST_ROOT, '..', 'src', 'editable.js'))
 end
 
-put '/lists/1' do
-  puts params.to_yaml
-  list = { :title =&gt; params['list[title]'] }
+put '/lists/1.json' do
+  list = { :title =&gt; params['list']['title'] }
   list.to_json
 end
 
-put '/users/2' do
-  user = { :user =&gt; { :first_name =&gt; params['user[first_name]'] } }
+put '/users/2.json' do
+  user = { :user =&gt; { :first_name =&gt; params['user']['first_name'] } }
   user.to_json
-end
\ No newline at end of file
+end
+
+put '/posts/5.json' do
+  post = { :post =&gt; { :author_id =&gt; params['post']['author_id'] } }
+  post.to_json
+end</diff>
      <filename>test/lib/test_server.rb</filename>
    </modified>
    <modified>
      <diff>@@ -17,6 +17,12 @@
 
 &lt;div id=&quot;user_2_first_name&quot; rel=&quot;http://localhost:4567/users/2&quot; class=&quot;editable&quot;&gt;This is the user&lt;/div&gt;
 
+&lt;div id=&quot;post_3_body&quot; rel=&quot;http://localhost:4567/posts/3&quot; class=&quot;editable&quot;&gt;This is the post body&lt;/div&gt;
+
+&lt;div id=&quot;color_4_name&quot; rel=&quot;http://localhost:4567/colors/4&quot; class=&quot;editable&quot;&gt;White color&lt;/div&gt;
+
+&lt;div id=&quot;post_5_author&quot; rel=&quot;http://localhost:4567/posts/5&quot; class=&quot;editable&quot;&gt;Mike Rotch&lt;/div&gt;
+
 &lt;!-- Log output --&gt;
 &lt;div id=&quot;testlog&quot;&gt; &lt;/div&gt;
 
@@ -30,6 +36,8 @@ new Test.Unit.Runner({
     $$('form').invoke('remove');
     this.list = new Editable('list_1_title');
     this.user = new Editable('user_2_first_name');
+    this.post = new Editable('post_3_body');
+    this.color = new Editable('color_4_name');
   },
   
   testShouldGetElement: function() {
@@ -111,7 +119,7 @@ new Test.Unit.Runner({
     var properResult = &quot;This is a new value. Dude.&quot;;
     var form = this.list.element.next('form');
     this.list.edit({ stop: Prototype.K });
-    this.list.editInput.value = properResult;
+    this.list.editField.element.value = properResult;
     this.list.save({ element: function() { return form; }, stop: Prototype.K });
     this.wait(300, function() {
       this.assertEqual(properResult, this.list.element.innerHTML);
@@ -124,11 +132,11 @@ new Test.Unit.Runner({
   testShouldCancelForm: function() {
     var form = this.list.editForm;
     this.list.edit({ stop: Prototype.K });
-    this.list.editInput.value = 'A new value';
+    this.list.editField.element.value = 'A new value';
     this.list.cancel({ stop: Prototype.K });
     this.assertVisible(this.list.element);
     this.assertNotVisible(this.list.editForm);
-    this.assertEqual(this.list.value, this.list.editInput.value);
+    this.assertEqual(this.list.value, this.list.editField.element.value);
   },
   
   testShouldHandleBehaviors: function() {
@@ -168,17 +176,100 @@ new Test.Unit.Runner({
       this.assert(this.called, &quot;didn't call afterSave callback&quot;);
     });
   },
+
+  testShouldCallOnLoadingCallback: function() {
+    this.called = false;
+    this.list = new Editable('list_1_title', {
+        onLoading: function() {
+            this.called = true;
+        }.bind(this)
+    });    
+    this.list.save();
+    this.wait(300, function() {
+      this.assert(this.called, &quot;didn't call onLoading callback&quot;);
+    });
+  },
+
+  testShouldCallOnCompleteCallback: function() {
+    this.called = false;
+    this.list = new Editable('list_1_title', {
+        onLoading: function() {
+            this.called = true;
+        }.bind(this)
+    });
+    this.list.save();
+    this.wait(300, function() {
+      this.assert(this.called, &quot;didn't call onComplete callback&quot;);
+    });
+  },
   
   testShouldClobber: function() {
     this.fail = false
     $('list_1_title').observe('click', function() { this.fail = true }.bind(this))
     this.list.clobber();
-    this.assert(!this.fail, &quot;didn't unregister observers&quot;)
+    this.assert(!this.fail, &quot;didn't unregister observers&quot;);
+  },
+
+  testShouldSetClassNameOnInput: function() {    
+    this.list = new Editable('list_1_title', {
+        editField: {'class': 'custom'}
+    });
+    this.assert(this.list.editField.element.hasClassName('custom'), &quot;didn't set class name on input element&quot;);
+  },
+
+  testShouldSetClassNameOnSaveButton: function() {
+    this.list = new Editable('list_1_title', {
+        submitButtonClass: 'custom'
+    });
+    this.assert(this.list.saveInput.hasClassName('custom'), &quot;didn't set class name on save button&quot;);
+  },
+
+  testShouldSetClassNameOnCancelLink: function() {
+    this.list = new Editable('list_1_title', {
+        cancelButtonClass: 'custom'
+    });
+    this.assert(this.list.cancelLink.hasClassName('custom'), &quot;didn't set class name on cancel button&quot;);
+  },
+
+  testShouldCreateFormWithTextAreaEditInput: function() {
+    this.post = new Editable('post_3_body', {
+        editField: {'type': 'textarea'}
+    });
+    this.assert(this.post.editForm.down('textarea'), &quot;didn't create textarea input element&quot;);
+  },
+
+  testShouldCreateFormWithSelectEditInput: function() {
+    this.color = new Editable('color_4_name', {
+        editField: {
+            type: 'select',
+            options: [['White color', '1'], ['Black color', '2']]
+        }
+    });
+    this.assert(this.color.editForm.down('select'), &quot;didn't create select input element&quot;);
+    this.assertEqual(2, this.color.editField.element.options.length, &quot;didn't create options for select input element&quot;);
+  },
+
+  testShouldSaveForeignKeyAttribute: function() {
+    this.post = new Editable('post_5_author', {
+        editField: {
+            type: 'select',
+            options: [['Mike Rotch', '1'], ['I. P. Freely', '2']],
+            foreignKey: true
+        }
+    });
+    this.post.editField.element.selectedIndex = 1;
+    this.post.save();
+    
+    this.wait(300, function() {
+        this.assertEqual('I. P. Freely', this.post.element.innerHTML, &quot;didn't set foreign key correctly&quot;);
+    });
   },
 
   teardown: function() {
     this.list.clobber();
-    this.user.clobber();
+    this.user.clobber();    
+    this.post.clobber();
+    this.color.clobber();
   }
   
 });</diff>
      <filename>test/unit/editable_test.html</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>a6934258566f7f9018d5af0a0af183864763cab5</id>
    </parent>
  </parents>
  <author>
    <name>Szymon Nowak</name>
    <email>szymon.nowak@u2i.com</email>
  </author>
  <url>http://github.com/nakajima/better-edit-in-place/commit/c709253db76c3139fd65f36a3c9b82f197ad20fd</url>
  <id>c709253db76c3139fd65f36a3c9b82f197ad20fd</id>
  <committed-date>2009-01-21T13:55:57-08:00</committed-date>
  <authored-date>2009-01-21T07:34:51-08:00</authored-date>
  <message>Huge update - see README for details

Signed-off-by: Pat Nakajima &lt;patnakajima@gmail.com&gt;</message>
  <tree>50fa9fb2601e694814750873546c77a272b6e119</tree>
  <committer>
    <name>Pat Nakajima</name>
    <email>patnakajima@gmail.com</email>
  </committer>
</commit>
