/* * merge.c: handle the MERGE response processing * * ==================================================================== * Copyright (c) 2000-2004 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 #include #include #include #include #include #include "svn_pools.h" #include "svn_fs.h" #include "svn_props.h" #include "dav_svn.h" #include "svn_xml.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. ################################################################# */ /* ------------------------------------------------------------------------- PRIVATE HELPER FUNCTIONS */ /* send a response to the client for this baton */ static svn_error_t *send_response(const dav_svn_repos *repos, svn_fs_root_t *root, const char *path, svn_boolean_t is_dir, ap_filter_t *output, apr_bucket_brigade *bb, apr_pool_t *pool) { const char *href; const char *vsn_url; apr_status_t status; svn_revnum_t rev_to_use; href = dav_svn_build_uri(repos, DAV_SVN_BUILD_URI_PUBLIC, SVN_IGNORED_REVNUM, path, 0 /* add_href */, pool); rev_to_use = dav_svn_get_safe_cr(root, path, pool); vsn_url = dav_svn_build_uri(repos, DAV_SVN_BUILD_URI_VERSION, rev_to_use, path, 0 /* add_href */, pool); status = ap_fputstrs(output, bb, "" DEBUG_CR "", apr_xml_quote_string(pool, href, 1), "" DEBUG_CR "" DEBUG_CR, is_dir ? "" : "", DEBUG_CR, "", apr_xml_quote_string(pool, vsn_url, 1), "" DEBUG_CR "" DEBUG_CR "HTTP/1.1 200 OK" DEBUG_CR "" DEBUG_CR "" DEBUG_CR, NULL); if (status != APR_SUCCESS) return svn_error_wrap_apr(status, "Can't write response to output"); return SVN_NO_ERROR; } static svn_error_t *do_resources(const dav_svn_repos *repos, svn_fs_root_t *root, svn_revnum_t revision, ap_filter_t *output, apr_bucket_brigade *bb, apr_pool_t *pool) { apr_hash_t *changes; apr_hash_t *sent = apr_hash_make(pool); apr_hash_index_t *hi; apr_pool_t *subpool = svn_pool_create(pool); /* Fetch the paths changed in this revision. This will contain everything except otherwise-unchanged parent directories of added and deleted things. Also, note that deleted things don't merit responses of their own -- they are considered modifications to their parent. */ SVN_ERR(svn_fs_paths_changed(&changes, root, pool)); for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) { const void *key; void *val; const char *path; svn_fs_path_change_t *change; svn_boolean_t send_self; svn_boolean_t send_parent; svn_pool_clear(subpool); apr_hash_this(hi, &key, NULL, &val); path = key; change = val; /* Figure out who needs to get sent. */ switch (change->change_kind) { case svn_fs_path_change_delete: send_self = FALSE; send_parent = TRUE; break; case svn_fs_path_change_add: case svn_fs_path_change_replace: send_self = TRUE; send_parent = TRUE; break; case svn_fs_path_change_modify: default: send_self = TRUE; send_parent = FALSE; break; } if (send_self) { /* If we haven't already sent this path, send it (and then remember that we sent it). */ if (! apr_hash_get(sent, path, APR_HASH_KEY_STRING)) { svn_node_kind_t kind; SVN_ERR(svn_fs_check_path(&kind, root, path, subpool)); SVN_ERR(send_response(repos, root, path, kind == svn_node_dir ? TRUE : FALSE, output, bb, subpool)); apr_hash_set(sent, path, APR_HASH_KEY_STRING, (void *)1); } } if (send_parent) { /* If it hasn't already been sent, send the parent directory (and then remember that you sent it). Allocate parent in pool, not subpool, because it stays in the sent hash afterwards. */ const char *parent = svn_path_dirname(path, pool); if (! apr_hash_get(sent, parent, APR_HASH_KEY_STRING)) { SVN_ERR(send_response(repos, root, parent, TRUE, output, bb, subpool)); apr_hash_set(sent, parent, APR_HASH_KEY_STRING, (void *)1); } } } svn_pool_destroy(subpool); return SVN_NO_ERROR; } /* ------------------------------------------------------------------------- PUBLIC FUNCTIONS */ dav_error * dav_svn__merge_response(ap_filter_t *output, const dav_svn_repos *repos, svn_revnum_t new_rev, char *post_commit_err, apr_xml_elem *prop_elem, svn_boolean_t disable_merge_response, apr_pool_t *pool) { apr_bucket_brigade *bb; svn_fs_root_t *root; svn_error_t *serr; const char *vcc; const char *rev; svn_string_t *creationdate, *creator_displayname; const char *post_commit_err_elem = NULL, *post_commit_header_info = NULL; serr = svn_fs_revision_root(&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.", repos->pool); } 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 */ rev = apr_psprintf(pool, "%ld", new_rev); /* get the post-commit hook stderr, if any */ if (post_commit_err) { post_commit_header_info = apr_psprintf(pool, " xmlns:S=\"%s\"", SVN_XML_NAMESPACE); post_commit_err_elem = apr_psprintf(pool, "%s" "", post_commit_err); } else { post_commit_header_info = "" ; post_commit_err_elem = "" ; } /* 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", repos->pool); } 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", repos->pool); } (void) ap_fputstrs(output, bb, DAV_XML_HEADER DEBUG_CR "" DEBUG_CR "" DEBUG_CR /* generate a response for the new baseline */ "" DEBUG_CR "", apr_xml_quote_string(pool, vcc, 1), "" DEBUG_CR "" 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. */ "" DEBUG_CR, post_commit_err_elem, DEBUG_CR "", rev, "" DEBUG_CR, NULL); if (creationdate) { (void) ap_fputstrs(output, bb, "", apr_xml_quote_string(pool, creationdate->data, 1), "" DEBUG_CR, NULL); } if (creator_displayname) { (void) ap_fputstrs(output, bb, "", apr_xml_quote_string(pool, creator_displayname->data, 1), "" DEBUG_CR, NULL); } (void) ap_fputstrs(output, bb, "" DEBUG_CR "HTTP/1.1 200 OK" DEBUG_CR "" DEBUG_CR "" DEBUG_CR, NULL); /* ONLY have dir_delta drive the editor if the caller asked us to generate a full MERGE response. svn clients can ask us to suppress this walk by sending specific request headers. */ if (! disable_merge_response) { /* 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 */ /* and go make me proud, boy! */ serr = do_resources(repos, root, new_rev, output, bb, pool); if (serr != NULL) { return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Error constructing resource list.", repos->pool); } } /* wrap up the merge response */ (void) ap_fputs(output, bb, "" DEBUG_CR "" DEBUG_CR); /* send whatever is left in the brigade */ (void) ap_pass_brigade(output, bb); return SVN_NO_ERROR; }