(module svn-client (svn-info? svn-info-url svn-info-repos-root-url svn-info-rev svn-info-last-changed-rev svn-client-info svn-file? svn-file-base-path svn-file-path svn-file-kind svn-file-size svn-file-has-props? svn-file-last-changed-revision svn-file-last-changed-time svn-file-last-changed-author svn-log? svn-log-message svn-log-author svn-log-date svn-log-revision svn-log-changes svn-log-change? svn-log-change-path svn-log-change-action svn-log-change-from-path svn-log-change-from-revision make-svn-opt-revision-number svn-opt-revision-head svn-opt-revision-unspecified svn-commit svn-checkout svn-diff svn-client-list svn-propget svn-propset-local svn-add svn-client-log svn-client-revert svn-client-cat svn-update svn-repos-create ) (import chicken scheme foreign) (use lolevel) (foreign-declare "#include \"svn-client-base.c\"") (define gc-root-ref (foreign-lambda* scheme-object (((c-pointer "void") root)) "C_return(CHICKEN_gc_root_ref(root));")) ;;; Pools (define-record apr-pool ptr) (define-foreign-type apr-pool-type (c-pointer "apr_pool_t") apr-pool-ptr make-apr-pool) (define svn-pool-create (foreign-lambda apr-pool-type "svn_pool_create" apr-pool-type)) ;;; Hashes (define-record apr-hash ptr) (define-foreign-type apr-hash-type (c-pointer "apr_hash_t") apr-hash-ptr make-apr-hash) ;; This is sooo stupid (define canonicalize-path-or-url (foreign-lambda nonnull-c-string* "svnclient_canonicalize_path_or_url" nonnull-c-string)) (define-foreign-type svn-path-or-url c-string canonicalize-path-or-url) ;; For svn-strings only (define (apr-hash-ref hash key) ((foreign-safe-lambda* c-string ((apr-hash-type hash) ((const c-string) key)) "svn_string_t *res_s;" ;; How nice that this is so well-documented *COUGH* "if ((res_s = apr_hash_get(hash, key, APR_HASH_KEY_STRING)))" " C_return(res_s->data);" ;; Can we do this? svn's compat.c sure thinks so! "else" " C_return(NULL);") hash key)) ;;; Errors (define-record svn-error ptr) (define-foreign-type svn-error-type (c-pointer "svn_error_t") svn-error-ptr make-svn-error) (define svn-no-error (foreign-value "SVN_NO_ERROR" svn-error-type)) ;;; Info (define-record svn-info url repos-root-url rev last-changed-rev) (define-foreign-type svn-info-type (c-pointer "svn_client_info2_t")) ;;; log (define-foreign-type svn-log-entry-type (c-pointer "svn_log_entry_t")) (define svn-log-entry-changed-paths (foreign-lambda* apr-hash-type ((svn-log-entry-type log)) "C_return(log->changed_paths);")) (define svn-log-entry-revision (foreign-lambda* svn-revnum-type ((svn-log-entry-type log)) "C_return(log->revision);")) (define svn-log-entry-revprops (foreign-lambda* apr-hash-type ((svn-log-entry-type log)) "C_return(log->revprops);")) ;;; Revisions (define-record svn-opt-revision ptr) (define (make-svn-opt-revision-with-finalizer x) (set-finalizer! x free) (make-svn-opt-revision x)) (define-foreign-type svn-opt-revision-type (c-pointer "svn_opt_revision_t") svn-opt-revision-ptr make-svn-opt-revision-with-finalizer) (define make-svn-opt-revision-number (foreign-lambda* svn-opt-revision-type ((long num)) "svn_opt_revision_t *rev = malloc(sizeof(svn_opt_revision_t));" "if (rev) {" " rev->kind = svn_opt_revision_number;" " rev->value.number = num;" "}" "C_return(rev);")) (define svn-opt-revision-head (make-svn-opt-revision (foreign-value "&revision_head" (c-pointer "svn_opt_revision_t")))) (define svn-opt-revision-unspecified (make-svn-opt-revision (foreign-value "&revision_unspecified" (c-pointer "svn_opt_revision_t")))) (define svn-opt-revision-number (foreign-lambda* long ((svn-opt-revision-type rev)) "C_return(rev->kind == svn_opt_revision_number ? rev->value.number : -1);")) (define svn-opt-revision-kind (foreign-lambda* unsigned-int ((svn-opt-revision-type rev)) "C_return(rev->kind);")) (define-foreign-type svn-revnum-type long) ;;; svn_log_changed_path_t (define-record svn-log-changed-path ptr) (define (make-svn-log-changed-path-with-finalizer x) (set-finalizer! x (foreign-lambda* void (((c-pointer "svn_log_changed_path_t") obj)) "if (obj) free(obj);")) (make-svn-log-changed-path x)) (define-foreign-type svn-log-changed-path-type (c-pointer "svn_log_changed_path_t") svn-log-changed-path-ptr make-svn-log-changed-path-with-finalizer) (define svn-log-changed-path-action (foreign-lambda* char ((svn-log-changed-path-type info)) "C_return(info->action);")) (define svn-log-changed-path-copy-from-path (foreign-lambda* c-string ((svn-log-changed-path-type info)) "C_return(info->copyfrom_path);")) (define svn-log-changed-path-copy-from-rev (foreign-lambda* long ((svn-log-changed-path-type info)) "C_return(info->copyfrom_rev);")) ;;; lock (define-foreign-type svn-lock-type (c-pointer "svn_lock_t")) ;;; dirent (define-foreign-type svn-dirent-type (c-pointer "svn_dirent_t")) (define svn-dirent-kind (foreign-lambda* (enum "svn_node_kind_t") ((svn-dirent-type dirent)) "C_return(dirent->kind);")) (define svn-dirent-size (foreign-lambda* unsigned-integer64 ((svn-dirent-type dirent)) "C_return(dirent->size);")) (define svn-dirent-has-props? (foreign-lambda* bool ((svn-dirent-type dirent)) "C_return(dirent->has_props);")) (define svn-dirent-created-rev (foreign-lambda* long ((svn-dirent-type dirent)) "C_return(dirent->created_rev);")) (define svn-dirent-time (foreign-lambda* unsigned-integer64 ((svn-dirent-type dirent)) "C_return(dirent->time);")) (define svn-dirent-last-author (foreign-lambda* c-string ((svn-dirent-type dirent)) "C_return(dirent->last_author);")) ;;; Depth (define-foreign-type svn-depth int (lambda (d) (cond ((boolean? d) (if d (foreign-value "svn_depth_infinity" int) (foreign-value "svn_depth_empty" int))) ((fixnum? d) d) (else (error "Not a correct depth value!"))))) ;;; Other stuff (define svn-commit (foreign-lambda* scheme-object ((c-string path) (svn-depth depth) (c-string user) (c-string pass) (c-string changes)) #<log_msg_func = svnwiki_log_callback; svn_ctx->log_msg_baton = changes; err = svn_client_commit6(targets, depth, FALSE, FALSE, FALSE, FALSE, FALSE, NULL, NULL, svnclient_commit_set_revision, &retval, svn_ctx, tmp_pool); if (err) { svn_handle_error2(err, stderr, FALSE, "svn: "); svn_error_clear(err); svn_pool_destroy(tmp_pool); C_return(C_SCHEME_FALSE); } svn_pool_destroy(tmp_pool); C_return(retval); EOF )) (define svn-checkout (foreign-lambda* scheme-object ((c-string url) (c-string path) (svn-opt-revision-type rev) (svn-depth depth) (c-string user) (c-string pass)) #< updated by set-cdr! in svn_ls_add. cdr is returned */ result = C_a_pair(&ap, C_SCHEME_FALSE, C_SCHEME_END_OF_LIST); /* Is this really necessary? */ resultroot = CHICKEN_new_gc_root(); CHICKEN_gc_root_set(resultroot, result); tmp_pool = svn_pool_create(NULL); head.kind = svn_opt_revision_head; set_creds_for_next_command(user, pass); /* Use DIRENT_ALL? */ err = svn_client_list3(path, &peg_revision, rev, depth, SVN_DIRENT_ALL, TRUE, FALSE, svn_ls_add, resultroot, svn_ctx, tmp_pool); if (err) { svn_handle_error2(err, stderr, FALSE, "svn: "); svn_error_clear(err); svn_pool_destroy(tmp_pool); CHICKEN_delete_gc_root(resultroot); C_return(C_SCHEME_FALSE); } svn_pool_destroy(tmp_pool); result = CHICKEN_gc_root_ref(resultroot); CHICKEN_delete_gc_root(resultroot); C_return(revlist(C_u_i_cdr(result))); EOF )) (define-external (revlist (scheme-object list)) scheme-object (reverse list)) ;; We could just pass the dirent around, but doing this copies stuff ;; to Scheme memory so we can just deallocate the pool when done. (define-record svn-file base-path path kind size has-props? last-changed-revision last-changed-time last-changed-author) (define-external (svn_ls_add ((c-pointer void) baton) ((const c-string) path) ((const svn-dirent-type) dirent) ((const svn-lock-type) lock) ((const c-string) base-path) ((const c-string) external-parent-url) ((const c-string) external-target) (apr-pool-type pool)) svn-error-type (let* ((result (gc-root-ref baton)) ;; "unused" is to trick the compiler into thinking lock and pool are ;; used, as a workaround for bug #584 (unused (list lock pool)) (kind (svn-dirent-kind dirent)) (kind-symbol (cond ((= kind (foreign-value "svn_node_none" int)) 'none) ((= kind (foreign-value "svn_node_file" int)) 'file) ((= kind (foreign-value "svn_node_dir" int)) 'directory) (else 'unknown))) (svn-file (make-svn-file base-path path kind-symbol (svn-dirent-size dirent) (svn-dirent-has-props? dirent) (svn-dirent-created-rev dirent) (svn-dirent-time dirent) (svn-dirent-last-author dirent)))) (set-cdr! result (cons svn-file (cdr result))) svn-no-error)) ; Currently returns a list of all property values for a given file. (define svn-propget (foreign-safe-lambda* scheme-object ((c-string propname) (c-string target) (svn-opt-revision-type rev) (svn-depth depth) (c-string user) (c-string pass)) #< updated by set-cdr! in svn_propget_add. cdr is returned */ result = C_a_pair(&ap, C_SCHEME_FALSE, C_SCHEME_END_OF_LIST); resultroot = CHICKEN_new_gc_root(); CHICKEN_gc_root_set(resultroot, result); set_creds_for_next_command(user, pass); err = svn_client_propget5(&props, NULL, propname, target, &peg_revision, rev, NULL, depth, NULL, svn_ctx, pool, pool); if (err) { apr_pool_destroy(pool); svn_handle_error2(err, stderr, FALSE, "svn: "); svn_error_clear(err); C_return(C_SCHEME_FALSE); } for (tmp = apr_hash_first(pool, props); tmp; tmp = apr_hash_next(tmp)) { const void *key; apr_ssize_t keylen; svn_string_t *value; apr_hash_this(tmp, &key, &keylen, (void **) &value); svn_propget_add(key, value->data, resultroot); } apr_pool_destroy(pool); result = CHICKEN_gc_root_ref(resultroot); CHICKEN_delete_gc_root(resultroot); C_return(revlist(C_u_i_cdr(result))); EOF )) (define-external (svn_propget_add ((const c-string) key) ((const c-string) value) ((c-pointer void) baton)) void (let ((result (gc-root-ref baton))) (set-cdr! result (cons (list key value) (cdr result))))) (define svn-propset-local (foreign-safe-lambda* scheme-object ((c-string propname) (c-string propval) ;; Really just a path (svn-path-or-url path) (svn-depth depth) (bool skip_checks)) #< updated by set-cdr! in changed_paths_fix_one. cdr is returned */ result = C_a_pair(&ap, C_SCHEME_FALSE, C_SCHEME_END_OF_LIST); CHICKEN_gc_root_set(resultroot, result); tmp_pool = svn_pool_create(NULL); for (tmp = apr_hash_first(tmp_pool, paths); tmp; tmp = apr_hash_next(tmp)) { const void *key; void *old; apr_ssize_t keylen; svn_log_changed_path_t *new = malloc(sizeof(svn_log_changed_path_t)); /* WTF */ apr_hash_this(tmp, &key, &keylen, &old); *new = * (svn_log_changed_path_t *) old; changed_paths_fix_one(key, new, resultroot); } svn_pool_destroy(tmp_pool); result = CHICKEN_gc_root_ref(resultroot); CHICKEN_delete_gc_root(resultroot); C_return(revlist(C_u_i_cdr(result))); EOF )) (define svn-client-log (foreign-safe-lambda* scheme-object ((svn-path-or-url path) (svn-opt-revision-type start) (svn-opt-revision-type end) (int limit) (bool discover_changed_paths) (bool strict_node_history) (c-string user) (c-string pass)) #< updated by set-cdr! in svn_history_add. cdr is returned */ result = C_a_pair(&ap, C_SCHEME_FALSE, C_SCHEME_END_OF_LIST); CHICKEN_gc_root_set(resultroot, result); tmp_pool = svn_pool_create(NULL); targets = apr_array_make(tmp_pool, 1, sizeof(char *)); *(char **)apr_array_push(targets) = path; /* These should probably be passed as an argument too */ revprops = apr_array_make(tmp_pool, 3, sizeof(char *)); APR_ARRAY_PUSH(revprops, char *) = "svn:author"; APR_ARRAY_PUSH(revprops, char *) = "svn:date"; APR_ARRAY_PUSH(revprops, char *) = "svn:log"; set_creds_for_next_command(user, pass); range.start = *start; range.end = *end; revranges = apr_array_make(tmp_pool, 1, sizeof(svn_opt_revision_range_t *)); APR_ARRAY_PUSH(revranges, svn_opt_revision_range_t *) = ⦥ err = svn_client_log5(targets, &peg_revision, revranges, limit, discover_changed_paths, strict_node_history, include_merged_revs, revprops, svn_history_add, (void *) resultroot, svn_ctx, tmp_pool); svn_pool_destroy(tmp_pool); if (err) { CHICKEN_delete_gc_root(resultroot); svn_handle_error2(err, stderr, FALSE, "svn: "); svn_error_clear(err); C_return(C_SCHEME_FALSE); } result = CHICKEN_gc_root_ref(resultroot); CHICKEN_delete_gc_root(resultroot); C_return(C_u_i_cdr(result)); EOF )) ;; We could just pass the log entry around, but doing this copies stuff ;; to Scheme memory so we can just deallocate the pool when done. (define-record svn-log message author date changes revision) (define-external (svn_history_add ((c-pointer void) baton) (svn-log-entry-type entry) (apr-pool-type pool)) svn-error-type (let* ((result (gc-root-ref baton)) (paths (changed-paths-fix (svn-log-entry-changed-paths entry))) (h (svn-log-entry-revprops entry)) (svn-log (make-svn-log (apr-hash-ref h "svn:log") (apr-hash-ref h "svn:author") (apr-hash-ref h "svn:date") paths (svn-log-entry-revision entry)))) (set-cdr! result (cons svn-log (cdr result))) svn-no-error)) (define get-lock (foreign-lambda* scheme-object ((c-string path)) #< updated by set-cdr! in svn_client_info_receiver. cdr is returned */ result = C_a_pair(&ap, C_SCHEME_FALSE, C_SCHEME_END_OF_LIST); resultroot = CHICKEN_new_gc_root(); CHICKEN_gc_root_set(resultroot, result); set_creds_for_next_command(user, pass); err = svn_client_info3(abspath_or_url, peg_revision, revision, depth, TRUE, TRUE, NULL, svn_client_info_receiver, resultroot, svn_ctx, tmp_pool); if (err) { apr_pool_destroy(tmp_pool); svn_handle_error2(err, stderr, FALSE, "svn: "); svn_error_clear(err); CHICKEN_delete_gc_root(resultroot); C_return(C_SCHEME_FALSE); } apr_pool_destroy(tmp_pool); result = CHICKEN_gc_root_ref(resultroot); CHICKEN_delete_gc_root(resultroot); C_return(revlist(C_u_i_cdr(result))); EOF )) (define-external (svn_client_info_receiver ((c-pointer void) baton) ((const c-string) path-or-url) ((const svn-info-type) info) (apr-pool-type scratch-pool)) svn-error-type (let ((result (gc-root-ref baton))) (set-cdr! result (cons (list path-or-url (make-svn-info ((foreign-lambda* c-string ((svn-info-type info)) "C_return(info->URL);") info) ((foreign-lambda* c-string ((svn-info-type info)) "C_return(info->repos_root_URL);") info) ((foreign-lambda* long ((svn-info-type info)) "C_return(info->rev);") info) ((foreign-lambda* long ((svn-info-type info)) "C_return(info->last_changed_rev);") info))) (cdr result))) svn-no-error)) (define svn-repos-create (foreign-lambda* scheme-object ((nonnull-c-string path)) #<