/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2005 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.0 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_0.txt. | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Alan Knowles | | Wez Furlong | | Luca Furini | +----------------------------------------------------------------------+ */ /* $Id: svn.c,v 1.22 2006/03/20 05:59:38 alan_k Exp $ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "php_svn.h" #include "svn_pools.h" #include "svn_sorts.h" #include "svn_config.h" #include "svn_auth.h" #include "svn_path.h" #include "svn_fs.h" #include "svn_repos.h" #include "svn_utf.h" /* If you declare any globals in php_svn.h uncomment this: */ ZEND_DECLARE_MODULE_GLOBALS(svn) /* custom property for ignoring SSL cert verification errors */ #define PHP_SVN_AUTH_PARAM_IGNORE_SSL_VERIFY_ERRORS "php:svn:auth:ignore-ssl-verify-errors" static void php_svn_get_version(char *buf, int buflen); /* True global resources - no need for thread safety here */ struct php_svn_repos { long rsrc_id; apr_pool_t *pool; svn_repos_t *repos; }; struct php_svn_fs { struct php_svn_repos *repos; svn_fs_t *fs; }; struct php_svn_fs_root { struct php_svn_repos *repos; svn_fs_root_t *root; }; struct php_svn_repos_fs_txn { struct php_svn_repos *repos; svn_fs_txn_t *txn; }; static int le_svn_repos; static int le_svn_fs; static int le_svn_fs_root; static int le_svn_repos_fs_txn; static ZEND_RSRC_DTOR_FUNC(php_svn_repos_dtor) { struct php_svn_repos *r = rsrc->ptr; svn_pool_destroy(r->pool); efree(r); } static ZEND_RSRC_DTOR_FUNC(php_svn_fs_dtor) { struct php_svn_fs *r = rsrc->ptr; zend_list_delete(r->repos->rsrc_id); efree(r); } static ZEND_RSRC_DTOR_FUNC(php_svn_fs_root_dtor) { struct php_svn_fs_root *r = rsrc->ptr; zend_list_delete(r->repos->rsrc_id); efree(r); } static ZEND_RSRC_DTOR_FUNC(php_svn_repos_fs_txn_dtor) { struct php_svn_repos_fs_txn *r = rsrc->ptr; zend_list_delete(r->repos->rsrc_id); efree(r); } /* {{{ svn_functions[] */ function_entry svn_functions[] = { PHP_FE(svn_checkout, NULL) PHP_FE(svn_cat, NULL) PHP_FE(svn_ls, NULL) PHP_FE(svn_log, NULL) PHP_FE(svn_auth_set_parameter, NULL) PHP_FE(svn_auth_get_parameter, NULL) PHP_FE(svn_client_version, NULL) PHP_FE(svn_diff, NULL) PHP_FE(svn_cleanup, NULL) PHP_FE(svn_commit, NULL) PHP_FE(svn_add, NULL) PHP_FE(svn_status, NULL) PHP_FE(svn_update, NULL) PHP_FE(svn_import, NULL) PHP_FE(svn_repos_create, NULL) PHP_FE(svn_repos_recover, NULL) PHP_FE(svn_repos_hotcopy, NULL) PHP_FE(svn_repos_open, NULL) PHP_FE(svn_repos_fs, NULL) PHP_FE(svn_repos_fs_begin_txn_for_commit, NULL) PHP_FE(svn_repos_fs_commit_txn, NULL) PHP_FE(svn_fs_revision_root, NULL) PHP_FE(svn_fs_check_path, NULL) PHP_FE(svn_fs_revision_prop, NULL) PHP_FE(svn_fs_dir_entries, NULL) PHP_FE(svn_fs_node_created_rev, NULL) PHP_FE(svn_fs_youngest_rev, NULL) PHP_FE(svn_fs_file_contents, NULL) PHP_FE(svn_fs_file_length, NULL) PHP_FE(svn_fs_txn_root, NULL) PHP_FE(svn_fs_make_file, NULL) PHP_FE(svn_fs_make_dir, NULL) PHP_FE(svn_fs_apply_text, NULL) PHP_FE(svn_fs_copy, NULL) PHP_FE(svn_fs_delete, NULL) PHP_FE(svn_fs_begin_txn2, NULL) PHP_FE(svn_fs_is_dir, NULL) PHP_FE(svn_fs_is_file, NULL) PHP_FE(svn_fs_node_prop, NULL) PHP_FE(svn_fs_change_node_prop, NULL) PHP_FE(svn_fs_contents_changed, NULL) PHP_FE(svn_fs_props_changed, NULL) PHP_FE(svn_fs_abort_txn, NULL) {NULL, NULL, NULL} }; /* }}} */ /* {{{ svn_module_entry */ zend_module_entry svn_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "svn", svn_functions, PHP_MINIT(svn), NULL, NULL, PHP_RSHUTDOWN(svn), PHP_MINFO(svn), #if ZEND_MODULE_API_NO >= 20010901 "0.1", /* Replace with version number for your extension */ #endif STANDARD_MODULE_PROPERTIES }; /* }}} */ #ifdef COMPILE_DL_SVN ZEND_GET_MODULE(svn) #endif /* {{{ PHP_INI */ /* Remove comments and fill if you need to have entries in php.ini PHP_INI_BEGIN() STD_PHP_INI_ENTRY("svn.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_svn_globals, svn_globals) STD_PHP_INI_ENTRY("svn.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_svn_globals, svn_globals) PHP_INI_END() */ /* }}} */ #include "ext/standard/php_smart_str.h" static void php_svn_handle_error(svn_error_t *error TSRMLS_DC) { svn_error_t *itr = error; smart_str s = {0,0,0}; smart_str_appendl(&s, "svn error(s) occured\n", sizeof("svn error(s) occured\n")-1); while (itr) { char buf[256]; smart_str_append_long(&s, itr->apr_err); smart_str_appendl(&s, " (", 2); svn_strerror(itr->apr_err, buf, sizeof(buf)); smart_str_appendl(&s, buf, strlen(buf)); smart_str_appendl(&s, ") ", 2); smart_str_appendl(&s, itr->message, strlen(itr->message)); if (itr->child) { smart_str_appendl(&s, "\n", 1); } itr = itr->child; } smart_str_appendl(&s, "\n", 1); smart_str_0(&s); php_error_docref(NULL TSRMLS_CC, E_WARNING, s.c); smart_str_free(&s); } static svn_error_t *php_svn_auth_ssl_client_server_trust_prompter( svn_auth_cred_ssl_server_trust_t **cred, void *baton, const char *realm, apr_uint32_t failures, const svn_auth_ssl_server_cert_info_t *cert_info, svn_boolean_t may_save, apr_pool_t *pool) { const char *ignore; TSRMLS_FETCH(); ignore = (const char*)svn_auth_get_parameter(SVN_G(ctx)->auth_baton, PHP_SVN_AUTH_PARAM_IGNORE_SSL_VERIFY_ERRORS); if (ignore && atoi(ignore)) { *cred = apr_palloc(SVN_G(pool), sizeof(**cred)); (*cred)->may_save = 0; (*cred)->accepted_failures = failures; } return SVN_NO_ERROR; } static void php_svn_init_globals(zend_svn_globals *g) { memset(g, 0, sizeof(*g)); } static svn_error_t *php_svn_get_commit_log(const char **log_msg, const char **tmp_file, apr_array_header_t *commit_items, void *baton, apr_pool_t *pool) { *log_msg = (const char*)baton; *tmp_file = NULL; return SVN_NO_ERROR; } static void init_svn_client(TSRMLS_D) { svn_error_t *err; svn_boolean_t store_password_val = TRUE; svn_auth_provider_object_t *provider; svn_auth_baton_t *ab; apr_array_header_t *providers; if (SVN_G(pool)) return; SVN_G(pool) = svn_pool_create(NULL); if ((err = svn_client_create_context (&SVN_G(ctx), SVN_G(pool)))) { php_svn_handle_error(err TSRMLS_CC); return; } if ((err = svn_config_get_config(&SVN_G(ctx)->config, NULL, SVN_G(pool)))) { php_svn_handle_error(err TSRMLS_CC); return; } SVN_G(ctx)->log_msg_func = php_svn_get_commit_log; /* The whole list of registered providers */ providers = apr_array_make (SVN_G(pool), 10, sizeof (svn_auth_provider_object_t *)); /* The main disk-caching auth providers, for both 'username/password' creds and 'username' creds. */ svn_client_get_simple_provider (&provider, SVN_G(pool)); APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; svn_client_get_username_provider (&provider, SVN_G(pool)); APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; svn_client_get_ssl_server_trust_prompt_provider (&provider, php_svn_auth_ssl_client_server_trust_prompter, NULL, SVN_G(pool)); APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; /* The server-cert, client-cert, and client-cert-password providers. */ svn_client_get_ssl_server_trust_file_provider (&provider, SVN_G(pool)); APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; svn_client_get_ssl_client_cert_file_provider (&provider, SVN_G(pool)); APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; svn_client_get_ssl_client_cert_pw_file_provider (&provider, SVN_G(pool)); APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider; /* skip prompt stuff */ svn_auth_open (&ab, providers, SVN_G(pool)); /* turn off prompting */ svn_auth_set_parameter(ab, SVN_AUTH_PARAM_NON_INTERACTIVE, ""); /* turn off storing passwords */ svn_auth_set_parameter(ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, ""); SVN_G(ctx)->auth_baton = ab; } PHP_FUNCTION(svn_auth_get_parameter) { char *key; int keylen; const char *value; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &keylen)) { return; } init_svn_client(TSRMLS_C); value = svn_auth_get_parameter(SVN_G(ctx)->auth_baton, key); if (value) { RETURN_STRING((char*)value, 1); } } PHP_FUNCTION(svn_auth_set_parameter) { char *key, *value; int keylen, valuelen; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &keylen, &value, &valuelen)) { return; } init_svn_client(TSRMLS_C); svn_auth_set_parameter(SVN_G(ctx)->auth_baton, apr_pstrdup(SVN_G(pool), key), apr_pstrdup(SVN_G(pool), value)); } PHP_FUNCTION(svn_import) { svn_client_commit_info_t *commit_info_p = NULL; char *path; int pathlen; char *url; int urllen; svn_boolean_t nonrecursive; svn_error_t *err; apr_pool_t *subpool; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssb", &path, &pathlen, &url, &urllen, &nonrecursive)) { RETURN_FALSE; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_client_import(&commit_info_p, path, url, nonrecursive, SVN_G(ctx), subpool); if (err) { php_svn_handle_error (err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_TRUE; } svn_pool_destroy(subpool); } /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(svn) { apr_initialize(); ZEND_INIT_MODULE_GLOBALS(svn, php_svn_init_globals, NULL); #define STRING_CONST(foo) REGISTER_STRING_CONSTANT(#foo, foo, CONST_CS|CONST_PERSISTENT) #define LONG_CONST(foo) REGISTER_LONG_CONSTANT(#foo, foo, CONST_CS|CONST_PERSISTENT) STRING_CONST(SVN_AUTH_PARAM_DEFAULT_USERNAME); STRING_CONST(SVN_AUTH_PARAM_DEFAULT_PASSWORD); STRING_CONST(SVN_AUTH_PARAM_NON_INTERACTIVE); STRING_CONST(SVN_AUTH_PARAM_DONT_STORE_PASSWORDS); STRING_CONST(SVN_AUTH_PARAM_NO_AUTH_CACHE); STRING_CONST(SVN_AUTH_PARAM_SSL_SERVER_FAILURES); STRING_CONST(SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO); STRING_CONST(SVN_AUTH_PARAM_CONFIG); STRING_CONST(SVN_AUTH_PARAM_SERVER_GROUP); STRING_CONST(SVN_AUTH_PARAM_CONFIG_DIR); STRING_CONST(PHP_SVN_AUTH_PARAM_IGNORE_SSL_VERIFY_ERRORS); STRING_CONST(SVN_FS_CONFIG_FS_TYPE); STRING_CONST(SVN_FS_TYPE_BDB); STRING_CONST(SVN_FS_TYPE_FSFS); STRING_CONST(SVN_PROP_REVISION_DATE); STRING_CONST(SVN_PROP_REVISION_ORIG_DATE); STRING_CONST(SVN_PROP_REVISION_AUTHOR); STRING_CONST(SVN_PROP_REVISION_LOG); LONG_CONST(svn_wc_status_none); LONG_CONST(svn_wc_status_unversioned); LONG_CONST(svn_wc_status_normal); LONG_CONST(svn_wc_status_added); LONG_CONST(svn_wc_status_missing); LONG_CONST(svn_wc_status_deleted); LONG_CONST(svn_wc_status_replaced); LONG_CONST(svn_wc_status_modified); LONG_CONST(svn_wc_status_merged); LONG_CONST(svn_wc_status_conflicted); LONG_CONST(svn_wc_status_ignored); LONG_CONST(svn_wc_status_obstructed); LONG_CONST(svn_wc_status_external); LONG_CONST(svn_wc_status_incomplete); LONG_CONST(svn_node_none); LONG_CONST(svn_node_file); LONG_CONST(svn_node_dir); LONG_CONST(svn_node_unknown); /* this is probably temporary until we sort out a proper revision parser. */ REGISTER_LONG_CONSTANT("SVN_REVISON_HEAD", -1, CONST_CS|CONST_PERSISTENT); le_svn_repos = zend_register_list_destructors_ex(php_svn_repos_dtor, NULL, "svn-repos", module_number); le_svn_fs = zend_register_list_destructors_ex(php_svn_fs_dtor, NULL, "svn-fs", module_number); le_svn_fs_root = zend_register_list_destructors_ex(php_svn_fs_root_dtor, NULL, "svn-fs-root", module_number); le_svn_repos_fs_txn = zend_register_list_destructors_ex(php_svn_repos_fs_txn_dtor, NULL, "svn-repos-fs-txn", module_number); return SUCCESS; } /* }}} */ /* {{{ PHP_RSHUTDOWN_FUNCTION */ PHP_RSHUTDOWN_FUNCTION(svn) { if (SVN_G(pool)) { svn_pool_destroy(SVN_G(pool)); SVN_G(pool) = NULL; } return SUCCESS; } /* }}} */ /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(svn) { char vstr[128]; php_info_print_table_start(); php_info_print_table_header(2, "svn support", "enabled"); php_svn_get_version(vstr, sizeof(vstr)); php_info_print_table_row(2, "svn client version", vstr); php_info_print_table_end(); /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */ } /* }}} */ /* reference http://www.linuxdevcenter.com/pub/a/linux/2003/04/24/libsvn1.html */ /* {{{ proto bool svn_checkout(string repos, string targetpath [, int revision]) Checks out a particular revision from repos into targetpath */ PHP_FUNCTION(svn_checkout) { char *repos_url = NULL, *target_path = NULL; int repos_url_len, target_path_len; svn_error_t *err; svn_opt_revision_t revision = { 0 }; long revno = -1; apr_pool_t *subpool; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &repos_url, &repos_url_len, &target_path, &target_path_len, &revno) == FAILURE) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } if (revno > 0) { revision.kind = svn_opt_revision_number; revision.value.number = revno; } else { revision.kind = svn_opt_revision_head; } err = svn_client_checkout (NULL, repos_url, target_path, &revision, TRUE, /* yes, we want to recurse into the URL */ SVN_G(ctx), subpool); if (err) { php_svn_handle_error (err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_TRUE; } svn_pool_destroy(subpool); } /* }}} */ PHP_FUNCTION(svn_cat) { char *repos_url = NULL; int repos_url_len, revision_no = -1, size; svn_error_t *err; svn_opt_revision_t revision = { 0 }; svn_stream_t *out = NULL; svn_stringbuf_t *buf = NULL; char *retdata =NULL; apr_pool_t *subpool; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &repos_url, &repos_url_len, &revision_no) == FAILURE) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } RETVAL_FALSE; if (revision_no <= 0) { revision.kind = svn_opt_revision_head; } else { revision.kind = svn_opt_revision_number; revision.value.number = revision_no ; } buf = svn_stringbuf_create("", subpool); if (!buf) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to allocate stringbuf"); goto cleanup; } out = svn_stream_from_stringbuf(buf, subpool); if (!out) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create svn stream"); goto cleanup; } err = svn_client_cat(out, repos_url, &revision, SVN_G(ctx), subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); goto cleanup; } retdata = emalloc(buf->len + 1); size = buf->len; err = svn_stream_read(out, retdata, &size); if (err) { php_svn_handle_error(err TSRMLS_CC); goto cleanup; } retdata[size] = '\0'; RETURN_STRINGL(retdata, size, 0); retdata = NULL; cleanup: svn_pool_destroy(subpool); if (retdata) efree(retdata); } PHP_FUNCTION(svn_ls) { char *repos_url = NULL; int repos_url_len, size, revision_no = -1; svn_error_t *err; svn_opt_revision_t revision = { 0 }; svn_stream_t *out; svn_stringbuf_t *buf; char *retdata =NULL; apr_hash_t *dirents; apr_array_header_t *array; int i; apr_pool_t *subpool; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &repos_url, &repos_url_len, &revision_no) == FAILURE) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } RETVAL_FALSE; if (revision_no <= 0) { revision.kind = svn_opt_revision_head; } else { revision.kind = svn_opt_revision_number; revision.value.number = revision_no ; } /* grab the most recent version of the website. */ err = svn_client_ls (&dirents, repos_url, &revision, FALSE, SVN_G(ctx), subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); goto cleanup; } array = svn_sort__hash (dirents, svn_sort_compare_items_as_paths, subpool); array_init(return_value); for (i = 0; i < array->nelts; ++i) { const char *utf8_entryname; svn_dirent_t *dirent; svn_sort__item_t *item; apr_time_t now = apr_time_now(); apr_time_exp_t exp_time; apr_status_t apr_err; apr_size_t size; char timestr[20]; const char *utf8_timestr; zval *row; item = &APR_ARRAY_IDX (array, i, svn_sort__item_t); utf8_entryname = item->key; dirent = apr_hash_get (dirents, utf8_entryname, item->klen); /* svn_time_to_human_cstring gives us something *way* too long to use for this, so we have to roll our own. We include the year if the entry's time is not within half a year. */ apr_time_exp_lt (&exp_time, dirent->time); if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2) && apr_time_sec(dirent->time - now) < (365 * 86400 / 2)) { apr_err = apr_strftime (timestr, &size, sizeof (timestr), "%b %d %H:%M", &exp_time); } else { apr_err = apr_strftime (timestr, &size, sizeof (timestr), "%b %d %Y", &exp_time); } /* if that failed, just zero out the string and print nothing */ if (apr_err) timestr[0] = '\0'; /* we need it in UTF-8. */ svn_utf_cstring_to_utf8 (&utf8_timestr, timestr, subpool); MAKE_STD_ZVAL(row); array_init(row); add_assoc_long(row, "created_rev", (long) dirent->created_rev); add_assoc_string(row, "last_author", dirent->last_author ? (char *) dirent->last_author : " ? ", 1); add_assoc_long(row, "size", dirent->size); add_assoc_string(row, "time", timestr,1); add_assoc_long(row, "time_t", apr_time_sec(dirent->time)); /* this doesnt have a matching struct name */ add_assoc_string(row, "name", (char *) utf8_entryname,1); /* should this be a integer or something? - not very clear though.*/ add_assoc_string(row, "type", (dirent->kind == svn_node_dir) ? "dir" : "file",1); add_next_index_zval(return_value,row); } cleanup: svn_pool_destroy(subpool); } static svn_error_t * php_svn_log_message_receiver ( void *baton, apr_hash_t *changed_paths, svn_revnum_t rev, const char *author, const char *date, const char *msg, apr_pool_t *pool) { zval *return_value = (zval *)baton, *row, *paths; char *path; apr_hash_index_t *hi; apr_array_header_t *sorted_paths; int i; TSRMLS_FETCH(); if (rev == 0) { return SVN_NO_ERROR; } MAKE_STD_ZVAL(row); array_init(row); add_assoc_long(row, "rev", (long) rev); if (author) { add_assoc_string(row, "author", (char *) author, 1); } if (msg) { add_assoc_string(row, "msg", (char *) msg, 1); } if (date) { add_assoc_string(row, "date", (char *) date, 1); } if (!changed_paths) { add_next_index_zval(return_value, row); return SVN_NO_ERROR; } MAKE_STD_ZVAL(paths); array_init(paths); sorted_paths = svn_sort__hash(changed_paths, svn_sort_compare_items_as_paths, pool); for (i = 0; i < sorted_paths->nelts; i++) { svn_sort__item_t *item; svn_log_changed_path_t *log_item; zval *zpaths; const char *path; MAKE_STD_ZVAL(zpaths); array_init(zpaths); item = &(APR_ARRAY_IDX (sorted_paths, i, svn_sort__item_t)); path = item->key; log_item = apr_hash_get (changed_paths, item->key, item->klen); add_assoc_stringl(zpaths, "action", &(log_item->action), 1,1); add_assoc_string(zpaths, "path", (char *) item->key, 1); if (log_item->copyfrom_path && SVN_IS_VALID_REVNUM (log_item->copyfrom_rev)) { add_assoc_string(zpaths, "copyfrom", (char *) log_item->copyfrom_path, 1); add_assoc_long(zpaths, "rev", (long) log_item->copyfrom_rev); } else { } add_next_index_zval(paths,zpaths); } add_assoc_zval(row,"paths",paths); add_next_index_zval(return_value, row); return SVN_NO_ERROR; } PHP_FUNCTION(svn_log) { const char *repos_url = NULL, *utf8_repos_url = NULL; int repos_url_len; int revision = -2; svn_error_t *err; svn_opt_revision_t start_revision = { 0 }, end_revision = { 0 }; char *retdata =NULL; int size; apr_array_header_t *targets; const char *target; int i; apr_pool_t *subpool; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &repos_url, &repos_url_len, &revision) == FAILURE) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } RETVAL_FALSE; svn_utf_cstring_to_utf8 (&utf8_repos_url, repos_url, subpool); if (revision < -1) { start_revision.kind = svn_opt_revision_head; end_revision.kind = svn_opt_revision_number; end_revision.value.number = 1 ; } else if (revision == -1) { start_revision.kind = svn_opt_revision_head; end_revision.kind = svn_opt_revision_head; } else { start_revision.kind = svn_opt_revision_number; start_revision.value.number = revision ; end_revision.kind = svn_opt_revision_number; end_revision.value.number = revision ; } targets = apr_array_make (subpool, 1, sizeof(char *)); APR_ARRAY_PUSH(targets, const char *) = svn_path_canonicalize(utf8_repos_url, subpool); array_init(return_value); err = svn_client_log( targets, &start_revision, &end_revision, 1, // svn_boolean_t discover_changed_paths, 1, // svn_boolean_t strict_node_history, php_svn_log_message_receiver, (void *) return_value, SVN_G(ctx), subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } svn_pool_destroy(subpool); } static size_t php_apr_file_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) { apr_file_t *thefile = (apr_file_t*)stream->abstract; apr_size_t nbytes = (apr_size_t)count; apr_file_write(thefile, buf, &nbytes); return (size_t)nbytes; } static size_t php_apr_file_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) { apr_file_t *thefile = (apr_file_t*)stream->abstract; apr_size_t nbytes = (apr_size_t)count; apr_file_read(thefile, buf, &nbytes); if (nbytes == 0) stream->eof = 1; return (size_t)nbytes; } static int php_apr_file_close(php_stream *stream, int close_handle TSRMLS_DC) { if (close_handle) { apr_file_close((apr_file_t*)stream->abstract); } return 0; } static int php_apr_file_flush(php_stream *stream TSRMLS_DC) { apr_file_flush((apr_file_t*)stream->abstract); return 0; } static int php_apr_file_seek(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC) { apr_file_t *thefile = (apr_file_t*)stream->abstract; apr_off_t off = (apr_off_t)offset; /* NB: apr_seek_where_t is defined using the standard SEEK_XXX whence values */ apr_file_seek(thefile, whence, &off); *newoffset = (off_t)off; return 0; } static php_stream_ops php_apr_stream_ops = { php_apr_file_write, php_apr_file_read, php_apr_file_close, php_apr_file_flush, "svn diff stream", php_apr_file_seek, NULL, /* cast */ NULL, /* stat */ NULL /* set_option */ }; /* {{{ proto mixed svn_diff(string path1, int rev1, string path2, int rev2) Recursively diffs two paths. Returns an array consisting of two streams: the first is the diff output and the second contains error stream output */ PHP_FUNCTION(svn_diff) { const char *tmp_dir; char outname[256], errname[256]; apr_pool_t *subpool; apr_file_t *outfile = NULL, *errfile = NULL; svn_error_t *err; char *path1, *path2; int path1len, path2len; long rev1 = -1, rev2 = -1; apr_array_header_t diff_options = { 0, 0, 0, 0, 0}; svn_opt_revision_t revision1, revision2; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl!sl!", &path1, &path1len, &rev1, &path2, &path2len, &rev2)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } RETVAL_FALSE; if (rev1 <= 0) { revision1.kind = svn_opt_revision_head; } else { revision1.kind = svn_opt_revision_number; revision1.value.number = rev1; } if (rev2 <= 0) { revision2.kind = svn_opt_revision_head; } else { revision2.kind = svn_opt_revision_number; revision2.value.number = rev2; } apr_temp_dir_get(&tmp_dir, subpool); sprintf(outname, "%s/phpsvnXXXXXX", tmp_dir); sprintf(errname, "%s/phpsvnXXXXXX", tmp_dir); /* use global pool, so stream lives after this function call */ apr_file_mktemp(&outfile, outname, APR_CREATE|APR_READ|APR_WRITE|APR_EXCL|APR_DELONCLOSE, SVN_G(pool)); /* use global pool, so stream lives after this function call */ apr_file_mktemp(&errfile, errname, APR_CREATE|APR_READ|APR_WRITE|APR_EXCL|APR_DELONCLOSE, SVN_G(pool)); err = svn_client_diff(&diff_options, path1, &revision1, path2, &revision2, 1, 0, 0, outfile, errfile, SVN_G(ctx), subpool); if (err) { apr_file_close(errfile); apr_file_close(outfile); php_svn_handle_error(err TSRMLS_CC); } else { zval *t; php_stream *stm = NULL; apr_off_t off = (apr_off_t)0; array_init(return_value); /* set the file pointer to the beginning of the file */ apr_file_seek(outfile, APR_SET, &off); apr_file_seek(errfile, APR_SET, &off); /* 'bless' the apr files into streams and return those */ stm = php_stream_alloc(&php_apr_stream_ops, outfile, 0, "rw"); MAKE_STD_ZVAL(t); php_stream_to_zval(stm, t); add_next_index_zval(return_value, t); stm = php_stream_alloc(&php_apr_stream_ops, errfile, 0, "rw"); MAKE_STD_ZVAL(t); php_stream_to_zval(stm, t); add_next_index_zval(return_value, t); } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto bool svn_cleanup(string workingdir) Recursively cleanup a working copy directory, finishing any incomplete operations, removing lockfiles, etc. */ PHP_FUNCTION(svn_cleanup) { char *workingdir; int len; svn_error_t *err; apr_pool_t *subpool; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &workingdir, &len)) { RETURN_FALSE; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_client_cleanup(workingdir, SVN_G(ctx), subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_TRUE; } svn_pool_destroy(subpool); } /* }}} */ static int replicate_hash(void *pDest, int num_args, va_list args, zend_hash_key *key) { zval **val = (zval **)pDest; apr_hash_t *hash = va_arg(args, apr_hash_t*); if (key->nKeyLength && Z_TYPE_PP(val) == IS_STRING) { /* apr doesn't want the NUL terminator in its keys */ apr_hash_set(hash, key->arKey, key->nKeyLength-1, Z_STRVAL_PP(val)); } va_end(args); return ZEND_HASH_APPLY_KEEP; } static apr_hash_t *replicate_zend_hash_to_apr_hash(zval *arr, apr_pool_t *pool TSRMLS_DC) { apr_hash_t *hash; if (!arr) return NULL; hash = apr_hash_make(pool); zend_hash_apply_with_arguments(Z_ARRVAL_P(arr), replicate_hash, 1, hash); return hash; } /* {{{ proto string svn_fs_revision_prop(resource fs, int revnum, string propname) Fetches the value of a named property */ PHP_FUNCTION(svn_fs_revision_prop) { zval *zfs; long revnum; struct php_svn_fs *fs; svn_error_t *err; svn_string_t *str; char *propname; int propnamelen; apr_pool_t *subpool; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rls", &zfs, &revnum, &propname, &propnamelen)) { return; } ZEND_FETCH_RESOURCE(fs, struct php_svn_fs *, &zfs, -1, "svn-fs", le_svn_fs); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_fs_revision_prop(&str, fs->fs, revnum, propname, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } RETVAL_STRINGL((char*)str->data, str->len, 1); svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto resource svn_fs_youngest_rev(resource fs) Returns the number of the youngest revision in the filesystem */ PHP_FUNCTION(svn_fs_youngest_rev) { zval *zfs; struct php_svn_fs *fs; svn_fs_root_t *root; svn_error_t *err; struct php_svn_fs_root *resource; svn_revnum_t revnum; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zfs)) { return; } ZEND_FETCH_RESOURCE(fs, struct php_svn_fs *, &zfs, -1, "svn-fs", le_svn_fs); err = svn_fs_youngest_rev(&revnum, fs->fs, fs->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } RETURN_LONG(revnum); } /* }}} */ /* {{{ proto resource svn_fs_revision_root(resource fs, int revnum) Get a handle on a specific version of the repository root */ PHP_FUNCTION(svn_fs_revision_root) { zval *zfs; long revnum; struct php_svn_fs *fs; svn_fs_root_t *root; svn_error_t *err; struct php_svn_fs_root *resource; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &zfs, &revnum)) { return; } ZEND_FETCH_RESOURCE(fs, struct php_svn_fs *, &zfs, -1, "svn-fs", le_svn_fs); err = svn_fs_revision_root(&root, fs->fs, revnum, fs->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } resource = emalloc(sizeof(*resource)); resource->root = root; resource->repos = fs->repos; zend_list_addref(fs->repos->rsrc_id); ZEND_REGISTER_RESOURCE(return_value, resource, le_svn_fs_root); } /* }}} */ /* {{{ proto resource svn_fs_file_contents(resource fsroot, string path) Returns a stream to access the contents of a file from a given version of the fs */ static size_t php_svn_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) { svn_stream_t *thefile = (svn_stream_t*)stream->abstract; apr_size_t nbytes = (apr_size_t)count; svn_stream_write(thefile, buf, &nbytes); return (size_t)nbytes; } static size_t php_svn_stream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) { svn_stream_t *thefile = (svn_stream_t*)stream->abstract; apr_size_t nbytes = (apr_size_t)count; svn_stream_read(thefile, buf, &nbytes); if (nbytes == 0) stream->eof = 1; return (size_t)nbytes; } static int php_svn_stream_close(php_stream *stream, int close_handle TSRMLS_DC) { if (close_handle) { svn_stream_close((svn_stream_t*)stream->abstract); } return 0; } static int php_svn_stream_flush(php_stream *stream TSRMLS_DC) { return 0; } static php_stream_ops php_svn_stream_ops = { php_svn_stream_write, php_svn_stream_read, php_svn_stream_close, php_svn_stream_flush, "svn content stream", NULL, /* seek */ NULL, /* cast */ NULL, /* stat */ NULL /* set_option */ }; PHP_FUNCTION(svn_fs_file_contents) { zval *zfsroot; struct php_svn_fs_root *fsroot; char *path; int pathlen; svn_filesize_t len; svn_error_t *err; apr_pool_t *subpool; svn_stream_t *svnstm; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zfsroot, &path, &pathlen)) { return; } ZEND_FETCH_RESOURCE(fsroot, struct php_svn_fs_root*, &zfsroot, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_file_contents(&svnstm, fsroot->root, path, SVN_G(pool)); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { php_stream *stm; stm = php_stream_alloc(&php_svn_stream_ops, svnstm, 0, "r"); php_stream_to_zval(stm, return_value); } } /* }}} */ /* {{{ proto int svn_fs_file_length(resource fsroot, string path) Returns the length of a file from a given version of the fs */ PHP_FUNCTION(svn_fs_file_length) { zval *zfsroot; struct php_svn_fs_root *fsroot; char *path; int pathlen; svn_filesize_t len; svn_error_t *err; apr_pool_t *subpool; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zfsroot, &path, &pathlen)) { return; } ZEND_FETCH_RESOURCE(fsroot, struct php_svn_fs_root*, &zfsroot, -1, "svn-fs-root", le_svn_fs_root); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_fs_file_length(&len, fsroot->root, path, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { /* TODO: 64 bit */ RETVAL_LONG(len); } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto long svn_fs_node_prop(resource fsroot, string path, string propname) Returns the value of a property for a node */ PHP_FUNCTION(svn_fs_node_prop) { zval *zfsroot; struct php_svn_fs_root *fsroot; char *path, *propname; int pathlen, propnamelen; svn_error_t *err; apr_pool_t *subpool; svn_revnum_t rev; svn_string_t *val; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rss", &zfsroot, &path, &pathlen, &propname, &propnamelen)) { return; } ZEND_FETCH_RESOURCE(fsroot, struct php_svn_fs_root*, &zfsroot, -1, "svn-fs-root", le_svn_fs_root); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_fs_node_prop(&val, fsroot->root, path, propname, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { if (val != NULL && val->data != NULL) { RETVAL_STRINGL((char *)val->data, val->len, 1); } else { RETVAL_EMPTY_STRING(); } } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto long svn_fs_node_created_rev(resource fsroot, string path) Returns the revision in which path under fsroot was created */ PHP_FUNCTION(svn_fs_node_created_rev) { zval *zfsroot; struct php_svn_fs_root *fsroot; char *path; int pathlen; svn_error_t *err; apr_pool_t *subpool; svn_revnum_t rev; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zfsroot, &path, &pathlen)) { return; } ZEND_FETCH_RESOURCE(fsroot, struct php_svn_fs_root*, &zfsroot, -1, "svn-fs-root", le_svn_fs_root); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_fs_node_created_rev(&rev, fsroot->root, path, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_LONG(rev); } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto array svn_fs_dir_entries(resource fsroot, string path) Enumerates the directory entries under path; returns a hash of dir names to file type */ PHP_FUNCTION(svn_fs_dir_entries) { zval *zfsroot; struct php_svn_fs_root *fsroot; char *path; int pathlen; svn_error_t *err; apr_pool_t *subpool; apr_hash_t *hash; apr_hash_index_t *hi; union { void *vptr; svn_fs_dirent_t *ent; } pun; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zfsroot, &path, &pathlen)) { return; } ZEND_FETCH_RESOURCE(fsroot, struct php_svn_fs_root*, &zfsroot, -1, "svn-fs-root", le_svn_fs_root); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_fs_dir_entries(&hash, fsroot->root, path, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { array_init(return_value); for (hi = apr_hash_first(subpool, hash); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, NULL, &pun.vptr); add_assoc_long(return_value, (char*)pun.ent->name, pun.ent->kind); } } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto int svn_fs_check_path(resource fsroot, string path) Determines what kind of item lives at path in a given repository fsroot */ PHP_FUNCTION(svn_fs_check_path) { zval *zfsroot; svn_node_kind_t kind; struct php_svn_fs_root *fsroot; char *path; int pathlen; svn_error_t *err; apr_pool_t *subpool; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zfsroot, &path, &pathlen)) { return; } ZEND_FETCH_RESOURCE(fsroot, struct php_svn_fs_root*, &zfsroot, -1, "svn-fs-root", le_svn_fs_root); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_fs_check_path(&kind, fsroot->root, path, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_LONG(kind); } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto resource svn_repos_fs(resource repos) Gets a handle on the filesystem for a repository */ PHP_FUNCTION(svn_repos_fs) { struct php_svn_repos *repos = NULL; struct php_svn_fs *resource = NULL; zval *zrepos; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zrepos)) { return; } ZEND_FETCH_RESOURCE(repos, struct php_svn_repos *, &zrepos, -1, "svn-repos", le_svn_repos); resource = emalloc(sizeof(*resource)); resource->repos = repos; zend_list_addref(repos->rsrc_id); resource->fs = svn_repos_fs(repos->repos); ZEND_REGISTER_RESOURCE(return_value, resource, le_svn_fs); } /* }}} */ /* {{{ proto resource svn_repos_open(string path) Open a shared lock on a repository. */ PHP_FUNCTION(svn_repos_open) { char *path; int pathlen; apr_pool_t *subpool; svn_error_t *err; svn_repos_t *repos = NULL; struct php_svn_repos *resource = NULL; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &path, &pathlen)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_repos_open(&repos, path, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); } if (repos) { resource = emalloc(sizeof(*resource)); resource->pool = subpool; resource->repos = repos; ZEND_REGISTER_RESOURCE(return_value, resource, le_svn_repos); } else { svn_pool_destroy(subpool); RETURN_FALSE; } } /* }}} */ /* {{{ proto resource svn_repos_create(string path [, array config [, array fsconfig]]) Create a new subversion repository at path */ PHP_FUNCTION(svn_repos_create) { char *path; int pathlen; zval *config = NULL; zval *fsconfig = NULL; apr_hash_t *config_hash = NULL; apr_hash_t *fsconfig_hash = NULL; apr_pool_t *subpool; svn_error_t *err; svn_repos_t *repos = NULL; struct php_svn_repos *resource = NULL; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a!a!", &path, &pathlen, &config, &fsconfig)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } config_hash = replicate_zend_hash_to_apr_hash(config, subpool TSRMLS_CC); fsconfig_hash = replicate_zend_hash_to_apr_hash(fsconfig, subpool TSRMLS_CC); err = svn_repos_create(&repos, path, NULL, NULL, config_hash, fsconfig_hash, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); } if (repos) { resource = emalloc(sizeof(*resource)); resource->pool = subpool; resource->repos = repos; ZEND_REGISTER_RESOURCE(return_value, resource, le_svn_repos); } else { svn_pool_destroy(subpool); RETURN_FALSE; } } /* }}} */ /* {{{ proto bool svn_repos_recover(string path) Run recovery procedures on the repository located at path. */ PHP_FUNCTION(svn_repos_recover) { char *path; int pathlen; apr_pool_t *subpool; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &path, &pathlen)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_repos_recover2(path, 0, NULL, NULL, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_TRUE; } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto bool svn_repos_hotcopy(string repospath, string destpath, bool cleanlogs) Make a hot-copy of the repos at repospath; copy it to destpath */ PHP_FUNCTION(svn_repos_hotcopy) { char *path, *dest; int pathlen, destlen; zend_bool cleanlogs; apr_pool_t *subpool; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssb", &path, &pathlen, &dest, &destlen, &cleanlogs)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_repos_hotcopy(path, dest, cleanlogs, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_TRUE; } svn_pool_destroy(subpool); } /* }}} */ static int replicate_array(void *pDest, int num_args, va_list args, zend_hash_key *key) { zval **val = (zval **)pDest; apr_pool_t *pool = (apr_pool_t*)va_arg(args, apr_pool_t*); apr_array_header_t *arr = (apr_array_header_t*)va_arg(args, apr_array_header_t*); if (Z_TYPE_PP(val) == IS_STRING) { APR_ARRAY_PUSH(arr, const char*) = apr_pstrdup(pool, Z_STRVAL_PP(val)); } va_end(args); return ZEND_HASH_APPLY_KEEP; } static apr_array_header_t *replicate_zend_hash_to_apr_array(zval *arr, apr_pool_t *pool TSRMLS_DC) { apr_array_header_t *apr_arr = apr_array_make(pool, zend_hash_num_elements(Z_ARRVAL_P(arr)), sizeof(const char*)); if (!apr_arr) return NULL; zend_hash_apply_with_arguments(Z_ARRVAL_P(arr), replicate_array, 2, pool, apr_arr); return apr_arr; } /* {{{ proto array svn_commit(string log, array targets [, bool dontrecurse]) Make a hot-copy of the repos at repospath; copy it to destpath */ PHP_FUNCTION(svn_commit) { char *log; int loglen; zend_bool dontrecurse = 0; apr_pool_t *subpool; svn_error_t *err; svn_client_commit_info_t *info = NULL; zval *targets = NULL; apr_array_header_t *targets_array; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|b", &log, &loglen, &targets, &dontrecurse)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } SVN_G(ctx)->log_msg_baton = log; targets_array = replicate_zend_hash_to_apr_array(targets, subpool TSRMLS_CC); err = svn_client_commit(&info, targets_array, dontrecurse, SVN_G(ctx), subpool); SVN_G(ctx)->log_msg_baton = NULL; if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else if (info) { array_init(return_value); add_next_index_long(return_value, info->revision); add_next_index_string(return_value, (char*)info->date, 1); add_next_index_string(return_value, (char*)info->author, 1); } else { php_error_docref(NULL TSRMLS_CC, E_WARNING, "commit didn't return any info"); RETVAL_FALSE; } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto array svn_add(string path [, bool recursive [, bool force]]) Schedule the addition of a file in a working directory */ PHP_FUNCTION(svn_add) { char *path; int pathlen; zend_bool recurse = 1, force = 0; apr_pool_t *subpool; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|bb", &path, &pathlen, &recurse, &force)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_client_add2((const char*)path, recurse, force, SVN_G(ctx), subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_TRUE; } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto array svn_status(string path [, bool recursive [, bool get_all [, bool update [, bool no_ignore]]]]) Schedule the addition of a file in a working directory */ static void status_func(void *baton, const char *path, svn_wc_status_t *status) { zval *return_value = (zval*)baton; zval *entry; TSRMLS_FETCH(); MAKE_STD_ZVAL(entry); array_init(entry); add_assoc_string(entry, "path", (char*)path, 1); if (status) { add_assoc_long(entry, "text_status", status->text_status); add_assoc_long(entry, "repos_text_status", status->repos_text_status); add_assoc_long(entry, "prop_status", status->prop_status); add_assoc_long(entry, "repos_prop_status", status->repos_prop_status); if (status->locked) add_assoc_bool(entry, "locked", status->locked); if (status->copied) add_assoc_bool(entry, "copied", status->copied); if (status->switched) add_assoc_bool(entry, "switched", status->switched); if (status->entry) { if (status->entry->name) { add_assoc_string(entry, "name", (char*)status->entry->name, 1); } if (status->entry->url) { add_assoc_string(entry, "url", (char*)status->entry->url, 1); } if (status->entry->repos) { add_assoc_string(entry, "repos", (char*)status->entry->repos, 1); } add_assoc_long(entry, "revision", status->entry->revision); add_assoc_long(entry, "kind", status->entry->kind); add_assoc_long(entry, "schedule", status->entry->schedule); if (status->entry->deleted) add_assoc_bool(entry, "deleted", status->entry->deleted); if (status->entry->absent) add_assoc_bool(entry, "absent", status->entry->absent); if (status->entry->incomplete) add_assoc_bool(entry, "incomplete", status->entry->incomplete); if (status->entry->copyfrom_url) { add_assoc_string(entry, "copyfrom_url", (char*)status->entry->copyfrom_url, 1); add_assoc_long(entry, "copyfrom_rev", status->entry->copyfrom_rev); } if (status->entry->cmt_author) { add_assoc_long(entry, "cmt_date", apr_time_sec(status->entry->cmt_date)); add_assoc_long(entry, "cmt_rev", status->entry->cmt_rev); add_assoc_string(entry, "cmt_author", (char*)status->entry->cmt_author, 1); } if (status->entry->prop_time) { add_assoc_long(entry, "prop_time", apr_time_sec(status->entry->prop_time)); } if (status->entry->text_time) { add_assoc_long(entry, "text_time", apr_time_sec(status->entry->text_time)); } } } add_next_index_zval(return_value, entry); } PHP_FUNCTION(svn_status) { char *path; int pathlen; zend_bool recurse = 1, get_all = 0, update = 0, no_ignore = 0; apr_pool_t *subpool; svn_error_t *err; svn_revnum_t result_rev; svn_opt_revision_t rev; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|bbbb", &path, &pathlen, &recurse, &get_all, &update, &no_ignore)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } array_init(return_value); rev.kind = svn_opt_revision_head; err = svn_client_status(&result_rev, path, &rev, status_func, return_value, recurse, get_all, update, no_ignore, SVN_G(ctx), subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } svn_pool_destroy(subpool); } /* }}} */ /* {{{ proto array svn_update(string path [, int revno [, bool recurse]]) Update working copy at path to revno */ PHP_FUNCTION(svn_update) { char *path; int pathlen; zend_bool recurse = 1; apr_pool_t *subpool; svn_error_t *err; svn_revnum_t result_rev; svn_opt_revision_t rev; long revno = -1; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|lb", &path, &pathlen, &revno, &recurse)) { return; } init_svn_client(TSRMLS_C); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } if (revno > 0) { rev.kind = svn_opt_revision_number; rev.value.number = revno; } else { rev.kind = svn_opt_revision_head; } err = svn_client_update(&result_rev, path, &rev, recurse, SVN_G(ctx), subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETVAL_FALSE; } else { RETVAL_LONG(result_rev); } svn_pool_destroy(subpool); } /* }}} */ static void php_svn_get_version(char *buf, int buflen) { const svn_version_t *vers; vers = svn_client_version(); if (strlen(vers->tag)) snprintf(buf, buflen, "%d.%d.%d#%s", vers->major, vers->minor, vers->patch, vers->tag); else snprintf(buf, buflen, "%d.%d.%d", vers->major, vers->minor, vers->patch); } PHP_FUNCTION(svn_client_version) { char vers[128]; if (ZEND_NUM_ARGS()) { WRONG_PARAM_COUNT; } php_svn_get_version(vers, sizeof(vers)); RETURN_STRING(vers, 1); } /* {{{ proto resource svn_repos_fs_begin_txn_for_commit(resource repos, long rev, string author, string log_msg) create a new transaction */ PHP_FUNCTION(svn_repos_fs_begin_txn_for_commit) { svn_fs_txn_t *txn_p = NULL; struct php_svn_repos_fs_txn *new_txn = NULL; zval *zrepos; struct php_svn_repos *repos = NULL; svn_revnum_t rev; char *author; int author_len; char *log_msg; int log_msg_len; apr_pool_t *subpool; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlss", &zrepos, &rev, &author, &author_len, &log_msg, &log_msg_len)) { return; } ZEND_FETCH_RESOURCE(repos, struct php_svn_repos *, &zrepos, -1, "svn-repos", le_svn_repos); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_repos_fs_begin_txn_for_commit(&txn_p, repos->repos, rev, author, log_msg, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); } if (txn_p) { new_txn = emalloc(sizeof(*new_txn)); new_txn->repos = repos; zend_list_addref(repos->rsrc_id); new_txn->txn = txn_p; ZEND_REGISTER_RESOURCE(return_value, new_txn, le_svn_repos_fs_txn); } else { // something went wrong } } /* }}} */ /* {{{ proto long svn_repos_fs_commit_txn(resource txn) commits a transaction and returns the new revision */ PHP_FUNCTION(svn_repos_fs_commit_txn) { zval *ztxn; struct php_svn_repos_fs_txn *txn; const char *conflicts; svn_revnum_t new_rev; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &ztxn)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(txn, struct php_svn_repos_fs_txn *, &ztxn, -1, "svn-repos-fs-txn", le_svn_repos_fs_txn); err = svn_repos_fs_commit_txn(&conflicts, txn->repos->repos, &new_rev, txn->txn, txn->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } RETURN_LONG(new_rev); } /* }}} */ /* {{{ proto resource svn_fs_txn_root(resource txn) creates and returns a transaction root */ PHP_FUNCTION(svn_fs_txn_root) { svn_fs_root_t *root_p = NULL; struct php_svn_fs_root *new_root = NULL; zval *ztxn; struct php_svn_repos_fs_txn *txn = NULL; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &ztxn)) { return; } ZEND_FETCH_RESOURCE(txn, struct php_svn_repos_fs_txn *, &ztxn, -1, "svn-fs-repos-txn", le_svn_repos_fs_txn); err = svn_fs_txn_root(&root_p, txn->txn, txn->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); } if (root_p) { new_root = emalloc(sizeof(*new_root)); new_root->repos = txn->repos; zend_list_addref(txn->repos->rsrc_id); new_root->root = root_p; ZEND_REGISTER_RESOURCE(return_value, new_root, le_svn_fs_root); } else { // something went wrong } } /* }}} */ /* {{{ proto boolean svn_fs_make_file(resource root, string path) creates a new empty file, returns true if all is ok, false otherwise */ PHP_FUNCTION(svn_fs_make_file) { zval *zroot; struct php_svn_fs_root *root = NULL; char *path; int path_len; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zroot, &path, &path_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root, struct php_svn_fs_root *, &zroot, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_make_file(root->root, path, root->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ proto boolean svn_fs_make_dir(resource root, string path) creates a new empty directory, returns true if all is ok, false otherwise*/ PHP_FUNCTION(svn_fs_make_dir) { zval *zroot; struct php_svn_fs_root *root = NULL; char *path; int path_len; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zroot, &path, &path_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root, struct php_svn_fs_root *, &zroot, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_make_dir(root->root, path, root->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ proto resource svn_fs_apply_text(resource root, string path) creates and returns a stream that will be used to replace the content of an existing file*/ PHP_FUNCTION(svn_fs_apply_text) { zval *zroot; struct php_svn_fs_root *root = NULL; char *path; int path_len; svn_error_t *err; svn_stream_t *stream_p = NULL; struct php_svn_stream *stream = NULL; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zroot, &path, &path_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root, struct php_svn_fs_root *, &zroot, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_apply_text(&stream_p, root->root, path, NULL, root->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } if (stream_p) { php_stream *stm; stm = php_stream_alloc(&php_svn_stream_ops, stream_p, 0, "w"); php_stream_to_zval(stm, return_value); } else { // something went wrong RETURN_FALSE; } } /* }}} */ /* {{{ proto boolean svn_fs_copy(resource from_root, string from_path, resourse to_root, string to_path) copies a file or a directory, returns true if all is ok, false otherwise */ PHP_FUNCTION(svn_fs_copy) { zval *zfrom_root, *zto_root; struct php_svn_fs_root *from_root, *to_root; char *from_path, *to_path; int from_path_len, to_path_len; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsrs", &zfrom_root, &from_path, &from_path_len, &zto_root, &to_path, &to_path_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(from_root, struct php_svn_fs_root *, &zfrom_root, -1, "svn-fs-root", le_svn_fs_root); ZEND_FETCH_RESOURCE(to_root, struct php_svn_fs_root *, &zto_root, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_copy(from_root->root, from_path, to_root->root, to_path, to_root->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ proto boolean svn_fs_delete(resource root, string path) deletes a file or a directory, return true if all is ok, false otherwise */ PHP_FUNCTION(svn_fs_delete) { zval *zroot; struct php_svn_fs_root *root = NULL; char *path; int path_len; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zroot, &path, &path_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root, struct php_svn_fs_root *, &zroot, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_delete(root->root, path, root->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ proto resource svn_fs_begin_txn2(resource repos, long rev) create a new transaction */ PHP_FUNCTION(svn_fs_begin_txn2) { svn_fs_txn_t *txn_p = NULL; struct php_svn_repos_fs_txn *new_txn = NULL; zval *zfs; struct php_svn_fs *fs = NULL; svn_revnum_t rev; apr_pool_t *subpool; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &zfs, &rev)) { return; } ZEND_FETCH_RESOURCE(fs, struct php_svn_fs *, &zfs, -1, "svn-fs", le_svn_fs); subpool = svn_pool_create(SVN_G(pool)); if (!subpool) { RETURN_FALSE; } err = svn_fs_begin_txn2(&txn_p, fs->fs, rev, 0, subpool); if (err) { php_svn_handle_error(err TSRMLS_CC); } if (txn_p) { new_txn = emalloc(sizeof(*new_txn)); new_txn->repos = fs->repos; zend_list_addref(fs->repos->rsrc_id); new_txn->txn = txn_p; ZEND_REGISTER_RESOURCE(return_value, new_txn, le_svn_repos_fs_txn); } else { // something went wrong } } /* }}} */ /* {{{ proto boolean svn_fs_is_file(resource root, string path) return true if the path points to a file, false otherwise */ PHP_FUNCTION(svn_fs_is_file) { zval *zroot; struct php_svn_fs_root *root = NULL; char *path; int path_len; svn_error_t *err; int is_file; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zroot, &path, &path_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root, struct php_svn_fs_root *, &zroot, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_is_file(&is_file, root->root, path, root->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { RETURN_BOOL(is_file); } } /* }}} */ /* {{{ proto boolean svn_fs_is_dir(resource root, string path) return true if the path points to a directory, false otherwise */ PHP_FUNCTION(svn_fs_is_dir) { zval *zroot; struct php_svn_fs_root *root = NULL; char *path; int path_len; svn_error_t *err; int is_dir; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &zroot, &path, &path_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root, struct php_svn_fs_root *, &zroot, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_is_dir(&is_dir, root->root, path, root->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { RETURN_BOOL(is_dir); } } /* }}} */ /* {{{ proto boolean svn_fs_change_node_prop(resource root, string path, string name, string value) return true if everything is ok, false otherwise */ PHP_FUNCTION(svn_fs_change_node_prop) { zval *zroot; struct php_svn_fs_root *root = NULL; char *path; int path_len; char *name; int name_len; char *value; int value_len; svn_string_t *svn_value; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsss", &zroot, &path, &path_len, &name, &name_len, &value, &value_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root, struct php_svn_fs_root *, &zroot, -1, "svn-fs-root", le_svn_fs_root); svn_value = emalloc(sizeof(*svn_value)); svn_value->data = value; svn_value->len = value_len; err = svn_fs_change_node_prop(root->root, path, name, svn_value, root->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ proto boolean svn_fs_contents_changed(resource root1, string path1, resource root2, string path2) return true if content is different, false otherwise */ PHP_FUNCTION(svn_fs_contents_changed) { zval *zroot1; struct php_svn_fs_root *root1 = NULL; zval *zroot2; struct php_svn_fs_root *root2 = NULL; char *path1; int path1_len; char *path2; int path2_len; svn_boolean_t changed; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsrs", &zroot1, &path1, &path1_len, &zroot2, &path2, &path2_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root1, struct php_svn_fs_root *, &zroot1, -1, "svn-fs-root", le_svn_fs_root); ZEND_FETCH_RESOURCE(root2, struct php_svn_fs_root *, &zroot2, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_contents_changed(&changed, root1->root, path1, root2->root, path2, root1->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { if (changed == 1) { RETURN_TRUE; } else { RETURN_FALSE; } } } /* }}} */ /* {{{ proto boolean svn_fs_props_changed(resource root1, string path1, resource root2, string path2) return true if props are different, false otherwise */ PHP_FUNCTION(svn_fs_props_changed) { zval *zroot1; struct php_svn_fs_root *root1 = NULL; zval *zroot2; struct php_svn_fs_root *root2 = NULL; char *path1; int path1_len; char *path2; int path2_len; svn_boolean_t changed; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rsrs", &zroot1, &path1, &path1_len, &zroot2, &path2, &path2_len)) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(root1, struct php_svn_fs_root *, &zroot1, -1, "svn-fs-root", le_svn_fs_root); ZEND_FETCH_RESOURCE(root2, struct php_svn_fs_root *, &zroot2, -1, "svn-fs-root", le_svn_fs_root); err = svn_fs_props_changed(&changed, root1->root, path1, root2->root, path2, root1->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { if (changed == 1) { RETURN_TRUE; } else { RETURN_FALSE; } } } /* }}} */ /* {{{ proto boolean svn_fs_abort_txn(resource txn) abort a transaction, returns true if everything is ok, false othewise */ PHP_FUNCTION(svn_fs_abort_txn) { zval *ztxn; struct php_svn_repos_fs_txn *txn; svn_error_t *err; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &ztxn)) { return; } ZEND_FETCH_RESOURCE(txn, struct php_svn_repos_fs_txn *, &ztxn, -1, "svn-repos-fs-txn", le_svn_repos_fs_txn); err = svn_fs_abort_txn(txn->txn, txn->repos->pool); if (err) { php_svn_handle_error(err TSRMLS_CC); RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */