blob: e29be6786bb910dd76402645b562031dfc4f3c21 [file] [log] [blame]
/*
* merge.c: handle the MERGE response processing
*
* ====================================================================
* Copyright (c) 2000-2002 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <apr_pools.h>
#include <apr_buckets.h>
#include <apr_xml.h>
#include <httpd.h>
#include <util_filter.h>
#include "svn_pools.h"
#include "svn_fs.h"
#include "svn_repos.h"
#include "dav_svn.h"
/* #################################################################
These functions are currently *VERY* SVN specific.
* we don't check prop_elem for what the client requested
* we presume a baseline was checked out into the activity, and is
part of the MERGE
* we presume that all "changed" files/dirs were checked out into
the activity and are part of the MERGE
(not sure if this is SVN specific; I can't see how a file/dir
would be part of the new revision if a working resource had
not been created for it)
* we return some props for some resources, and a different set for
other resources (to keep the wire smaller for now)
At some point in the future, we'll want to make this "real". Especially
for proper interoperability.
#################################################################
*/
typedef struct {
apr_pool_t *pool;
ap_filter_t *output;
apr_bucket_brigade *bb;
svn_fs_root_t *root;
const dav_svn_repos *repos;
} merge_response_ctx;
typedef struct mr_baton {
merge_response_ctx *mrc;
/* for directories, this is a subpool. otherwise, the pool to use. */
apr_pool_t *pool;
/* path for this baton's corresponding FS object */
const char *path;
/* for a directory, have we seen a change yet? */
svn_boolean_t seen_change;
} mr_baton;
/* -------------------------------------------------------------------------
PRIVATE HELPER FUNCTIONS
*/
static mr_baton *make_child_baton(mr_baton *parent, const char *name,
svn_boolean_t is_dir)
{
apr_pool_t *pool;
mr_baton *subdir;
if (is_dir)
pool = svn_pool_create(parent->pool);
else
pool = parent->pool;
subdir = apr_pcalloc(pool, sizeof(*subdir));
subdir->mrc = parent->mrc;
subdir->pool = pool;
if (parent->path[1] == '\0') /* must be "/" */
subdir->path = apr_pstrcat(pool, "/", name, NULL);
else
subdir->path = apr_pstrcat(pool, parent->path, "/", name, NULL);
return subdir;
}
/* send a response to the client for this baton */
static svn_error_t *send_response(mr_baton *baton, svn_boolean_t is_dir)
{
merge_response_ctx *mrc = baton->mrc;
const char *href;
const char *rt;
svn_fs_id_t *id;
svn_stringbuf_t *stable_id;
const char *vsn_url;
apr_status_t status;
href = dav_svn_build_uri(mrc->repos, DAV_SVN_BUILD_URI_PUBLIC,
SVN_IGNORED_REVNUM, baton->path,
0 /* add_href */, baton->pool);
rt = is_dir
? "<D:resourcetype><D:collection/></D:resourcetype>" DEBUG_CR
: "<D:resourcetype/>" DEBUG_CR;
SVN_ERR( svn_fs_node_id(&id, mrc->root, baton->path, baton->pool) );
stable_id = svn_fs_unparse_id(id, baton->pool);
svn_stringbuf_appendcstr(stable_id, baton->path);
vsn_url = dav_svn_build_uri(mrc->repos, DAV_SVN_BUILD_URI_VERSION,
SVN_INVALID_REVNUM, stable_id->data,
0 /* add_href */, baton->pool);
status = ap_fputstrs(mrc->output, mrc->bb,
"<D:response>" DEBUG_CR
"<D:href>",
apr_xml_quote_string (baton->pool, href, 1),
"</D:href>" DEBUG_CR
"<D:propstat><D:prop>" DEBUG_CR,
rt,
"<D:checked-in><D:href>",
apr_xml_quote_string (baton->pool, vsn_url, 1),
"</D:href></D:checked-in>" DEBUG_CR
"</D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR
"</D:response>" DEBUG_CR,
NULL);
if (status != APR_SUCCESS)
return svn_error_create(status, 0, NULL, baton->pool,
"could not write response to output");
return APR_SUCCESS;
}
/* -------------------------------------------------------------------------
EDITOR FUNCTIONS
*/
static svn_error_t *mr_open_root(void *edit_baton,
svn_revnum_t base_revision,
void **root_baton)
{
merge_response_ctx *mrc = edit_baton;
apr_pool_t *pool;
mr_baton *b;
/* note that we create a subpool; the root_baton is passed to the
close_directory callback, where we will destroy the pool. */
pool = svn_pool_create(mrc->pool);
b = apr_pcalloc(pool, sizeof(*b));
b->mrc = mrc;
b->pool = pool;
b->path = "/";
*root_baton = b;
return NULL;
}
static svn_error_t *mr_delete_entry(svn_stringbuf_t *name,
svn_revnum_t revision,
void *parent_baton)
{
mr_baton *parent = parent_baton;
/* Removing an item is an explicit change to the parent. Mark it so the
client will get the data on the new parent. */
parent->seen_change = TRUE;
return NULL;
}
static svn_error_t *mr_add_directory(svn_stringbuf_t *name,
void *parent_baton,
svn_stringbuf_t *copyfrom_path,
svn_revnum_t copyfrom_revision,
void **child_baton)
{
mr_baton *parent = parent_baton;
mr_baton *subdir = make_child_baton(parent, name->data, TRUE);
/* pretend that we've already seen a change for this dir (so that a prop
change won't generate a second response) */
subdir->seen_change = TRUE;
/* the response for this directory will occur at close_directory time */
/* Adding a subdir is an explicit change to the parent. Mark it so the
client will get the data on the new parent. */
parent->seen_change = TRUE;
*child_baton = subdir;
return NULL;
}
static svn_error_t *mr_open_directory(svn_stringbuf_t *name,
void *parent_baton,
svn_revnum_t base_revision,
void **child_baton)
{
mr_baton *parent = parent_baton;
mr_baton *subdir = make_child_baton(parent, name->data, TRUE);
/* Don't issue a response until we see a prop change, or a file/subdir
is added/removed inside this directory. */
*child_baton = subdir;
return NULL;
}
static svn_error_t *mr_change_dir_prop(void *dir_baton,
svn_stringbuf_t *name,
svn_stringbuf_t *value)
{
mr_baton *dir = dir_baton;
/* okay, this qualifies as a change, and we need to tell the client
(which happens at close_directory time). */
dir->seen_change = TRUE;
return NULL;
}
static svn_error_t *mr_close_directory(void *dir_baton)
{
mr_baton *dir = dir_baton;
/* if we ever saw a change for this directory, then issue a response
for it. */
if (dir->seen_change)
{
SVN_ERR( send_response(dir, TRUE /* is_dir */) );
}
svn_pool_destroy(dir->pool);
return NULL;
}
static svn_error_t *mr_add_file(svn_stringbuf_t *name,
void *parent_baton,
svn_stringbuf_t *copy_path,
svn_revnum_t copy_revision,
void **file_baton)
{
mr_baton *parent = parent_baton;
mr_baton *file = make_child_baton(parent, name->data, FALSE);
/* We wait until close_file to issue a response for this. */
/* Adding a file is an explicit change to the parent. Mark it so the
client will get the data on the new parent. */
parent->seen_change = TRUE;
*file_baton = file;
return NULL;
}
static svn_error_t *mr_open_file(svn_stringbuf_t *name,
void *parent_baton,
svn_revnum_t base_revision,
void **file_baton)
{
mr_baton *parent = parent_baton;
mr_baton *file = make_child_baton(parent, name->data, FALSE);
/* We wait until close_file to issue a response for this. */
*file_baton = file;
return NULL;
}
static svn_error_t *mr_close_file(void *file_baton)
{
/* nothing to do except for sending the response. */
return send_response(file_baton, FALSE /* is_dir */);
}
/* -------------------------------------------------------------------------
PUBLIC FUNCTIONS
*/
dav_error * dav_svn__merge_response(ap_filter_t *output,
const dav_svn_repos *repos,
svn_revnum_t new_rev,
apr_xml_elem *prop_elem,
apr_pool_t *pool)
{
apr_bucket_brigade *bb;
svn_fs_root_t *committed_root;
svn_fs_root_t *previous_root;
svn_error_t *serr;
const char *vcc;
char revbuf[20]; /* long enough for %ld */
svn_string_t *creationdate, *creator_displayname;
apr_hash_t *revs;
svn_revnum_t *rev_ptr;
svn_delta_edit_fns_t *editor;
merge_response_ctx mrc = { 0 };
serr = svn_fs_revision_root(&committed_root, repos->fs, new_rev, pool);
if (serr != NULL)
{
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Could not open the FS root for the "
"revision just committed.");
}
serr = svn_fs_revision_root(&previous_root, repos->fs, new_rev - 1, pool);
if (serr != NULL)
{
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Could not open the FS root for the "
"previous revision.");
}
bb = apr_brigade_create(pool, output->c->bucket_alloc);
/* prep some strings */
/* the HREF for the baseline is actually the VCC */
vcc = dav_svn_build_uri(repos, DAV_SVN_BUILD_URI_VCC, SVN_IGNORED_REVNUM,
NULL, 0 /* add_href */, pool);
/* the version-name of the baseline is the revision number */
sprintf(revbuf, "%ld", new_rev);
/* get the creationdate and creator-displayname of the new revision, too. */
serr = svn_fs_revision_prop(&creationdate, repos->fs, new_rev,
SVN_PROP_REVISION_DATE, pool);
if (serr != NULL)
{
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Could not get date of newest revision");
}
serr = svn_fs_revision_prop(&creator_displayname, repos->fs, new_rev,
SVN_PROP_REVISION_AUTHOR, pool);
if (serr != NULL)
{
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Could not get author of newest revision");
}
(void) ap_fputstrs(output, bb,
DAV_XML_HEADER DEBUG_CR
"<D:merge-response xmlns:D=\"DAV:\">" DEBUG_CR
"<D:updated-set>" DEBUG_CR
/* generate a response for the new baseline */
"<D:response>" DEBUG_CR
"<D:href>",
apr_xml_quote_string (pool, vcc, 1),
"</D:href>" DEBUG_CR
"<D:propstat><D:prop>" DEBUG_CR
/* ### this is wrong. it's a VCC, not a baseline. but
### we need to tell the client to look at *this*
### resource for the version-name. */
"<D:resourcetype><D:baseline/></D:resourcetype>" DEBUG_CR
"<D:version-name>", revbuf, "</D:version-name>" DEBUG_CR
"<D:creationdate>", creationdate->data,
"</D:creationdate>" DEBUG_CR
"<D:creator-displayname>", creator_displayname->data,
"</D:creator-displayname>" DEBUG_CR
"</D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR
"</D:response>" DEBUG_CR,
NULL);
/* Now we need to generate responses for all the resources which changed.
This is done through a delta of the two roots.
Note that a directory is not marked when open_dir is seen (since it
typically is used just for changing members in that directory); instead,
we want for a property change (the only reason the client would need to
fetch a new directory).
### we probably should say something about the dirs, so that we can
### pass back the new version URL */
/* ### hrm. needing this hash table feels wonky. */
revs = apr_hash_make(pool);
rev_ptr = apr_palloc(pool, sizeof(*rev_ptr));
*rev_ptr = new_rev - 1;
apr_hash_set(revs, "", APR_HASH_KEY_STRING, rev_ptr);
/* set up the editor for the delta process */
editor = svn_delta_old_default_editor(pool);
editor->open_root = mr_open_root;
editor->delete_entry = mr_delete_entry;
editor->add_directory = mr_add_directory;
editor->open_directory = mr_open_directory;
editor->change_dir_prop = mr_change_dir_prop;
editor->close_directory = mr_close_directory;
editor->add_file = mr_add_file;
editor->open_file = mr_open_file;
editor->close_file = mr_close_file;
/* set up the merge response context */
mrc.pool = pool;
mrc.output = output;
mrc.bb = bb;
mrc.root = committed_root;
mrc.repos = repos;
serr = svn_repos_dir_delta(previous_root, "/",
NULL, /* ### should fix */
revs,
committed_root, "/",
editor, &mrc,
FALSE, /* don't bother with text-deltas */
TRUE, /* Do recurse into subdirectories */
FALSE, /* Do not allow copyfrom args */
pool);
if (serr != NULL)
{
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"could not process the merge delta.");
}
/* wrap up the merge response */
(void) ap_fputs(output, bb,
"</D:updated-set>" DEBUG_CR
"</D:merge-response>" DEBUG_CR);
/* send whatever is left in the brigade */
(void) ap_pass_brigade(output, bb);
return NULL;
}
/* ----------------------------------------------------------------
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/