You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2014/04/01 13:51:43 UTC

svn commit: r1583639 [2/3] - in /subversion/branches/ra-git: ./ build/ac-macros/ subversion/include/ subversion/libsvn_client/ subversion/libsvn_ra/ subversion/libsvn_ra_git/ tools/dev/unix-build/

Added: subversion/branches/ra-git/subversion/libsvn_ra_git/ra_plugin.c
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/subversion/libsvn_ra_git/ra_plugin.c?rev=1583639&view=auto
==============================================================================
--- subversion/branches/ra-git/subversion/libsvn_ra_git/ra_plugin.c (added)
+++ subversion/branches/ra-git/subversion/libsvn_ra_git/ra_plugin.c Tue Apr  1 11:51:43 2014
@@ -0,0 +1,2543 @@
+/*
+ * ra_plugin.c : the main RA module for git repository access
+ *
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ */
+
+#include "svn_hash.h"
+#include "svn_ra.h"
+#include "svn_delta.h"
+#include "svn_pools.h"
+#include "svn_fs.h"
+#include "svn_time.h"
+#include "svn_props.h"
+#include "svn_path.h"
+#include "svn_version.h"
+#include "svn_sorts.h"
+
+#include "svn_private_config.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "private/svn_atomic.h"
+#include "private/svn_fspath.h"
+
+#include "ra_git.h"
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#define RA_GIT_DEFAULT_REFSPEC      "+refs/heads/master:refs/remotes/origin/master"
+#define RA_GIT_DEFAULT_REMOTE_NAME  "origin"
+#define RA_GIT_DEFAULT_REF          "refs/remotes/origin/master"
+
+typedef struct svn_ra_git__session_baton_t
+{
+  /* The URL of the session. */
+  const char *session_url;
+
+  /* The user accessing the repository. */
+  const char *username;
+
+  /* Git repository data structures. */
+  git_repository *repos;
+  git_remote *remote;
+  git_revwalk *revwalk;
+
+  /* The URL of the remote. */
+  const char *remote_url;
+
+  /* The local abspath to the local git repository. */
+  const char *repos_abspath;
+
+  /* Wether we did 'git fetch' for this session already. */
+  svn_boolean_t fetch_done;
+
+  /* The relative path in the tree the session is rooted at. */
+  svn_stringbuf_t *fs_path;  /* URI-decoded, always without leading slash. */
+
+  /* The UUID associated with REPOS above (cached) */
+  const char *uuid;
+
+  /* Map revision numbers to git commit IDs. */
+  apr_hash_t *revmap;
+
+  /* Callbacks/baton passed to svn_ra_open. */
+  const svn_ra_callbacks2_t *callbacks;
+  void *callback_baton;
+
+  const char *useragent;
+
+  /* Scratch pool for routines that cannot otherwise get one. */
+  apr_pool_t *scratch_pool;
+
+} svn_ra_git__session_baton_t;
+
+/*----------------------------------------------------------------*/
+
+/*** Miscellaneous helper functions ***/
+
+svn_error_t *
+svn_ra_git__wrap_git_error(void)
+{
+  git_error git_err;
+
+  if (giterr_detach(&git_err) == -1)
+    SVN_ERR_MALFUNCTION();
+
+  /* ### TODO: map error code */
+  return svn_error_createf(SVN_ERR_BASE, NULL,
+                           _("git: %s"), git_err.message);
+}
+
+static const char *
+make_git_url(const char *session_url)
+{
+  if (strncmp(session_url, "git+", 4) == 0) /* git+file://, git+http://, git+https:// */
+    return session_url + 4;
+  else
+    /* git:// */
+    return session_url;
+}
+
+static const char *
+make_svn_url(const char *git_url, apr_pool_t *result_pool)
+{
+  if (strncmp(git_url, "git:", 4) == 0) /* git:// */
+    return git_url;
+  else /* git+file://, git+http://, git+https:// */
+    return apr_pstrcat(result_pool, "git+", git_url, SVN_VA_NULL);
+}
+
+static svn_error_t *
+split_url(const char **remote_url,
+          svn_stringbuf_t *fs_path,
+          git_repository *repos,
+          const char *session_url,
+          apr_pool_t *result_pool,
+          apr_pool_t *scratch_pool)
+{
+  svn_boolean_t found_remote = FALSE;
+  svn_stringbuf_t *remote_url_buf;
+  
+  remote_url_buf = svn_stringbuf_create(make_git_url(session_url), scratch_pool);
+  while (!found_remote)
+    {
+      git_remote *remote;
+      int git_err;
+
+      SVN_DBG(("trying remote url '%s'", remote_url_buf->data));
+
+      /* Create an in-memory remote... */
+      git_err = git_remote_create_inmemory(&remote, repos,
+                                           RA_GIT_DEFAULT_REFSPEC,
+                                           remote_url_buf->data);
+      if (git_err)
+        return svn_error_trace(svn_ra_git__wrap_git_error());
+
+      /* ... and try to connect to it. */
+      git_err = git_remote_connect(remote, GIT_DIRECTION_FETCH);
+      if (git_err)
+        {
+          apr_size_t slash_pos;
+          const char *component;
+
+          giterr_clear();
+
+          slash_pos = svn_stringbuf_find_char_backward(remote_url_buf, '/');
+          if (slash_pos >= remote_url_buf->len)
+            break;
+
+          if (!svn_stringbuf_isempty(fs_path))
+            component = apr_pstrcat(scratch_pool, remote_url_buf->data + slash_pos + 1,
+                                   "/", SVN_VA_NULL); 
+          else
+            component = apr_pstrcat(scratch_pool, remote_url_buf->data + slash_pos + 1,
+                                    SVN_VA_NULL); 
+          svn_stringbuf_insert(fs_path, 0, component, strlen(component));
+
+          svn_stringbuf_chop(remote_url_buf, remote_url_buf->len - slash_pos); 
+        }
+      else
+        found_remote = TRUE;
+
+      git_remote_free(remote);
+    }
+
+  if (found_remote)
+    *remote_url = apr_pstrdup(result_pool, remote_url_buf->data);
+  else
+    return svn_error_compose_create(
+             svn_ra_git__wrap_git_error(),
+             svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+                               _("No git repository found at URL '%s'"),
+                               session_url));
+
+  SVN_DBG(("found remote url '%s', fs_path: '%s'\n", *remote_url, fs_path->data));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+do_git_fetch(svn_ra_git__session_baton_t *sess)
+{
+  int git_err;
+
+  /* Do one fetch per session.
+   * ### mutex? atomic_init? */
+  if (sess->fetch_done)
+    return SVN_NO_ERROR;
+
+  if (!git_remote_connected(sess->remote))
+    {
+      git_err = git_remote_connect(sess->remote, GIT_DIRECTION_FETCH);
+      if (git_err)
+        return svn_error_trace(svn_ra_git__wrap_git_error());
+    }
+
+  SVN_DBG(("fetching from %s\n", git_remote_url(sess->remote)));
+
+  git_err = git_remote_fetch(sess->remote);
+  if (git_err)
+    return svn_error_trace(svn_ra_git__wrap_git_error());
+
+  sess->fetch_done = TRUE;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fill_revmap(git_revwalk *revwalk,
+            git_repository *repos,
+            apr_hash_t *revmap,
+            apr_pool_t *pool)
+{
+  svn_revnum_t rev;
+  int git_err;
+
+  /* If the revmap has already been filled, there is nothing to do. */
+  if (apr_hash_count(revmap) > 0)
+    return SVN_NO_ERROR;
+
+  git_revwalk_reset(revwalk);
+  git_revwalk_push_ref(revwalk, RA_GIT_DEFAULT_REF);
+  git_revwalk_simplify_first_parent(revwalk);
+  git_revwalk_sorting(revwalk, GIT_SORT_REVERSE);
+
+  SVN_DBG(("scanning git commits...\n"));
+  rev = 0;
+  do
+    {
+      git_oid oid;
+
+      git_err = git_revwalk_next(&oid, revwalk);
+      if (git_err)
+        {
+          if (git_err != GIT_ITEROVER)
+            return svn_error_trace(svn_ra_git__wrap_git_error());
+        }
+      else
+        {
+          git_commit *commit;
+          svn_revnum_t *revp;
+          git_oid *oidp;
+          char rev_str[GIT_OID_HEXSZ + 1];
+
+          git_err = git_commit_lookup(&commit, repos, &oid);
+          if (git_err)
+            return svn_error_trace(svn_ra_git__wrap_git_error());
+
+          revp = apr_palloc(apr_hash_pool_get(revmap), sizeof(*revp));
+          *revp = ++rev;
+          oidp = apr_palloc(apr_hash_pool_get(revmap), sizeof(*oidp));
+          git_oid_cpy(oidp, &oid);
+          apr_hash_set(revmap, revp, sizeof(*revp), oidp);
+
+          git_oid_tostr(rev_str, sizeof(rev_str), oidp);
+          SVN_DBG(("r%lu -> %s", rev, rev_str));
+
+          git_commit_free(commit);
+        }
+    }
+  while (git_err != GIT_ITEROVER);
+
+  SVN_DBG(("done scanning git commits (%lu revisions)\n", rev));
+
+  return SVN_NO_ERROR;
+}
+
+/* Return the git tree, and the git commit pointing to it, corresponding
+ * to Subverswion revision REVISION. If REVISION is SVN_INVALID_REVNUM
+ * fetch the HEAD revision and store its revision number in *FETCHED_REV
+ * if FETCHED_REV is not NULL.
+ *
+ * PATH is relative to the session url of SESS. Return the corresponding
+ * repository-root-relative path in *REPOS_ROOT_RELPATH if REPOS_ROOT_RELPATH
+ * is not NULL.
+ *
+ * Do all allocations in POOL. */
+static svn_error_t *
+fetch_revision_root(git_tree **tree,
+                    git_commit **commit,
+                    const char **repos_root_relpath,
+                    svn_revnum_t *fetched_rev,
+                    svn_ra_git__session_baton_t *sess,
+                    const char *path,
+                    svn_revnum_t revision,
+                    apr_pool_t *pool)
+{
+  git_oid *oid;
+  int git_err;
+
+  do_git_fetch(sess);
+  fill_revmap(sess->revwalk, sess->repos, sess->revmap, pool);
+
+  if (!SVN_IS_VALID_REVNUM(revision))
+    revision = apr_hash_count(sess->revmap);
+
+  oid = apr_hash_get(sess->revmap, &revision, sizeof(revision));
+  if (oid == NULL)
+    return svn_error_create(SVN_ERR_FS_NO_SUCH_REVISION, NULL, NULL);
+
+  git_err = git_commit_lookup(commit, sess->repos, oid);
+  if (git_err)
+    return svn_error_trace(svn_ra_git__wrap_git_error());
+
+  if (tree)
+    {
+      git_err = git_commit_tree(tree, *commit);
+      if (git_err)
+        {
+          git_commit_free(*commit);
+          return svn_error_trace(svn_ra_git__wrap_git_error());
+        }
+    }
+
+  /* Handle reparented sessions and sessions not rooted at the git repos root. */
+  if (repos_root_relpath)
+    {
+      if (!svn_stringbuf_isempty(sess->fs_path))
+        *repos_root_relpath = svn_relpath_join(sess->fs_path->data, path, pool);
+      else
+        *repos_root_relpath = path;
+     }
+
+  if (fetched_rev)
+    *fetched_rev = revision;
+
+  return SVN_NO_ERROR;
+}
+
+/* Fetch a username for use with SESSION, and store it in SESSION->username.
+ *
+ * Allocate the username in SESSION->pool.  Use SCRATCH_POOL for temporary
+ * allocations. */
+static svn_error_t *
+get_username(svn_ra_session_t *session,
+             apr_pool_t *scratch_pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+
+  /* If we've already found the username don't ask for it again. */
+  if (! sess->username)
+    {
+      /* Get a username somehow, so we have some svn:author property to
+         attach to a commit. */
+      if (sess->callbacks->auth_baton)
+        {
+          void *creds;
+          svn_auth_cred_username_t *username_creds;
+          svn_auth_iterstate_t *iterstate;
+
+          SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
+                                             SVN_AUTH_CRED_USERNAME,
+                                             sess->uuid, /* realmstring */
+                                             sess->callbacks->auth_baton,
+                                             scratch_pool));
+
+          /* No point in calling next_creds(), since that assumes that the
+             first_creds() somehow failed to authenticate.  But there's no
+             challenge going on, so we use whatever creds we get back on
+             the first try. */
+          username_creds = creds;
+          if (username_creds && username_creds->username)
+            {
+              sess->username = apr_pstrdup(session->pool,
+                                           username_creds->username);
+              svn_error_clear(svn_auth_save_credentials(iterstate,
+                                                        scratch_pool));
+            }
+          else
+            sess->username = "";
+        }
+      else
+        sess->username = "";
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------*/
+
+/*** The reporter vtable needed by do_update() and friends ***/
+
+typedef struct reporter_baton_t
+{
+  svn_ra_git__session_baton_t *sess;
+  void *report_baton;
+
+} reporter_baton_t;
+
+static svn_error_t *
+reporter_set_path(void *reporter_baton,
+                  const char *path,
+                  svn_revnum_t revision,
+                  svn_depth_t depth,
+                  svn_boolean_t start_empty,
+                  const char *lock_token,
+                  apr_pool_t *pool)
+{
+  reporter_baton_t *rbaton = reporter_baton;
+  return svn_error_trace(svn_ra_git__reporter_set_path(rbaton->report_baton,
+                                                       path, revision, depth,
+                                                       start_empty, lock_token,
+                                                       pool));
+}
+
+static svn_error_t *
+reporter_delete_path(void *reporter_baton,
+                     const char *path,
+                     apr_pool_t *pool)
+{
+  reporter_baton_t *rbaton = reporter_baton;
+  return svn_error_trace(svn_ra_git__reporter_delete_path(rbaton->report_baton,
+                                                          path, pool));
+}
+
+
+static svn_error_t *
+reporter_link_path(void *reporter_baton,
+                   const char *path,
+                   const char *url,
+                   svn_revnum_t revision,
+                   svn_depth_t depth,
+                   svn_boolean_t start_empty,
+                   const char *lock_token,
+                   apr_pool_t *pool)
+{
+  reporter_baton_t *rb = reporter_baton;
+  const char *linked_path;
+
+  linked_path = svn_uri_skip_ancestor(rb->sess->remote_url,
+                                      make_git_url(url), pool);
+  if (!linked_path)
+    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+                             _("'%s'\n"
+                               "is not the same repository as\n"
+                               "'%s'"), url, rb->sess->session_url);
+
+  if (!svn_stringbuf_isempty(rb->sess->fs_path))
+    {
+      path = svn_relpath_join(rb->sess->fs_path->data, path, pool);
+      linked_path = svn_relpath_join(rb->sess->fs_path->data,
+                                        linked_path, pool);
+    }
+
+  return svn_error_trace(svn_ra_git__reporter_link_path(rb->report_baton,
+                                                        path, linked_path,
+                                                        revision,
+                                                        depth, start_empty,
+                                                        lock_token, pool));
+}
+
+static svn_error_t *
+reporter_finish_report(void *reporter_baton,
+                       apr_pool_t *pool)
+{
+  reporter_baton_t *rbaton = reporter_baton;
+  return svn_error_trace(svn_ra_git__reporter_finish_report(
+                           rbaton->report_baton, pool));
+}
+
+static svn_error_t *
+reporter_abort_report(void *reporter_baton,
+                      apr_pool_t *pool)
+{
+  reporter_baton_t *rbaton = reporter_baton;
+  return svn_error_trace(svn_ra_git__reporter_abort_report(
+                           rbaton->report_baton, pool));
+}
+
+
+static const svn_ra_reporter3_t ra_git_reporter =
+{
+  reporter_set_path,
+  reporter_delete_path,
+  reporter_link_path,
+  reporter_finish_report,
+  reporter_abort_report
+};
+
+
+/* ...
+ *
+ * Allocate @a *reporter and @a *report_baton in @a result_pool.  Use
+ * @a scratch_pool for temporary allocations.
+ */
+static svn_error_t *
+make_reporter(svn_ra_session_t *session,
+              const svn_ra_reporter3_t **reporter,
+              void **report_baton,
+              svn_revnum_t revision,
+              const char *target,
+              const char *other_url,
+              svn_boolean_t text_deltas,
+              svn_depth_t depth,
+              svn_boolean_t send_copyfrom_args,
+              svn_boolean_t ignore_ancestry,
+              const svn_delta_editor_t *editor,
+              void *edit_baton,
+              apr_pool_t *result_pool,
+              apr_pool_t *scratch_pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  struct reporter_baton_t *rb;
+  const char *other_fs_path = NULL;
+  void *wrapped_rb;
+
+  /* Get the HEAD revision if one is not supplied. */
+  if (! SVN_IS_VALID_REVNUM(revision))
+    revision = apr_hash_count(sess->revmap);
+
+  /* If OTHER_URL was provided, validate it and convert it into a
+     regular filesystem path. */
+  if (other_url)
+    {
+      const char *other_relpath
+        = svn_uri_skip_ancestor(sess->remote_url, make_git_url(other_url),
+                                scratch_pool);
+
+      /* Sanity check:  the other_url better be in the same repository as
+         the original session url! */
+      if (! other_relpath)
+        return svn_error_createf
+          (SVN_ERR_RA_ILLEGAL_URL, NULL,
+           _("'%s'\n"
+             "is not the same repository as\n"
+             "'%s'"), other_url, sess->session_url);
+
+      other_fs_path = other_relpath;
+    }
+
+  if (sess->callbacks)
+    SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
+                                              sess->callback_baton,
+                                              editor,
+                                              edit_baton,
+                                              &editor,
+                                              &edit_baton,
+                                              result_pool));
+
+  /* Build a reporter baton. */
+  SVN_ERR(svn_ra_git__reporter_begin_report(&wrapped_rb,
+                                            revision,
+                                            sess->repos,
+                                            sess->revmap,
+                                            sess->fs_path->data,
+                                            target,
+                                            other_fs_path,
+                                            text_deltas,
+                                            depth,
+                                            ignore_ancestry,
+                                            send_copyfrom_args,
+                                            editor,
+                                            edit_baton,
+                                            1024 * 1024,
+                                            result_pool));
+
+  /* Pass back our reporter */
+  *reporter = &ra_git_reporter;
+  rb = apr_palloc(result_pool, sizeof(*rb));
+  rb->sess = sess;
+  rb->report_baton = wrapped_rb;
+  *report_baton = rb;
+
+  return SVN_NO_ERROR;
+}
+
+static apr_status_t
+cleanup_temporary_repos(void *data)
+{
+  svn_ra_session_t *session = data;
+  svn_ra_git__session_baton_t *sess = session->priv;
+  svn_error_t *err;
+
+  err = svn_io_remove_dir2(sess->repos_abspath, TRUE, NULL, NULL, session->pool);
+  if (err)
+    {
+      apr_status_t apr_err = err->apr_err;
+      svn_error_clear(err);
+      return apr_err;
+    }
+
+  return APR_SUCCESS;
+}
+
+
+static void
+check_cancel_stop_remote(svn_ra_git__session_baton_t *sess)
+{
+  svn_error_t *err;
+
+  if (sess->callbacks->cancel_func == NULL)
+    return;
+
+  err = (sess->callbacks->cancel_func)(sess->callback_baton);
+  if (err)
+    {
+      if (err->apr_err == SVN_ERR_CANCELLED)
+        git_remote_stop(sess->remote);
+      svn_error_clear(err);
+    }
+}
+
+static int remote_progress_cb(const char *str, int len, void *data)
+{
+  svn_ra_git__session_baton_t *sess = data;
+  svn_string_t *s;
+
+  if (len)
+    {
+      svn_pool_clear(sess->scratch_pool);
+      s = svn_string_ncreate(str, len, sess->scratch_pool);
+      SVN_DBG(("%s\n", s->data));
+    }
+
+  check_cancel_stop_remote(sess);
+  return 0;
+}
+
+static int remote_transfer_progress_cb(const git_transfer_progress *stats,
+                                       void *data)
+{
+  svn_ra_git__session_baton_t *sess = data;
+
+  SVN_DBG(("objects: %u total %u indexed %u received %u local, "
+           "deltas: %u total %u indexed, %ld bytes received\n", 
+           stats->total_objects,
+           stats->indexed_objects,
+           stats->received_objects,
+           stats->local_objects,
+           stats->total_deltas,
+           stats->indexed_deltas,
+           (long)stats->received_bytes));
+
+  check_cancel_stop_remote(sess);
+  return 0;
+}
+
+static int remote_update_tips_cb(const char *refname,
+                                 const git_oid *a,
+                                 const git_oid *b,
+                                 void *data)
+{
+  svn_ra_git__session_baton_t *sess = data;
+
+  SVN_DBG(("update %s\n", refname));
+
+  check_cancel_stop_remote(sess);
+  return 0;
+}
+
+static svn_error_t *
+do_libgit_init(void *baton, apr_pool_t *pool)
+{
+  git_threads_init();
+  return SVN_NO_ERROR;
+}
+
+/* Return the last-changed revision of the repos-root-relative
+ * PATH@PEGREV in *LAST_CHANGED. */
+svn_error_t *
+svn_ra_git__find_last_changed(svn_revnum_t *last_changed,
+                              apr_hash_t *revmap,
+                              const char *path,
+                              svn_revnum_t pegrev,
+                              git_repository *repos,
+                              apr_pool_t *pool)
+{
+  int git_err;
+  const git_oid *oid;
+  git_oid last_oid;
+  git_commit *commit;
+  git_tree *tree;
+  git_tree_entry *entry;
+  svn_revnum_t rev;
+
+  oid = apr_hash_get(revmap, &pegrev, sizeof(pegrev));
+  if (oid == NULL)
+    return svn_error_create(SVN_ERR_FS_NO_SUCH_REVISION, NULL, NULL);
+
+  /* PATH has already been made relative to repos root by caller. */
+  if (path[0] == '\0')
+    {
+      /* The root directory of the repository was last changed in HEAD. */
+      *last_changed = apr_hash_count(revmap);
+      return SVN_NO_ERROR;
+    }
+
+  git_err = git_commit_lookup(&commit, repos, oid);
+  if (git_err)
+    return svn_error_trace(svn_ra_git__wrap_git_error());
+  git_err = git_commit_tree(&tree, commit);
+  if (git_err)
+    {
+      git_commit_free(commit);
+      return svn_error_trace(svn_ra_git__wrap_git_error());
+    }
+
+  git_err = git_tree_entry_bypath(&entry, tree, path);
+  if (git_err)
+    {
+      git_tree_free(tree);
+      git_commit_free(commit);
+
+      if (git_err == GIT_ENOTFOUND)
+        return svn_error_createf(SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+                                 _("No entry for %s@%lu\n"), path, pegrev);
+
+      return svn_error_trace(svn_ra_git__wrap_git_error());
+    }
+
+  git_oid_cpy(&last_oid, git_tree_entry_id(entry));
+  rev = apr_hash_count(revmap);
+
+  git_tree_free(tree);
+  git_commit_free(commit);
+
+  while (rev >= 2)
+    {
+      oid = apr_hash_get(revmap, &rev, sizeof(rev));
+      if (oid == NULL)
+        return svn_error_create(SVN_ERR_FS_NO_SUCH_REVISION, NULL, NULL);
+      git_err = git_commit_lookup(&commit, repos, oid);
+      if (git_err)
+        return svn_error_trace(svn_ra_git__wrap_git_error());
+      git_err = git_commit_tree(&tree, commit);
+      if (git_err)
+        {
+          git_commit_free(commit);
+          return svn_error_trace(svn_ra_git__wrap_git_error());
+        }
+      git_err = git_tree_entry_bypath(&entry, tree, path);
+      if (git_err)
+        {
+          git_tree_free(tree);
+          git_commit_free(commit);
+
+          if (git_err == GIT_ENOTFOUND)
+            {
+              *last_changed = rev;
+              return SVN_NO_ERROR;
+            }
+
+          return svn_error_trace(svn_ra_git__wrap_git_error());
+        }
+
+      git_tree_free(tree);
+      git_commit_free(commit);
+
+      oid = git_tree_entry_id(entry);
+      if (git_oid_cmp(oid, &last_oid) != 0)
+        {
+          git_tree_entry_free(entry);
+          break;
+        }
+
+      git_oid_cpy(&last_oid, git_tree_entry_id(entry));
+      git_tree_entry_free(entry);
+      rev--;
+    }
+
+  *last_changed = rev;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+map_obj_to_dirent(svn_dirent_t **out,
+                  apr_hash_t *revmap, const char *path, svn_revnum_t pegrev,
+                  apr_uint32_t dirent_fields,
+                  git_repository *repos, git_commit *commit, git_object *obj,
+                  apr_pool_t *pool)
+{
+  svn_dirent_t *dirent = svn_dirent_create(pool);
+  git_otype type = git_object_type(obj);
+  svn_revnum_t last_changed_rev = SVN_INVALID_REVNUM;
+  git_commit *last_changed_commit = NULL;
+
+  if (dirent_fields & (SVN_DIRENT_CREATED_REV | SVN_DIRENT_TIME | SVN_DIRENT_LAST_AUTHOR))
+    {
+      SVN_ERR(svn_ra_git__find_last_changed(&last_changed_rev, revmap, path,
+                                            pegrev, repos, pool));
+
+      if (dirent_fields & (SVN_DIRENT_TIME | SVN_DIRENT_LAST_AUTHOR))
+        {
+          git_oid *oid;
+          int git_err;
+
+          oid = apr_hash_get(revmap, &last_changed_rev, sizeof(last_changed_rev));
+          if (oid == NULL)
+            return svn_error_create(SVN_ERR_FS_NO_SUCH_REVISION, NULL, NULL);
+
+          git_err = git_commit_lookup(&last_changed_commit, repos, oid);
+          if (git_err)
+            return svn_error_trace(svn_ra_git__wrap_git_error());
+       } 
+    }
+
+  if (dirent_fields & SVN_DIRENT_KIND)
+    {
+      if (type == GIT_OBJ_TREE)
+        dirent->kind = svn_node_dir;
+      else if (type == GIT_OBJ_BLOB)
+        dirent->kind = svn_node_file;
+      else
+        dirent->kind = svn_node_none;
+    }
+
+  if (dirent_fields & SVN_DIRENT_SIZE)
+    {
+      if (type == GIT_OBJ_BLOB)
+        dirent->size = git_blob_rawsize((git_blob *)obj);
+      else
+        dirent->size = 0;
+    }
+
+  if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+    dirent->has_props = FALSE; /* ### TODO map svn: properties */
+
+  if (dirent_fields & SVN_DIRENT_CREATED_REV)
+    dirent->created_rev = last_changed_rev;
+
+  if (dirent_fields & SVN_DIRENT_TIME)
+    dirent->time = git_commit_time(last_changed_commit) * 1000000;
+
+  if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+    dirent->last_author = apr_pstrdup(pool, git_commit_author(last_changed_commit)->email);
+
+  *out = dirent;
+  return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------*/
+
+/*** The RA vtable routines ***/
+
+#define RA_GIT_DESCRIPTION \
+        N_("Module for accessing a git repository.")
+
+static const char *
+svn_ra_git__get_description(apr_pool_t *pool)
+{
+  return _(RA_GIT_DESCRIPTION);
+}
+
+static const char * const *
+svn_ra_git__get_schemes(apr_pool_t *pool)
+{
+  /* TODO: git+ssh requires optional libssh dependency -- do we want that as well? */
+  static const char *schemes[] = { "git", "git+file", "git+http", "git+https", NULL };
+
+  return schemes;
+}
+
+#define USER_AGENT "SVN/" SVN_VER_NUMBER " (" SVN_BUILD_TARGET ")" \
+                   " ra_git"
+
+static svn_error_t *
+svn_ra_git__open(svn_ra_session_t *session,
+                 const char **corrected_url,
+                 const char *repos_url,
+                 const svn_ra_callbacks2_t *callbacks,
+                 void *callback_baton,
+                 apr_hash_t *config,
+                 apr_pool_t *pool)
+{
+  const char *client_string;
+  svn_ra_git__session_baton_t *sess;
+  static volatile svn_atomic_t libgit_initialized = 0;
+  int git_err;
+  git_remote_callbacks *remote_callbacks;
+
+
+  /* We don't support redirections in ra-git. */
+  if (corrected_url)
+    *corrected_url = NULL;
+
+  /* Allocate and stash the session_sess args we have already. */
+  sess = apr_pcalloc(session->pool, sizeof(*sess));
+  sess->callbacks = callbacks;
+  sess->callback_baton = callback_baton;
+
+  /* Root the session at the root directory. */
+  sess->fs_path = svn_stringbuf_create_empty(session->pool);
+
+  /* Fake up the repository UUID. */
+   sess->uuid = RA_GIT_UUID;
+
+  /* Be sure username is NULL so we know to look it up / ask for it */
+  sess->username = NULL;
+
+  if (sess->callbacks->get_client_string != NULL)
+    SVN_ERR(sess->callbacks->get_client_string(sess->callback_baton,
+                                               &client_string, session->pool));
+  else
+    client_string = NULL;
+
+  if (client_string)
+    sess->useragent = apr_pstrcat(session->pool, USER_AGENT " ",
+                                  client_string, SVN_VA_NULL);
+  else
+    sess->useragent = USER_AGENT;
+
+  sess->revmap = apr_hash_make(session->pool);
+  sess->fetch_done = FALSE;
+  sess->scratch_pool = svn_pool_create(session->pool);
+
+  sess->session_url = apr_pstrdup(pool, repos_url);
+  session->priv = sess;
+
+  /* Store the git repository within the working copy's admin area,
+   * if availble. Otherwise, create a temporary repository. */
+  if (sess->callbacks->get_wc_adm_subdir != NULL)
+    {
+      SVN_ERR(sess->callbacks->get_wc_adm_subdir(sess->callback_baton,
+                                                 &sess->repos_abspath,
+                                                 "git",
+                                                 pool, pool));
+    }
+  else
+    {
+      /* Use a temporary git repository. */
+      /* ### small race here, should be using mkdtemp() or similar */
+      SVN_ERR(svn_io_open_unique_file3(NULL, &sess->repos_abspath, NULL,
+                                       svn_io_file_del_none,
+                                       session->pool, pool));
+      SVN_ERR(svn_io_remove_file2(sess->repos_abspath, TRUE, pool));
+
+      /* Git repository is removed when the session pool gets destroyed. */
+      apr_pool_cleanup_register(session->pool, session, cleanup_temporary_repos,
+                                apr_pool_cleanup_null);
+    }
+
+  SVN_ERR(svn_atomic__init_once(&libgit_initialized, do_libgit_init, NULL,
+                                NULL));
+
+  SVN_DBG(("creating git repos in '%s'\n", sess->repos_abspath));
+
+  /* Init (or reinit) a bare git repository. */
+  git_err = git_repository_init(&sess->repos, sess->repos_abspath,
+                                TRUE /* is_bare */);
+  if (git_err)
+    return svn_error_trace(svn_ra_git__wrap_git_error());
+
+  /* Split the session URL into a git remote URL and, possibly, a path within
+   * the repository (in sess->fs_path). */
+  svn_pool_clear(sess->scratch_pool);
+  SVN_ERR(split_url(&sess->remote_url, sess->fs_path, sess->repos,
+                    sess->session_url, session->pool, sess->scratch_pool));
+
+  /* Check if our remote already exists. */
+  git_err = git_remote_load(&sess->remote, sess->repos,
+                            RA_GIT_DEFAULT_REMOTE_NAME);
+  if (git_err)
+    {
+      if (git_err == GIT_ENOTFOUND)
+        {
+          giterr_clear();
+          sess->remote = NULL;
+        }
+      else
+        return svn_error_trace(svn_ra_git__wrap_git_error());
+    }
+
+  if (sess->remote == NULL)
+    {
+      git_err = git_remote_create_with_fetchspec(
+                  &sess->remote, sess->repos, RA_GIT_DEFAULT_REMOTE_NAME,
+                  sess->remote_url, RA_GIT_DEFAULT_REFSPEC);
+      if (git_err)
+        return svn_error_trace(svn_ra_git__wrap_git_error());
+    }
+
+  remote_callbacks = apr_pcalloc(session->pool, sizeof(*remote_callbacks));
+  remote_callbacks->version = GIT_REMOTE_CALLBACKS_VERSION;
+  remote_callbacks->progress = remote_progress_cb;
+  remote_callbacks->transfer_progress = remote_transfer_progress_cb;
+  remote_callbacks->update_tips = remote_update_tips_cb;
+  remote_callbacks->payload = sess;
+  git_remote_set_callbacks(sess->remote, remote_callbacks);
+
+  git_err = git_revwalk_new(&sess->revwalk, sess->repos);
+  if (git_err)
+    return svn_error_trace(svn_ra_git__wrap_git_error());
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__dup_session(svn_ra_session_t *new_session,
+                        svn_ra_session_t *session,
+                        const char *new_session_url,
+                        apr_pool_t *result_pool,
+                        apr_pool_t *scratch_pool)
+{
+  svn_ra_git__session_baton_t *old_sess = session->priv;
+  svn_ra_git__session_baton_t *new_sess;
+
+  /* Allocate and stash the session_sess args we have already. */
+  new_sess = apr_pcalloc(result_pool, sizeof(*new_sess));
+  new_sess->callbacks = old_sess->callbacks;
+  new_sess->callback_baton = old_sess->callback_baton;
+
+  /* ### Make a deep copy of these? */
+  new_sess->repos = old_sess->repos;
+  new_sess->remote = old_sess->remote;
+  new_sess->revwalk = old_sess->revwalk;
+  new_sess->revmap = old_sess->revmap;
+
+  new_sess->fetch_done = old_sess->fetch_done;
+  new_sess->session_url = apr_pstrdup(result_pool, old_sess->session_url);
+  new_sess->remote_url = apr_pstrdup(result_pool, old_sess->remote_url);
+  new_sess->fs_path = svn_stringbuf_dup(old_sess->fs_path, result_pool);
+
+  /* Cache the repository UUID as well */
+  new_sess->uuid = apr_pstrdup(result_pool, old_sess->uuid);
+
+  new_sess->username = old_sess->username
+                            ? apr_pstrdup(result_pool, old_sess->username)
+                            : NULL;
+
+  new_sess->useragent = apr_pstrdup(result_pool, old_sess->useragent);
+  new_session->priv = new_sess;
+
+  new_sess->scratch_pool = old_sess->scratch_pool;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__reparent(svn_ra_session_t *session,
+                     const char *url,
+                     apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  const char *relpath = svn_uri_skip_ancestor(sess->remote_url,
+                                              make_git_url(url), pool);
+
+  /* If the new URL isn't the same as our repository root URL, then
+     let's ensure that it's some child of it. */
+  if (! relpath)
+    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+       _("URL '%s' is not a child of the session's repository root "
+         "URL '%s'"), url, sess->session_url);
+
+  if (strcmp(sess->session_url, url) != 0)
+    {
+      svn_stringbuf_set(sess->fs_path, svn_relpath_canonicalize(relpath, pool));
+      sess->session_url = apr_pstrdup(pool, url);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__get_session_url(svn_ra_session_t *session,
+                            const char **url,
+                            apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  *url = apr_pstrdup(pool, sess->session_url);
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__get_latest_revnum(svn_ra_session_t *session,
+                              svn_revnum_t *latest_revnum,
+                              apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+
+  do_git_fetch(sess);
+  fill_revmap(sess->revwalk, sess->repos, sess->revmap, pool);
+  *latest_revnum = apr_hash_count(sess->revmap);
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__get_file_revs(svn_ra_session_t *session,
+                          const char *path,
+                          svn_revnum_t start,
+                          svn_revnum_t end,
+                          svn_boolean_t include_merged_revisions,
+                          svn_file_rev_handler_t handler,
+                          void *handler_baton,
+                          apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+static svn_error_t *
+svn_ra_git__get_dated_revision(svn_ra_session_t *session,
+                               svn_revnum_t *revision,
+                               apr_time_t tm,
+                               apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__change_rev_prop(svn_ra_session_t *session,
+                            svn_revnum_t rev,
+                            const char *name,
+                            const svn_string_t *const *old_value_p,
+                            const svn_string_t *value,
+                            apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+static svn_error_t *
+svn_ra_git__get_uuid(svn_ra_session_t *session,
+                     const char **uuid,
+                     apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  *uuid = sess->uuid;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__get_repos_root(svn_ra_session_t *session,
+                           const char **url,
+                           apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  *url = svn_uri_get_longest_ancestor(make_svn_url(sess->remote_url, pool),
+                                      sess->session_url, pool);
+  return SVN_NO_ERROR;
+}
+
+apr_hash_t *
+svn_ra_git__make_revprops_hash(git_commit *commit, apr_pool_t *pool)
+{
+  apr_hash_t *props = apr_hash_make(pool);
+  svn_hash_sets(props, SVN_PROP_REVISION_LOG,
+                svn_string_create(git_commit_message(commit), pool));
+  svn_hash_sets(props, SVN_PROP_REVISION_AUTHOR,
+                svn_string_create(git_commit_author(commit)->email, pool));
+  svn_hash_sets(props, SVN_PROP_REVISION_DATE,
+                svn_string_create(
+                  svn_time_to_cstring(git_commit_time(commit) * 1000000, pool),
+                  pool));
+  return props;
+}
+
+static svn_error_t *
+svn_ra_git__rev_proplist(svn_ra_session_t *session,
+                         svn_revnum_t rev,
+                         apr_hash_t **props,
+                         apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  int git_err;
+  git_oid *oid;
+  git_commit *commit;
+
+  do_git_fetch(sess);
+  fill_revmap(sess->revwalk, sess->repos, sess->revmap, pool);
+
+  oid = apr_hash_get(sess->revmap, &rev, sizeof(rev));
+  if (oid == NULL)
+    return svn_error_create(SVN_ERR_FS_NO_SUCH_REVISION, NULL, NULL);
+
+  git_err = git_commit_lookup(&commit, sess->repos, oid);
+  if (git_err)
+    return svn_error_trace(svn_ra_git__wrap_git_error());
+
+  *props = svn_ra_git__make_revprops_hash(commit, pool);
+  git_commit_free(commit);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__rev_prop(svn_ra_session_t *session,
+                     svn_revnum_t rev,
+                     const char *name,
+                     svn_string_t **value,
+                     apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+static svn_error_t *
+svn_ra_git__get_commit_editor(svn_ra_session_t *session,
+                              const svn_delta_editor_t **editor,
+                              void **edit_baton,
+                              apr_hash_t *revprop_table,
+                              svn_commit_callback2_t callback,
+                              void *callback_baton,
+                              apr_hash_t *lock_tokens,
+                              svn_boolean_t keep_locks,
+                              apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__get_mergeinfo(svn_ra_session_t *session,
+                          svn_mergeinfo_catalog_t *catalog,
+                          const apr_array_header_t *paths,
+                          svn_revnum_t revision,
+                          svn_mergeinfo_inheritance_t inherit,
+                          svn_boolean_t include_descendants,
+                          apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__do_update(svn_ra_session_t *session,
+                        const svn_ra_reporter3_t **reporter,
+                        void **report_baton,
+                        svn_revnum_t update_revision,
+                        const char *update_target,
+                        svn_depth_t depth,
+                        svn_boolean_t send_copyfrom_args,
+                        svn_boolean_t ignore_ancestry,
+                        const svn_delta_editor_t *update_editor,
+                        void *update_baton,
+                        apr_pool_t *result_pool,
+                        apr_pool_t *scratch_pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+
+  do_git_fetch(sess);
+  fill_revmap(sess->revwalk, sess->repos, sess->revmap, scratch_pool);
+  return make_reporter(session,
+                       reporter,
+                       report_baton,
+                       update_revision,
+                       update_target,
+                       NULL,
+                       TRUE,
+                       depth,
+                       send_copyfrom_args,
+                       ignore_ancestry,
+                       update_editor,
+                       update_baton,
+                       result_pool, scratch_pool);
+}
+
+
+static svn_error_t *
+svn_ra_git__do_switch(svn_ra_session_t *session,
+                        const svn_ra_reporter3_t **reporter,
+                        void **report_baton,
+                        svn_revnum_t update_revision,
+                        const char *update_target,
+                        svn_depth_t depth,
+                        const char *switch_url,
+                        svn_boolean_t send_copyfrom_args,
+                        svn_boolean_t ignore_ancestry,
+                        const svn_delta_editor_t *update_editor,
+                        void *update_baton,
+                        apr_pool_t *result_pool,
+                        apr_pool_t *scratch_pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+
+  do_git_fetch(sess);
+  fill_revmap(sess->revwalk, sess->repos, sess->revmap, scratch_pool);
+  return make_reporter(session,
+                       reporter,
+                       report_baton,
+                       update_revision,
+                       update_target,
+                       switch_url,
+                       TRUE /* text_deltas */,
+                       depth,
+                       send_copyfrom_args,
+                       ignore_ancestry,
+                       update_editor,
+                       update_baton,
+                       result_pool, scratch_pool);
+}
+
+
+static svn_error_t *
+svn_ra_git__do_status(svn_ra_session_t *session,
+                        const svn_ra_reporter3_t **reporter,
+                        void **report_baton,
+                        const char *status_target,
+                        svn_revnum_t revision,
+                        svn_depth_t depth,
+                        const svn_delta_editor_t *status_editor,
+                        void *status_baton,
+                        apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+
+  do_git_fetch(sess);
+  fill_revmap(sess->revwalk, sess->repos, sess->revmap, pool);
+  return make_reporter(session,
+                       reporter,
+                       report_baton,
+                       revision,
+                       status_target,
+                       NULL,
+                       FALSE,
+                       depth,
+                       FALSE,
+                       FALSE,
+                       status_editor,
+                       status_baton,
+                       pool, pool);
+}
+
+
+static svn_error_t *
+svn_ra_git__do_diff(svn_ra_session_t *session,
+                    const svn_ra_reporter3_t **reporter,
+                    void **report_baton,
+                    svn_revnum_t update_revision,
+                    const char *update_target,
+                    svn_depth_t depth,
+                    svn_boolean_t ignore_ancestry,
+                    svn_boolean_t text_deltas,
+                    const char *switch_url,
+                    const svn_delta_editor_t *update_editor,
+                    void *update_baton,
+                    apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+
+  do_git_fetch(sess);
+  fill_revmap(sess->revwalk, sess->repos, sess->revmap, pool);
+  return make_reporter(session,
+                       reporter,
+                       report_baton,
+                       update_revision,
+                       update_target,
+                       switch_url,
+                       text_deltas,
+                       depth,
+                       FALSE,
+                       ignore_ancestry,
+                       update_editor,
+                       update_baton,
+                       pool, pool);
+}
+
+
+struct walk_added_tree_baton {
+  apr_hash_t *changed_paths;
+  const char *root_relpath;
+  apr_pool_t *pool;
+} walk_added_tree_baton;
+
+/* Implements git_treewalk_cb */
+static int
+walk_added_tree_cb(const char *root,
+                   const git_tree_entry *entry,
+                   void *payload)
+{
+  struct walk_added_tree_baton *b = payload;
+  svn_log_changed_path2_t *changed_path;
+  const char *entry_relpath;
+
+  changed_path = svn_log_changed_path2_create(b->pool);
+  changed_path->action = 'A';
+  root = svn_relpath_canonicalize(root, b->pool);
+  entry_relpath = svn_relpath_join(b->root_relpath,
+                                   svn_relpath_canonicalize(root, b->pool),
+                                   b->pool);
+  entry_relpath = svn_relpath_join(entry_relpath, git_tree_entry_name(entry),
+                                   b->pool);
+  svn_hash_sets(b->changed_paths, entry_relpath, changed_path);
+
+  return 0;
+}
+
+static svn_error_t *
+walk_added_tree(apr_hash_t *changed_paths,
+                const char *root_relpath,
+                git_tree *tree,
+                apr_pool_t *pool)
+{
+  int git_err;
+  struct walk_added_tree_baton b;
+
+  b.changed_paths = changed_paths;
+  b.root_relpath = root_relpath;
+  b.pool = pool;
+
+  /* Walk tree entries to compare children. */
+  git_err = git_tree_walk(tree, GIT_TREEWALK_PRE,
+                          walk_added_tree_cb, &b);
+  if (git_err)
+    return svn_error_trace(svn_ra_git__wrap_git_error());
+
+  return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+compare_git_tree_entries(apr_hash_t *changed_paths,
+                         git_repository *repos,
+                         git_tree *tree,
+                         git_tree *other_tree,
+                         const char *tree_relpath,
+                         apr_pool_t *pool)
+{
+  svn_log_changed_path2_t *changed_path;
+  apr_hash_t *other_entries;
+  int git_err;
+  int i;
+
+  /* Get the other tree's entries so we can compare entries of
+   * both tree objects. */
+  other_entries = apr_hash_make(pool);
+  for (i = 0; i < git_tree_entrycount(other_tree); i++)
+    {
+      const git_tree_entry *e;
+
+      /* Remember the entry's name and its oid. */
+      e = git_tree_entry_byindex(other_tree, i);
+      svn_hash_sets(other_entries, git_tree_entry_name(e),
+                    git_tree_entry_id(e));
+    }
+
+  /* Compare the trees' entries, pruning the other entries list
+   * of entries which exist in both trees or don't exist in the
+   * other tree. */
+  for (i = 0; i < git_tree_entrycount(tree); i++)
+    {
+      const git_tree_entry *e;
+      const git_oid *oid;
+      const git_oid *other_oid;
+
+      e = git_tree_entry_byindex(tree, i);
+      oid = git_tree_entry_id(e);
+      other_oid = svn_hash_gets(other_entries, git_tree_entry_name(e));
+
+      if (other_oid == NULL)
+        {
+          /* This entry was deleted in the other tree.
+           * Mark it as deleted. */
+          changed_path = svn_log_changed_path2_create(pool);
+          changed_path->action = 'D';
+          if (git_tree_entry_type(e) == GIT_OBJ_BLOB)
+            changed_path->node_kind = svn_node_file;
+          else if (git_tree_entry_type(e) == GIT_OBJ_TREE)
+            changed_path->node_kind = svn_node_dir;
+          else
+            changed_path->node_kind = svn_node_unknown;
+          svn_hash_sets(changed_paths,
+                        svn_relpath_join(svn_relpath_canonicalize(
+                                           tree_relpath, pool),
+                                         git_tree_entry_name(e), pool),
+                          changed_path);
+        }
+      else if (!git_oid_equal(oid, other_oid))
+        {
+          /* The entries differ.
+           * If it's a blob, mark it as modified if the other entry is
+           * also a blob, or mark it as replaced if the other entry is not
+           * a blob. If it's a tree object we'll deal with it later instead,
+           * while traversing it. */
+          if (git_tree_entry_type(e) == GIT_OBJ_BLOB)
+            {
+              const git_tree_entry *other_entry;
+              const char *entry_relpath;
+
+              changed_path = svn_log_changed_path2_create(pool);
+              other_entry = git_tree_entry_byoid(other_tree, other_oid);
+              if (git_tree_entry_type(other_entry) == GIT_OBJ_BLOB)
+                changed_path->action = 'M';
+              else
+                changed_path->action = 'R';
+              entry_relpath = svn_relpath_join(svn_relpath_canonicalize(
+                                                 tree_relpath, pool),
+                                               git_tree_entry_name(e), pool),
+              svn_hash_sets(changed_paths, entry_relpath, changed_path);
+
+              if (changed_path->action == 'R' &&
+                  git_tree_entry_type(other_entry) == GIT_OBJ_TREE)
+                {
+                  git_tree *added_tree;
+
+                  git_err = git_tree_entry_to_object(
+                              (git_object **)&added_tree, repos,
+                              other_entry);
+                  if (git_err)
+                    return svn_error_trace(svn_ra_git__wrap_git_error());
+                  SVN_ERR(walk_added_tree(changed_paths, entry_relpath, added_tree, pool));
+                  git_tree_free(added_tree);
+                }
+            }
+        }
+
+      /* This other entry has been dealt with. */
+      svn_hash_sets(other_entries, git_tree_entry_name(e), NULL);
+    }
+
+  /* Mark any remaining other entries as newly added. */
+  if (apr_hash_count(other_entries))
+    {
+      apr_hash_index_t *hi;
+
+      for (hi = apr_hash_first(pool, other_entries); hi;
+           hi = apr_hash_next(hi))
+        {
+          const char *other_entry_name = svn__apr_hash_index_key(hi);
+          const git_oid *other_entry_id = svn__apr_hash_index_val(hi);
+          const git_tree_entry *other_entry;
+          const char *other_entry_relpath;
+
+          changed_path = svn_log_changed_path2_create(pool);
+          changed_path->action = 'A';
+          other_entry_relpath = svn_relpath_join(
+                                  svn_relpath_canonicalize(tree_relpath,
+                                                           pool),
+                                  other_entry_name, pool),
+          svn_hash_sets(changed_paths, other_entry_relpath, changed_path);
+
+          other_entry = git_tree_entry_byoid(other_tree, other_entry_id);
+          if (git_tree_entry_type(other_entry) == GIT_OBJ_TREE)
+            {
+              git_tree *added_tree;
+
+              git_err = git_tree_entry_to_object(
+                          (git_object **)&added_tree, repos, other_entry);
+              if (git_err)
+                return svn_error_trace(svn_ra_git__wrap_git_error());
+
+              SVN_ERR(walk_added_tree(changed_paths, other_entry_relpath,
+                                      added_tree, pool));
+              git_tree_free(added_tree);
+            }
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+struct find_changed_paths_walk_baton {
+  apr_hash_t *changed_paths;
+  git_repository *repos;
+  git_tree *other_tree;
+  apr_pool_t *pool;
+  svn_error_t *err;
+} find_changed_paths_walk_baton;
+
+/* Implements git_treewalk_cb */
+static int
+find_changed_paths_walk_cb(const char *root,
+                           const git_tree_entry *entry,
+                           void *payload)
+{
+  struct find_changed_paths_walk_baton *b = payload;
+  int git_err;
+  git_tree_entry *other_root_entry;
+  git_tree_entry *other_entry;
+  git_otype other_type;
+  git_tree *tree;
+  git_tree *other_tree;
+  const char *entry_relpath;
+  svn_log_changed_path2_t *changed_path;
+
+  /* If this entry is not a tree object, we're not interested. */
+  if (git_tree_entry_type(entry) != GIT_OBJ_TREE)
+    return 0;
+
+  /* If this entry's root doesn't exist in the other tree,
+   * this entry was deleted along with the root. */
+  git_err = git_tree_entry_bypath(&other_root_entry, b->other_tree, root);
+  if (git_err)
+    {
+      if (git_err == GIT_ENOTFOUND)
+        {
+          giterr_clear();
+          return 0;
+        }
+
+      b->err = svn_error_trace(svn_ra_git__wrap_git_error());
+      return -1;
+    }
+  git_tree_entry_free(other_root_entry);
+
+  /* Look up the corresponding entry in the other tree. */
+  root = svn_relpath_canonicalize(root, b->pool);
+  entry_relpath = svn_relpath_join(root, git_tree_entry_name(entry), b->pool);
+  git_err = git_tree_entry_bypath(&other_entry, b->other_tree, entry_relpath);
+  if (git_err)
+    {
+      if (git_err == GIT_ENOTFOUND)
+        {
+          /* The entry has been deleted in the other tree. */
+          giterr_clear();
+          changed_path = svn_log_changed_path2_create(b->pool);
+          changed_path->action = 'D';
+          svn_hash_sets(b->changed_paths, entry_relpath, changed_path);
+          return 0;
+        }
+
+      b->err = svn_error_trace(svn_ra_git__wrap_git_error());
+      return -1;
+    }
+
+  other_type = git_tree_entry_type(other_entry);
+  if (other_type != GIT_OBJ_TREE)
+    {
+      /* The tree object has been replaced in the other tree
+       * by an object of a different type, most likely a blob. */
+      changed_path = svn_log_changed_path2_create(b->pool);
+      changed_path->action = 'R';
+      svn_hash_sets(b->changed_paths, entry_relpath, changed_path);
+      return 0;
+    }
+
+  /* Fetch the entry's tree object... */
+  git_err = git_tree_entry_to_object(((git_object **)&tree), b->repos, entry);
+  if (git_err)
+    {
+      b->err = svn_error_trace(svn_ra_git__wrap_git_error());
+      return -1;
+    }
+
+  /* .. and fetch the other entry's tree object .. */
+  git_err = git_tree_entry_to_object(((git_object **)&other_tree), b->repos,
+                                     other_entry);
+  git_tree_entry_free(other_entry);
+  if (git_err)
+    {
+      b->err = svn_error_trace(svn_ra_git__wrap_git_error());
+      return -1;
+    }
+
+  /* .. and compare the entries of both trees. */
+  b->err = svn_error_trace(compare_git_tree_entries(b->changed_paths,
+                                                    b->repos, tree, other_tree,
+                                                    entry_relpath, b->pool));
+  if (b->err)
+    return -1;
+    
+  return 0;
+}
+
+static svn_error_t *
+find_changed_paths(apr_hash_t **changed_paths,
+                   git_repository *repos,
+                   git_tree *tree,
+                   git_tree *other_tree,
+                   apr_pool_t *pool)
+{
+  int git_err;
+  struct find_changed_paths_walk_baton b;
+
+  b.changed_paths = apr_hash_make(pool);
+  b.repos = repos;
+  b.other_tree = other_tree;
+  b.pool = pool;
+  b.err = SVN_NO_ERROR;
+
+  if (tree == NULL)
+    {
+      SVN_ERR(walk_added_tree(b.changed_paths, "", other_tree, pool));
+    }
+  else
+    {
+      /* Compare the root entries. */
+      SVN_ERR(compare_git_tree_entries(b.changed_paths, repos, tree, other_tree,
+                                      "", pool));
+
+      /* Walk tree entries to compare children. */
+      git_err = git_tree_walk(tree, GIT_TREEWALK_PRE,
+                              find_changed_paths_walk_cb, &b);
+      if (git_err)
+        return svn_error_trace(svn_ra_git__wrap_git_error());
+      if (b.err)
+        return svn_error_trace(b.err);
+    }
+
+  *changed_paths = b.changed_paths;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__get_log(svn_ra_session_t *session,
+                    const apr_array_header_t *paths,
+                    svn_revnum_t start,
+                    svn_revnum_t end,
+                    int limit,
+                    svn_boolean_t discover_changed_paths,
+                    svn_boolean_t strict_node_history,
+                    svn_boolean_t include_merged_revisions,
+                    svn_move_behavior_t move_behavior,
+                    const apr_array_header_t *revprops,
+                    svn_log_entry_receiver_t receiver,
+                    void *receiver_baton,
+                    apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  int git_err;
+  git_commit *commit;
+  git_tree *tree;
+  svn_revnum_t revision;
+  int step;
+  apr_pool_t *iterpool;
+
+  if (!SVN_IS_VALID_REVNUM(start))
+    SVN_ERR(svn_ra_git__get_latest_revnum(session, &start, pool));
+  if (!SVN_IS_VALID_REVNUM(end))
+    SVN_ERR(svn_ra_git__get_latest_revnum(session, &end, pool));
+
+  step = (start < end) ? 1 : -1;
+  revision = start;
+  if (step == 1)
+    end++;
+  if (start == 0 && revision != end)
+    revision++;
+  if (revision == end)
+    end += step;
+  iterpool = svn_pool_create(sess->scratch_pool);
+  while (revision != end)
+    {
+      apr_hash_t *changed_paths;
+      git_tree *parent_tree;
+
+      svn_pool_clear(iterpool);
+
+      SVN_ERR(fetch_revision_root(&tree, &commit, NULL, &revision, sess, "",
+                                  revision, pool));
+      if (git_commit_parentcount(commit) == 0)
+        {
+          /* First commit. All tree entries were added. */
+          parent_tree = NULL;
+        }
+      else
+        {
+          git_commit *parent_commit;
+
+          git_err = git_commit_parent(&parent_commit, commit, 0);
+          if (git_err)
+            return svn_error_trace(svn_ra_git__wrap_git_error());
+
+          git_err = git_commit_tree(&parent_tree, parent_commit);
+          if (git_err)
+            {
+              git_commit_free(parent_commit);
+              return svn_error_trace(svn_ra_git__wrap_git_error());
+            }
+
+          git_commit_free(parent_commit);
+        }
+
+      SVN_ERR((find_changed_paths(&changed_paths, sess->repos,
+                                  parent_tree, tree, iterpool)));
+      if (parent_tree)
+        git_tree_free(parent_tree);
+
+      if (apr_hash_count(changed_paths) > 0)
+        {
+          svn_boolean_t show_log = FALSE;
+
+          if (paths)
+            {
+              int i;
+
+              /* Check if a desired path is among the changed paths. */
+              for (i = 0; i < paths->nelts; i++)
+                {
+                  const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+                  if (!svn_stringbuf_isempty(sess->fs_path))
+                    path = svn_relpath_join(sess->fs_path->data, path,
+                                            iterpool);
+                  
+                  show_log = (path[0] == '\0' ||
+                              svn_hash_gets(changed_paths, path) != NULL);
+                  if (show_log)
+                    break;
+                }
+            }
+          else
+            show_log = TRUE;
+
+          if (show_log)
+            {
+              svn_log_entry_t *log_entry = svn_log_entry_create(iterpool);
+
+              if (discover_changed_paths)
+                {
+                  /* ### Some callers expect svn_fspath style keys...
+                   * ### convert all keys. */
+                  apr_hash_index_t *hi;
+
+                  log_entry->changed_paths2 = apr_hash_make(iterpool);
+
+                  for (hi = apr_hash_first(pool, changed_paths); hi;
+                       hi = apr_hash_next(hi))
+                    {
+                      const char *relpath_key = svn__apr_hash_index_key(hi);
+                      void *val = svn__apr_hash_index_val(hi);
+                      const char *fspath_key;
+
+                      fspath_key = apr_pstrcat(iterpool, "/", relpath_key,
+                                               SVN_VA_NULL);
+                      svn_hash_sets(log_entry->changed_paths2, fspath_key, val);
+                    }
+
+                  log_entry->changed_paths = log_entry->changed_paths2;
+                }
+
+              log_entry->revision = revision;
+
+              if (revprops)
+                {
+                  apr_hash_t *revprops_hash = NULL;
+                  int i;
+
+                  if (revprops->nelts > 0)
+                    revprops_hash = svn_ra_git__make_revprops_hash(commit,
+                                                                   iterpool);
+
+                  log_entry->revprops = apr_hash_make(pool);
+                  for (i = 0; i < revprops->nelts; i++)
+                    {
+                      const char *revprop_name = APR_ARRAY_IDX(revprops, i,
+                                                               const char *);
+                      svn_string_t *val = svn_hash_gets(revprops_hash,
+                                                        revprop_name);
+                      if (val)
+                        svn_hash_sets(log_entry->revprops, revprop_name, val);
+                    }
+                }
+              else
+                log_entry->revprops = svn_ra_git__make_revprops_hash(commit,
+                                                                     iterpool);
+
+              SVN_ERR(receiver(receiver_baton, log_entry, iterpool));
+            }
+        }
+
+      revision += step;
+    }
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_git__check_path(svn_node_kind_t *kind, git_tree *tree, const char *path)
+{
+  git_tree_entry *entry;
+  int git_err;
+
+  if (path[0] == '\0')
+    {
+      /* The root directory of the repository. */
+      *kind = svn_node_dir;
+      return SVN_NO_ERROR;
+    }
+
+  git_err = git_tree_entry_bypath(&entry, tree, path);
+  if (git_err)
+    {
+      git_tree_free(tree);
+
+      if (git_err == GIT_ENOTFOUND)
+        {
+          *kind = svn_node_none;
+          return SVN_NO_ERROR;
+        }
+
+      return svn_error_trace(svn_ra_git__wrap_git_error());
+    }
+
+  if (git_tree_entry_filemode(entry) == GIT_FILEMODE_COMMIT)
+    *kind = svn_node_none; /* ### submodule, map to external */
+  else
+    {
+      git_otype type = git_tree_entry_type(entry);
+
+      if (type == GIT_OBJ_TREE) 
+        *kind = svn_node_dir;
+      else if (type == GIT_OBJ_BLOB)
+        *kind = svn_node_file;
+      else
+        *kind = svn_node_unknown;
+    }
+
+  git_tree_entry_free(entry);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__do_check_path(svn_ra_session_t *session,
+                          const char *path,
+                          svn_revnum_t revision,
+                          svn_node_kind_t *kind,
+                          apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  git_commit *commit;
+  git_tree *tree;
+
+  SVN_ERR(fetch_revision_root(&tree, &commit, &path, &revision,
+                              sess, path, revision, pool));
+
+  SVN_ERR(svn_ra_git__check_path(kind, tree, path));
+
+  git_tree_free(tree);
+  git_commit_free(commit);
+  return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_git__stat(svn_ra_session_t *session,
+                 const char *path,
+                 svn_revnum_t revision,
+                 svn_dirent_t **dirent,
+                 apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  int git_err;
+  git_commit *commit;
+  git_tree *tree;
+  git_otype type;
+
+  SVN_ERR(fetch_revision_root(&tree, &commit, &path, &revision,
+                              sess, path, revision, pool));
+
+  if (path[0] == '\0')
+    {
+      /* The root directory of the repository. */
+      type = GIT_OBJ_TREE;
+      SVN_ERR(map_obj_to_dirent(dirent, sess->revmap, path, revision, SVN_DIRENT_ALL,
+                                sess->repos, commit, (git_object *)tree, pool));
+    }
+  else
+    {
+      git_tree_entry *entry;
+
+      git_err = git_tree_entry_bypath(&entry, tree, path);
+      if (git_err)
+        {
+          git_tree_free(tree);
+          git_commit_free(commit);
+
+          if (git_err == GIT_ENOTFOUND)
+            {
+              *dirent = NULL;
+              return SVN_NO_ERROR;
+            }
+
+          return svn_error_trace(svn_ra_git__wrap_git_error());
+        }
+
+      type = git_tree_entry_type(entry);
+      if (type == GIT_OBJ_TREE) 
+        {
+          git_tree *this_tree;
+
+          git_err = git_tree_lookup(&this_tree, sess->repos, git_tree_entry_id(entry));
+          if (git_err)
+            {
+              git_tree_entry_free(entry);
+              git_tree_free(tree);
+              git_commit_free(commit);
+
+              return svn_error_trace(svn_ra_git__wrap_git_error());
+            }
+
+          SVN_ERR(map_obj_to_dirent(dirent, sess->revmap, path, revision, SVN_DIRENT_ALL,
+                                    sess->repos, commit, (git_object *)this_tree, pool));
+          git_tree_free(this_tree);
+        }
+      else if (type == GIT_OBJ_BLOB)
+        {
+          git_blob *blob;
+
+          git_err = git_blob_lookup(&blob, sess->repos, git_tree_entry_id(entry));
+          if (git_err)
+            {
+              git_tree_free(tree);
+              git_commit_free(commit);
+              return svn_error_trace(svn_ra_git__wrap_git_error());
+            }
+
+          SVN_ERR(map_obj_to_dirent(dirent, sess->revmap, path, revision, SVN_DIRENT_ALL,
+                                    sess->repos, commit, (git_object *)blob, pool));
+          git_blob_free(blob);
+        }
+      else
+        {
+          git_tree_entry_free(entry);
+          return svn_error_trace(svn_error_create(SVN_ERR_FS_NO_SUCH_ENTRY, NULL, NULL));
+        }
+
+      git_tree_entry_free(entry);
+    }
+
+  git_tree_free(tree);
+  git_commit_free(commit);
+
+  return SVN_NO_ERROR;
+}
+
+
+
+/* Obtain the properties for a node, including its 'entry props */
+static svn_error_t *
+get_node_props(apr_hash_t **props,
+               svn_fs_root_t *root,
+               const char *path,
+               const char *uuid,
+               apr_pool_t *result_pool,
+               apr_pool_t *scratch_pool)
+{
+  /* We have no 'wcprops' in ra_git, but might someday. */
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+/* Getting just one file. */
+static svn_error_t *
+svn_ra_git__get_file(svn_ra_session_t *session,
+                     const char *path,
+                     svn_revnum_t revision,
+                     svn_stream_t *stream,
+                     svn_revnum_t *fetched_rev,
+                     apr_hash_t **props,
+                     apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  int git_err;
+  git_commit *commit;
+  git_tree *tree;
+  git_tree_entry *entry;
+  git_otype type;
+  git_blob *blob;
+
+  SVN_ERR(fetch_revision_root(&tree, &commit, &path, &revision,
+                              sess, path, revision, pool));
+
+  if (path[0] == '\0')
+    {
+      git_tree_free(tree);
+      git_commit_free(commit);
+      return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, NULL);
+    }
+
+  git_err = git_tree_entry_bypath(&entry, tree, path);
+  if (git_err)
+    {
+      git_tree_free(tree);
+      git_commit_free(commit);
+
+      if (git_err == GIT_ENOTFOUND)
+        return svn_error_create(SVN_ERR_FS_NO_SUCH_ENTRY, NULL, NULL);
+
+      return svn_error_trace(svn_ra_git__wrap_git_error());
+    }
+
+  type = git_tree_entry_type(entry);
+  if (type != GIT_OBJ_BLOB)
+    {
+      git_tree_entry_free(entry);
+      git_tree_free(tree);
+      git_commit_free(commit);
+      return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, NULL);
+    }
+
+  if (stream)
+    {
+      apr_size_t total_size;
+      const char *data;
+      apr_size_t bytes_copied;
+
+      git_err = git_blob_lookup(&blob, sess->repos, git_tree_entry_id(entry));
+      if (git_err)
+        {
+          git_tree_free(tree);
+          git_commit_free(commit);
+          return svn_error_trace(svn_ra_git__wrap_git_error());
+        }
+
+      total_size = git_blob_rawsize(blob);
+      data = git_blob_rawcontent(blob);
+      bytes_copied = 0;
+
+      while (bytes_copied < total_size)
+        {
+          apr_size_t chunk_size = 1024;
+          apr_size_t len;
+
+          if (total_size - bytes_copied < chunk_size)
+            chunk_size = total_size - bytes_copied;
+
+          len = chunk_size;
+          SVN_ERR(svn_stream_write(stream, data, &len));
+          if (len != chunk_size)
+            {
+              git_tree_entry_free(entry);
+              git_tree_free(tree);
+              git_commit_free(commit);
+              return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
+            }
+
+          data += chunk_size;
+          bytes_copied += chunk_size;
+        }
+    }
+
+  if (fetched_rev)
+    *fetched_rev = revision;
+
+  if (props)
+    *props = apr_hash_make(pool);
+
+  git_tree_entry_free(entry);
+  git_tree_free(tree);
+  git_commit_free(commit);
+
+  return SVN_NO_ERROR;
+}
+
+/* Getting a directory's entries */
+static svn_error_t *
+svn_ra_git__get_dir(svn_ra_session_t *session,
+                    apr_hash_t **dirents,
+                    svn_revnum_t *fetched_rev,
+                    apr_hash_t **props,
+                    const char *path,
+                    svn_revnum_t revision,
+                    apr_uint32_t dirent_fields,
+                    apr_pool_t *pool)
+{
+  svn_ra_git__session_baton_t *sess = session->priv;
+  int git_err;
+  git_commit *commit;
+  git_tree *tree;
+
+  SVN_ERR(fetch_revision_root(&tree, &commit, &path, &revision,
+                              sess, path, revision, pool));
+
+  if (path[0] != '\0')
+    {
+      git_tree_entry *entry;
+      git_otype type;
+      git_object *subtree;
+
+      git_err = git_tree_entry_bypath(&entry, tree, path);
+      if (git_err)
+        {
+          git_tree_free(tree);
+          git_commit_free(commit);
+
+          if (git_err == GIT_ENOTFOUND)
+            return svn_error_create(SVN_ERR_FS_NO_SUCH_ENTRY, NULL, NULL);
+
+          return svn_error_trace(svn_ra_git__wrap_git_error());
+        }
+
+      /* ### Ignore git submodules for now.
+       * ### Eventually we'll map them to svn:externals. */ 
+      if (git_tree_entry_filemode(entry) == GIT_FILEMODE_COMMIT)
+        {
+          git_tree_entry_free(entry);
+          return svn_error_createf(SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+                                   _("'%s' is a git submodule but submodules are not "
+                                     "yet supported"), path);
+        }
+
+      type = git_tree_entry_type(entry);
+      if (type != GIT_OBJ_TREE)
+        {
+          git_tree_entry_free(entry);
+          git_tree_free(tree);
+          git_commit_free(commit);
+          return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, NULL);
+        }
+
+      git_err = git_tree_entry_to_object(&subtree, sess->repos, entry);
+      if (git_err)
+        return svn_error_trace(svn_ra_git__wrap_git_error());
+
+      git_tree_free(tree);
+      tree = (git_tree *)subtree;
+      git_tree_entry_free(entry);
+    }
+
+  if (dirents)
+    {
+      apr_size_t idx;
+      apr_pool_t *iterpool;
+      
+      *dirents = apr_hash_make(pool);
+
+      iterpool = svn_pool_create(sess->scratch_pool);
+      for (idx = 0; idx < git_tree_entrycount(tree); idx++)
+        {
+          const git_tree_entry *entry;
+          git_object *obj;
+          svn_dirent_t *dirent;
+
+          svn_pool_clear(iterpool);
+
+          entry = git_tree_entry_byindex(tree, idx);
+          SVN_ERR_ASSERT(entry);
+
+          /* Ignore git submodules for now. Eventually we'll map them to svn:externals. */ 
+          if (git_tree_entry_filemode(entry) == GIT_FILEMODE_COMMIT)
+            continue;
+
+          git_err = git_tree_entry_to_object(&obj, sess->repos, entry);
+          if (git_err)
+            {
+              git_tree_free(tree);
+              git_commit_free(commit);
+              return svn_error_trace(svn_ra_git__wrap_git_error());
+            }
+
+          SVN_ERR(map_obj_to_dirent(&dirent, sess->revmap,
+                                    svn_relpath_join(path,
+                                                     git_tree_entry_name(entry),
+                                                     iterpool),
+                                    revision, dirent_fields, sess->repos, commit,
+                                    obj, pool));
+          svn_hash_sets(*dirents, apr_pstrdup(pool, git_tree_entry_name(entry)), dirent);
+          git_object_free(obj);
+        }
+      svn_pool_destroy(iterpool);
+    }
+
+  if (fetched_rev)
+    *fetched_rev = revision;
+
+  if (props)
+    *props = apr_hash_make(pool);
+
+  git_tree_free(tree);
+  git_commit_free(commit);
+
+  return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_git__get_locations(svn_ra_session_t *session,
+                          apr_hash_t **locations,
+                          const char *path,
+                          svn_revnum_t peg_revision,
+                          const apr_array_header_t *location_revisions,
+                          apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__get_location_segments(svn_ra_session_t *session,
+                                  const char *path,
+                                  svn_revnum_t peg_revision,
+                                  svn_revnum_t start_rev,
+                                  svn_revnum_t end_rev,
+                                  svn_location_segment_receiver_t receiver,
+                                  void *receiver_baton,
+                                  apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+static svn_error_t *
+svn_ra_git__lock(svn_ra_session_t *session,
+                 apr_hash_t *path_revs,
+                 const char *comment,
+                 svn_boolean_t force,
+                 svn_ra_lock_callback_t lock_func,
+                 void *lock_baton,
+                 apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__unlock(svn_ra_session_t *session,
+                   apr_hash_t *path_tokens,
+                   svn_boolean_t force,
+                   svn_ra_lock_callback_t lock_func,
+                   void *lock_baton,
+                   apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+
+static svn_error_t *
+svn_ra_git__get_lock(svn_ra_session_t *session,
+                     svn_lock_t **lock,
+                     const char *path,
+                     apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+
+static svn_error_t *
+svn_ra_git__get_locks(svn_ra_session_t *session,
+                      apr_hash_t **locks,
+                      const char *path,
+                      svn_depth_t depth,
+                      apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__replay(svn_ra_session_t *session,
+                   svn_revnum_t revision,
+                   svn_revnum_t low_water_mark,
+                   svn_boolean_t send_deltas,
+                   const svn_delta_editor_t *editor,
+                   void *edit_baton,
+                   apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__replay_range(svn_ra_session_t *session,
+                           svn_revnum_t start_revision,
+                           svn_revnum_t end_revision,
+                           svn_revnum_t low_water_mark,
+                           svn_boolean_t send_deltas,
+                           svn_ra_replay_revstart_callback_t revstart_func,
+                           svn_ra_replay_revfinish_callback_t revfinish_func,
+                           void *replay_baton,
+                           apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__has_capability(svn_ra_session_t *session,
+                             svn_boolean_t *has,
+                             const char *capability,
+                             apr_pool_t *pool)
+{
+
+  if (strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0)
+    {
+      *has = TRUE;
+    }
+  else if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
+      || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0
+      || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0
+      || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0
+      || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0
+      || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0
+      || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0
+      )
+    {
+      /* ### These features are not yet implemented. */
+      *has = FALSE;
+    }
+  else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
+    {
+      /* Mergeinfo is unsupported by this RA layer.
+       * We can simply rely on git's native merge capabilities instead. */
+      *has = FALSE;
+    }
+  else  /* Don't know any other capabilities, so error. */
+    {
+      return svn_error_createf
+        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+         _("Don't know anything about capability '%s'"), capability);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_git__get_deleted_rev(svn_ra_session_t *session,
+                            const char *path,
+                            svn_revnum_t peg_revision,
+                            svn_revnum_t end_revision,
+                            svn_revnum_t *revision_deleted,
+                            apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+static svn_error_t *
+svn_ra_git__get_inherited_props(svn_ra_session_t *session,
+                                  apr_array_header_t **iprops,
+                                  const char *path,
+                                  svn_revnum_t revision,
+                                  apr_pool_t *result_pool,
+                                  apr_pool_t *scratch_pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+static svn_error_t *
+svn_ra_git__register_editor_shim_callbacks(svn_ra_session_t *session,
+                                    svn_delta_shim_callbacks_t *callbacks)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_git__get_commit_ev2(svn_editor_t **editor,
+                           svn_ra_session_t *session,
+                           apr_hash_t *revprops,
+                           svn_commit_callback2_t commit_cb,
+                           void *commit_baton,
+                           apr_hash_t *lock_tokens,
+                           svn_boolean_t keep_locks,
+                           svn_ra__provide_base_cb_t provide_base_cb,
+                           svn_ra__provide_props_cb_t provide_props_cb,
+                           svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+                           void *cb_baton,
+                           svn_cancel_func_t cancel_func,
+                           void *cancel_baton,
+                           apr_pool_t *result_pool,
+                           apr_pool_t *scratch_pool)
+{
+  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+/*----------------------------------------------------------------*/
+
+static const svn_version_t *
+ra_git_version(void)
+{
+  SVN_VERSION_BODY;
+}
+
+/** The ra_vtable **/
+
+static const svn_ra__vtable_t ra_git_vtable =
+{
+  ra_git_version,
+  svn_ra_git__get_description,
+  svn_ra_git__get_schemes,
+  svn_ra_git__open,
+  svn_ra_git__dup_session,
+  svn_ra_git__reparent,
+  svn_ra_git__get_session_url,
+  svn_ra_git__get_latest_revnum,
+  svn_ra_git__get_dated_revision,
+  svn_ra_git__change_rev_prop,
+  svn_ra_git__rev_proplist,
+  svn_ra_git__rev_prop,
+  svn_ra_git__get_commit_editor,
+  svn_ra_git__get_file,
+  svn_ra_git__get_dir,
+  svn_ra_git__get_mergeinfo,
+  svn_ra_git__do_update,
+  svn_ra_git__do_switch,
+  svn_ra_git__do_status,
+  svn_ra_git__do_diff,
+  svn_ra_git__get_log,
+  svn_ra_git__do_check_path,
+  svn_ra_git__stat,
+  svn_ra_git__get_uuid,
+  svn_ra_git__get_repos_root,
+  svn_ra_git__get_locations,
+  svn_ra_git__get_location_segments,
+  svn_ra_git__get_file_revs,
+  svn_ra_git__lock,
+  svn_ra_git__unlock,
+  svn_ra_git__get_lock,
+  svn_ra_git__get_locks,
+  svn_ra_git__replay,
+  svn_ra_git__has_capability,
+  svn_ra_git__replay_range,
+  svn_ra_git__get_deleted_rev,
+  svn_ra_git__register_editor_shim_callbacks,
+  svn_ra_git__get_inherited_props,
+  svn_ra_git__get_commit_ev2
+};
+
+
+/*----------------------------------------------------------------*/
+
+/** The One Public Routine, called by libsvn_ra **/
+
+svn_error_t *
+svn_ra_git__init(const svn_version_t *loader_version,
+                 const svn_ra__vtable_t **vtable,
+                 apr_pool_t *pool)
+{
+  static const svn_version_checklist_t checklist[] =
+    {
+      { "svn_subr",  svn_subr_version },
+      { NULL, NULL }
+    };
+
+
+  /* Simplified version check to make sure we can safely use the
+     VTABLE parameter. The RA loader does a more exhaustive check. */
+  if (loader_version->major != SVN_VER_MAJOR)
+    return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
+                             _("Unsupported RA loader version (%d) for ra_git"),
+                             loader_version->major);
+
+  SVN_ERR(svn_ver_check_list2(ra_git_version(), checklist, svn_ver_equal));
+
+  *vtable = &ra_git_vtable;
+
+  return SVN_NO_ERROR;
+}
+
+/* Compatibility wrapper for pre-1.2 subversions.  Needed? */
+#define NAME "ra_git"
+#define DESCRIPTION RA_GIT_DESCRIPTION
+#define VTBL ra_git_vtable
+#define INITFUNC svn_ra_git__init
+#define COMPAT_INITFUNC svn_ra_git_init
+#include "../libsvn_ra/wrapper_template.h"
+

Propchange: subversion/branches/ra-git/subversion/libsvn_ra_git/ra_plugin.c
------------------------------------------------------------------------------
    svn:eol-style = native