| /* |
| * repos_diff.c -- The diff editor for comparing two repository versions |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2006 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/. |
| * ==================================================================== |
| */ |
| |
| /* This code uses an editor driven by a tree delta between two |
| * repository revisions (REV1 and REV2). For each file encountered in |
| * the delta the editor constructs two temporary files, one for each |
| * revision. This necessitates a separate request for the REV1 version |
| * of the file when the delta shows the file being modified or |
| * deleted. Files that are added by the delta do not require a |
| * separate request, the REV1 version is empty and the delta is |
| * sufficient to construct the REV2 version. When both versions of |
| * each file have been created the diff callback is invoked to display |
| * the difference between the two files. */ |
| |
| #include "svn_hash.h" |
| #include "svn_wc.h" |
| #include "svn_pools.h" |
| #include "svn_path.h" |
| #include "svn_io.h" |
| #include "svn_props.h" |
| |
| #include "client.h" |
| |
| /* Overall crawler editor baton. */ |
| struct edit_baton { |
| /* TARGET is a working-copy directory which corresponds to the base |
| URL open in RA_SESSION below. */ |
| const char *target; |
| |
| /* ADM_ACCESS is an access baton that includes the TARGET directory */ |
| svn_wc_adm_access_t *adm_access; |
| |
| /* The callback and calback argument that implement the file comparison |
| function */ |
| const svn_wc_diff_callbacks2_t *diff_callbacks; |
| void *diff_cmd_baton; |
| |
| /* DRY_RUN is TRUE if this is a dry-run diff, false otherwise. */ |
| svn_boolean_t dry_run; |
| |
| /* RA_SESSION is the open session for making requests to the RA layer */ |
| svn_ra_session_t *ra_session; |
| |
| /* The rev1 from the '-r Rev1:Rev2' command line option */ |
| svn_revnum_t revision; |
| |
| /* The rev2 from the '-r Rev1:Rev2' option, specifically set by |
| set_target_revision(). */ |
| svn_revnum_t target_revision; |
| |
| /* The path to a temporary empty file used for add/delete |
| differences. The path is cached here so that it can be reused, |
| since all empty files are the same. */ |
| const char *empty_file; |
| |
| /* Empty hash used for adds. */ |
| apr_hash_t *empty_hash; |
| |
| /* Hash used to check replaced paths. Key is path relative CWD, |
| * Value is *kind_action_state_t. |
| * All allocations are from edit_baton's pool. */ |
| apr_hash_t *deleted_paths; |
| |
| /* If the func is non-null, send notifications of actions. */ |
| svn_wc_notify_func2_t notify_func; |
| void *notify_baton; |
| |
| apr_pool_t *pool; |
| }; |
| |
| typedef struct kind_action_state_t |
| { |
| svn_node_kind_t kind; |
| svn_wc_notify_action_t action; |
| svn_wc_notify_state_t state; |
| } kind_action_state_t; |
| |
| /* Directory level baton. |
| */ |
| struct dir_baton { |
| /* Gets set if the directory is added rather than replaced/unchanged. */ |
| svn_boolean_t added; |
| |
| /* The path of the directory within the repository */ |
| const char *path; |
| |
| /* The path of the directory in the wc, relative to cwd */ |
| const char *wcpath; |
| |
| /* The baton for the parent directory, or null if this is the root of the |
| hierarchy to be compared. */ |
| struct dir_baton *dir_baton; |
| |
| /* The overall crawler editor baton. */ |
| struct edit_baton *edit_baton; |
| |
| /* A cache of any property changes (svn_prop_t) received for this dir. */ |
| apr_array_header_t *propchanges; |
| |
| /* The pristine-property list attached to this directory. */ |
| apr_hash_t *pristine_props; |
| |
| /* The pool passed in by add_dir, open_dir, or open_root. |
| Also, the pool this dir baton is allocated in. */ |
| apr_pool_t *pool; |
| }; |
| |
| /* File level baton. |
| */ |
| struct file_baton { |
| /* Gets set if the file is added rather than replaced. */ |
| svn_boolean_t added; |
| |
| /* The path of the file within the repository */ |
| const char *path; |
| |
| /* The path of the file in the wc, relative to cwd */ |
| const char *wcpath; |
| |
| /* The path and APR file handle to the temporary file that contains the |
| first repository version. Also, the pristine-property list of |
| this file. */ |
| const char *path_start_revision; |
| apr_file_t *file_start_revision; |
| apr_hash_t *pristine_props; |
| |
| /* The path and APR file handle to the temporary file that contains the |
| second repository version. These fields are set when processing |
| textdelta and file deletion, and will be NULL if there's no |
| textual difference between the two revisions. */ |
| const char *path_end_revision; |
| apr_file_t *file_end_revision; |
| |
| /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */ |
| svn_txdelta_window_handler_t apply_handler; |
| void *apply_baton; |
| |
| /* The overall crawler editor baton. */ |
| struct edit_baton *edit_baton; |
| |
| /* A cache of any property changes (svn_prop_t) received for this file. */ |
| apr_array_header_t *propchanges; |
| |
| /* The pool passed in by add_file or open_file. |
| Also, the pool this file_baton is allocated in. */ |
| apr_pool_t *pool; |
| }; |
| |
| |
| /* Create a new directory baton for PATH in POOL. ADDED is set if |
| * this directory is being added rather than replaced. PARENT_BATON is |
| * the baton of the parent directory (or NULL if this is the root of |
| * the comparison hierarchy). The directory and its parent may or may |
| * not exist in the working copy. EDIT_BATON is the overall crawler |
| * editor baton. |
| */ |
| static struct dir_baton * |
| make_dir_baton(const char *path, |
| struct dir_baton *parent_baton, |
| struct edit_baton *edit_baton, |
| svn_boolean_t added, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *dir_baton = apr_pcalloc(pool, sizeof(*dir_baton)); |
| |
| dir_baton->dir_baton = parent_baton; |
| dir_baton->edit_baton = edit_baton; |
| dir_baton->added = added; |
| dir_baton->pool = pool; |
| dir_baton->path = apr_pstrdup(pool, path); |
| dir_baton->wcpath = svn_path_join(edit_baton->target, path, pool); |
| dir_baton->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t)); |
| |
| return dir_baton; |
| } |
| |
| /* Create a new file baton for PATH in POOL, which is a child of |
| * directory PARENT_PATH. ADDED is set if this file is being added |
| * rather than replaced. EDIT_BATON is a pointer to the global edit |
| * baton. |
| */ |
| static struct file_baton * |
| make_file_baton(const char *path, |
| svn_boolean_t added, |
| void *edit_baton, |
| apr_pool_t *pool) |
| { |
| struct file_baton *file_baton = apr_pcalloc(pool, sizeof(*file_baton)); |
| struct edit_baton *eb = edit_baton; |
| |
| file_baton->edit_baton = edit_baton; |
| file_baton->added = added; |
| file_baton->pool = pool; |
| file_baton->path = apr_pstrdup(pool, path); |
| file_baton->wcpath = svn_path_join(eb->target, path, pool); |
| file_baton->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t)); |
| |
| return file_baton; |
| } |
| |
| |
| /* Helper function: return up to two svn:mime-type values buried |
| * within a file baton. Set *MIMETYPE1 to the value within the file's |
| * pristine properties, or NULL if not available. Set *MIMETYPE2 to |
| * the value within the "new" file's propchanges, or NULL if not |
| * available. |
| */ |
| static void |
| get_file_mime_types(const char **mimetype1, |
| const char **mimetype2, |
| struct file_baton *b) |
| { |
| /* Defaults */ |
| *mimetype1 = NULL; |
| *mimetype2 = NULL; |
| |
| if (b->pristine_props) |
| { |
| svn_string_t *pristine_val; |
| pristine_val = apr_hash_get(b->pristine_props, SVN_PROP_MIME_TYPE, |
| strlen(SVN_PROP_MIME_TYPE)); |
| if (pristine_val) |
| *mimetype1 = pristine_val->data; |
| } |
| |
| if (b->propchanges) |
| { |
| int i; |
| svn_prop_t *propchange; |
| |
| for (i = 0; i < b->propchanges->nelts; i++) |
| { |
| propchange = &APR_ARRAY_IDX(b->propchanges, i, svn_prop_t); |
| if (strcmp(propchange->name, SVN_PROP_MIME_TYPE) == 0) |
| { |
| if (propchange->value) |
| *mimetype2 = propchange->value->data; |
| break; |
| } |
| } |
| } |
| } |
| |
| |
| /* Get the repository version of a file. This makes an RA request to |
| * retrieve the file contents. A pool cleanup handler is installed to |
| * delete this file. |
| */ |
| static svn_error_t * |
| get_file_from_ra(struct file_baton *b, svn_revnum_t revision) |
| { |
| apr_file_t *file; |
| svn_stream_t *fstream; |
| const char *temp_dir; |
| |
| SVN_ERR(svn_io_temp_dir(&temp_dir, b->pool)); |
| SVN_ERR(svn_io_open_unique_file2(&file, &(b->path_start_revision), |
| svn_path_join(temp_dir, "tmp", b->pool), |
| "", svn_io_file_del_on_pool_cleanup, |
| b->pool)); |
| |
| fstream = svn_stream_from_aprfile(file, b->pool); |
| SVN_ERR(svn_ra_get_file(b->edit_baton->ra_session, |
| b->path, |
| revision, |
| fstream, NULL, |
| &(b->pristine_props), |
| b->pool)); |
| SVN_ERR(svn_io_file_close(file, b->pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Get the props attached to a directory in the repository at BASE_REVISION. */ |
| static svn_error_t * |
| get_dirprops_from_ra(struct dir_baton *b, svn_revnum_t base_revision) |
| { |
| SVN_ERR(svn_ra_get_dir2(b->edit_baton->ra_session, |
| NULL, NULL, &(b->pristine_props), |
| b->path, |
| base_revision, |
| 0, |
| b->pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Create an empty file, the path to the file is returned in |
| EMPTY_FILE_PATH. If ADM_ACCESS is not NULL and a lock is held, |
| create the file in the adm tmp/ area, otherwise use a system temp |
| directory. |
| |
| If FILE is non-NULL, an open file is returned in *FILE. */ |
| static svn_error_t * |
| create_empty_file(apr_file_t **file, |
| const char **empty_file_path, |
| svn_wc_adm_access_t *adm_access, |
| svn_io_file_del_t delete_when, |
| apr_pool_t *pool) |
| { |
| if (adm_access && svn_wc_adm_locked(adm_access)) |
| SVN_ERR(svn_wc_create_tmp_file2(file, empty_file_path, |
| svn_wc_adm_access_path(adm_access), |
| delete_when, pool)); |
| else |
| { |
| const char *temp_dir; |
| |
| SVN_ERR(svn_io_temp_dir(&temp_dir, pool)); |
| SVN_ERR(svn_io_open_unique_file2(file, empty_file_path, |
| svn_path_join(temp_dir, "tmp", pool), |
| "", delete_when, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Return in *PATH_ACCESS the access baton for the directory PATH by |
| searching the access baton set of ADM_ACCESS. If ADM_ACCESS is NULL |
| then *PATH_ACCESS will be NULL. If LENIENT is TRUE then failure to find |
| an access baton will not return an error but will set *PATH_ACCESS to |
| NULL instead. */ |
| static svn_error_t * |
| get_path_access(svn_wc_adm_access_t **path_access, |
| svn_wc_adm_access_t *adm_access, |
| const char *path, |
| svn_boolean_t lenient, |
| apr_pool_t *pool) |
| { |
| if (! adm_access) |
| *path_access = NULL; |
| else |
| { |
| svn_error_t *err = svn_wc_adm_retrieve(path_access, adm_access, path, |
| pool); |
| if (err) |
| { |
| if (! lenient) |
| return err; |
| svn_error_clear(err); |
| *path_access = NULL; |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Like get_path_access except the returned access baton, in |
| *PARENT_ACCESS, is for the parent of PATH rather than for PATH |
| itself. */ |
| static svn_error_t * |
| get_parent_access(svn_wc_adm_access_t **parent_access, |
| svn_wc_adm_access_t *adm_access, |
| const char *path, |
| svn_boolean_t lenient, |
| apr_pool_t *pool) |
| { |
| if (! adm_access) |
| *parent_access = NULL; /* Avoid messing around with paths */ |
| else |
| { |
| const char *parent_path = svn_path_dirname(path, pool); |
| SVN_ERR(get_path_access(parent_access, adm_access, parent_path, |
| lenient, pool)); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| /* Get the empty file associated with the edit baton. This is cached so |
| * that it can be reused, all empty files are the same. |
| */ |
| static svn_error_t * |
| get_empty_file(struct edit_baton *eb, |
| const char **empty_file_path) |
| { |
| /* Create the file if it does not exist */ |
| /* Note that we tried to use /dev/null in r17220, but |
| that won't work on Windows: it's impossible to stat NUL */ |
| if (!eb->empty_file) |
| SVN_ERR(create_empty_file(NULL, &(eb->empty_file), eb->adm_access, |
| svn_io_file_del_on_pool_cleanup, eb->pool)); |
| |
| |
| *empty_file_path = eb->empty_file; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. The root of the comparison hierarchy */ |
| static svn_error_t * |
| set_target_revision(void *edit_baton, |
| svn_revnum_t target_revision, |
| apr_pool_t *pool) |
| { |
| struct edit_baton *eb = edit_baton; |
| |
| eb->target_revision = target_revision; |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. The root of the comparison hierarchy */ |
| static svn_error_t * |
| open_root(void *edit_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **root_baton) |
| { |
| struct edit_baton *eb = edit_baton; |
| struct dir_baton *b = make_dir_baton("", NULL, eb, FALSE, pool); |
| |
| /* Override the wcpath in our baton. */ |
| b->wcpath = apr_pstrdup(pool, eb->target); |
| |
| SVN_ERR(get_dirprops_from_ra(b, base_revision)); |
| |
| *root_baton = b; |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. */ |
| static svn_error_t * |
| delete_entry(const char *path, |
| svn_revnum_t base_revision, |
| void *parent_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct edit_baton *eb = pb->edit_baton; |
| svn_node_kind_t kind; |
| svn_wc_adm_access_t *adm_access; |
| svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; |
| svn_wc_notify_action_t action = svn_wc_notify_skip; |
| |
| /* We need to know if this is a directory or a file */ |
| SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind, pool)); |
| SVN_ERR(get_path_access(&adm_access, eb->adm_access, pb->wcpath, |
| TRUE, pool)); |
| if ((! eb->adm_access) || adm_access) |
| { |
| switch (kind) |
| { |
| case svn_node_file: |
| { |
| const char *mimetype1, *mimetype2; |
| struct file_baton *b; |
| |
| /* Compare a file being deleted against an empty file */ |
| b = make_file_baton(path, FALSE, eb, pool); |
| SVN_ERR(get_file_from_ra(b, eb->revision)); |
| SVN_ERR(get_empty_file(b->edit_baton, &(b->path_end_revision))); |
| |
| get_file_mime_types(&mimetype1, &mimetype2, b); |
| |
| SVN_ERR(eb->diff_callbacks->file_deleted |
| (adm_access, &state, b->wcpath, |
| b->path_start_revision, |
| b->path_end_revision, |
| mimetype1, mimetype2, |
| b->pristine_props, |
| b->edit_baton->diff_cmd_baton)); |
| |
| break; |
| } |
| case svn_node_dir: |
| { |
| SVN_ERR(eb->diff_callbacks->dir_deleted |
| (adm_access, &state, |
| svn_path_join(eb->target, path, pool), |
| eb->diff_cmd_baton)); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if ((state != svn_wc_notify_state_missing) |
| && (state != svn_wc_notify_state_obstructed)) |
| { |
| action = svn_wc_notify_update_delete; |
| if (eb->dry_run) |
| { |
| /* Remember what we _would've_ deleted (issue #2584). */ |
| const char *wcpath = svn_path_join(eb->target, path, pb->pool); |
| apr_hash_set(svn_client__dry_run_deletions(eb->diff_cmd_baton), |
| wcpath, APR_HASH_KEY_STRING, wcpath); |
| |
| /* ### TODO: if (kind == svn_node_dir), record all |
| ### children as deleted to avoid collisions from |
| ### subsequent edits. */ |
| } |
| } |
| } |
| |
| if (eb->notify_func) |
| { |
| const char* deleted_path; |
| kind_action_state_t *kas = apr_palloc(eb->pool, sizeof(*kas)); |
| deleted_path = svn_path_join(eb->target, path, eb->pool); |
| kas->kind = kind; |
| kas->action = action; |
| kas->state = state; |
| apr_hash_set(eb->deleted_paths, deleted_path, APR_HASH_KEY_STRING, kas); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. */ |
| static svn_error_t * |
| add_directory(const char *path, |
| void *parent_baton, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_revision, |
| apr_pool_t *pool, |
| void **child_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct edit_baton *eb = pb->edit_baton; |
| struct dir_baton *b; |
| svn_wc_adm_access_t *adm_access; |
| svn_wc_notify_state_t state; |
| svn_wc_notify_action_t action; |
| |
| /* ### TODO: support copyfrom? */ |
| |
| b = make_dir_baton(path, pb, eb, TRUE, pool); |
| b->pristine_props = eb->empty_hash; |
| *child_baton = b; |
| |
| SVN_ERR(get_path_access(&adm_access, eb->adm_access, pb->wcpath, TRUE, |
| pool)); |
| |
| SVN_ERR(eb->diff_callbacks->dir_added |
| (adm_access, &state, b->wcpath, eb->target_revision, |
| eb->diff_cmd_baton)); |
| |
| if ((state == svn_wc_notify_state_missing) |
| || (state == svn_wc_notify_state_obstructed)) |
| action = svn_wc_notify_skip; |
| else |
| action = svn_wc_notify_update_add; |
| |
| if (eb->notify_func) |
| { |
| svn_wc_notify_t *notify; |
| svn_boolean_t is_replace = FALSE; |
| kind_action_state_t *kas = apr_hash_get(eb->deleted_paths, b->wcpath, |
| APR_HASH_KEY_STRING); |
| if (kas) |
| { |
| svn_wc_notify_action_t new_action; |
| if (kas->action == svn_wc_notify_update_delete |
| && action == svn_wc_notify_update_add) |
| { |
| is_replace = TRUE; |
| new_action = svn_wc_notify_update_replace; |
| } |
| else |
| new_action = kas->action; |
| notify = svn_wc_create_notify(b->wcpath, new_action, pool); |
| notify->kind = kas->kind; |
| notify->content_state = notify->prop_state = kas->state; |
| notify->lock_state = svn_wc_notify_lock_state_inapplicable; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| apr_hash_set(eb->deleted_paths, b->wcpath, |
| APR_HASH_KEY_STRING, NULL); |
| } |
| |
| if (!is_replace) |
| { |
| notify = svn_wc_create_notify(b->wcpath, action, pool); |
| notify->kind = svn_node_dir; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. */ |
| static svn_error_t * |
| open_directory(const char *path, |
| void *parent_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **child_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct dir_baton *b; |
| |
| b = make_dir_baton(path, pb, pb->edit_baton, FALSE, pool); |
| *child_baton = b; |
| |
| SVN_ERR(get_dirprops_from_ra(b, base_revision)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* An editor function. */ |
| static svn_error_t * |
| add_file(const char *path, |
| void *parent_baton, |
| const char *copyfrom_path, |
| svn_revnum_t copyfrom_revision, |
| apr_pool_t *pool, |
| void **file_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct file_baton *b; |
| |
| /* ### TODO: support copyfrom? */ |
| |
| b = make_file_baton(path, TRUE, pb->edit_baton, pool); |
| *file_baton = b; |
| |
| SVN_ERR(get_empty_file(b->edit_baton, &(b->path_start_revision))); |
| b->pristine_props = pb->edit_baton->empty_hash; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. */ |
| static svn_error_t * |
| open_file(const char *path, |
| void *parent_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **file_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct file_baton *b; |
| |
| b = make_file_baton(path, FALSE, pb->edit_baton, pool); |
| *file_baton = b; |
| |
| SVN_ERR(get_file_from_ra(b, base_revision)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Do the work of applying the text delta. */ |
| static svn_error_t * |
| window_handler(svn_txdelta_window_t *window, |
| void *window_baton) |
| { |
| struct file_baton *b = window_baton; |
| |
| SVN_ERR(b->apply_handler(window, b->apply_baton)); |
| |
| if (!window) |
| { |
| SVN_ERR(svn_io_file_close(b->file_start_revision, b->pool)); |
| SVN_ERR(svn_io_file_close(b->file_end_revision, b->pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. */ |
| static svn_error_t * |
| apply_textdelta(void *file_baton, |
| const char *base_checksum, |
| apr_pool_t *pool, |
| svn_txdelta_window_handler_t *handler, |
| void **handler_baton) |
| { |
| struct file_baton *b = file_baton; |
| svn_wc_adm_access_t *adm_access; |
| |
| /* Open the file to be used as the base for second revision */ |
| SVN_ERR(svn_io_file_open(&(b->file_start_revision), |
| b->path_start_revision, |
| APR_READ, APR_OS_DEFAULT, b->pool)); |
| |
| /* Open the file that will become the second revision after applying the |
| text delta, it starts empty */ |
| if (b->edit_baton->adm_access) |
| { |
| svn_error_t *err; |
| |
| err = svn_wc_adm_probe_retrieve(&adm_access, b->edit_baton->adm_access, |
| b->wcpath, pool); |
| if (err) |
| { |
| svn_error_clear(err); |
| adm_access = NULL; |
| } |
| } |
| else |
| adm_access = NULL; |
| SVN_ERR(create_empty_file(&(b->file_end_revision), |
| &(b->path_end_revision), adm_access, |
| svn_io_file_del_on_pool_cleanup, b->pool)); |
| |
| svn_txdelta_apply(svn_stream_from_aprfile(b->file_start_revision, b->pool), |
| svn_stream_from_aprfile(b->file_end_revision, b->pool), |
| NULL, |
| b->path, |
| b->pool, |
| &(b->apply_handler), &(b->apply_baton)); |
| |
| *handler = window_handler; |
| *handler_baton = file_baton; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. When the file is closed we have a temporary |
| * file containing a pristine version of the repository file. This can |
| * be compared against the working copy. |
| * |
| * ### Ignore TEXT_CHECKSUM for now. Someday we can use it to verify |
| * ### the integrity of the file being diffed. Done efficiently, this |
| * ### would probably involve calculating the checksum as the data is |
| * ### received, storing the final checksum in the file_baton, and |
| * ### comparing against it here. |
| */ |
| static svn_error_t * |
| close_file(void *file_baton, |
| const char *text_checksum, |
| apr_pool_t *pool) |
| { |
| struct file_baton *b = file_baton; |
| struct edit_baton *eb = b->edit_baton; |
| svn_wc_adm_access_t *adm_access; |
| svn_error_t *err; |
| svn_wc_notify_action_t action; |
| svn_wc_notify_state_t |
| content_state = svn_wc_notify_state_unknown, |
| prop_state = svn_wc_notify_state_unknown; |
| |
| err = get_parent_access(&adm_access, eb->adm_access, |
| b->wcpath, eb->dry_run, b->pool); |
| |
| if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED) |
| { |
| /* ### maybe try to stat the local b->wcpath? */ |
| /* If the file path doesn't exist, then send a 'skipped' notification. */ |
| if (eb->notify_func) |
| { |
| svn_wc_notify_t *notify = svn_wc_create_notify(b->wcpath, |
| svn_wc_notify_skip, |
| pool); |
| notify->kind = svn_node_file; |
| notify->content_state = svn_wc_notify_state_missing; |
| notify->prop_state = prop_state; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| } |
| |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| else if (err) |
| return err; |
| |
| if (b->path_end_revision || b->propchanges->nelts > 0) |
| { |
| const char *mimetype1, *mimetype2; |
| get_file_mime_types(&mimetype1, &mimetype2, b); |
| |
| if (b->added) |
| SVN_ERR(eb->diff_callbacks->file_added |
| (adm_access, &content_state, &prop_state, |
| b->wcpath, |
| b->path_end_revision ? b->path_start_revision : NULL, |
| b->path_end_revision, |
| 0, |
| b->edit_baton->target_revision, |
| mimetype1, mimetype2, |
| b->propchanges, b->pristine_props, |
| b->edit_baton->diff_cmd_baton)); |
| else |
| SVN_ERR(eb->diff_callbacks->file_changed |
| (adm_access, &content_state, &prop_state, |
| b->wcpath, |
| b->path_end_revision ? b->path_start_revision : NULL, |
| b->path_end_revision, |
| b->edit_baton->revision, |
| b->edit_baton->target_revision, |
| mimetype1, mimetype2, |
| b->propchanges, b->pristine_props, |
| b->edit_baton->diff_cmd_baton)); |
| } |
| |
| |
| if ((content_state == svn_wc_notify_state_missing) |
| || (content_state == svn_wc_notify_state_obstructed)) |
| action = svn_wc_notify_skip; |
| else if (b->added) |
| action = svn_wc_notify_update_add; |
| else |
| action = svn_wc_notify_update_update; |
| |
| if (eb->notify_func) |
| { |
| svn_wc_notify_t *notify; |
| svn_boolean_t is_replace = FALSE; |
| kind_action_state_t *kas = apr_hash_get(eb->deleted_paths, b->wcpath, |
| APR_HASH_KEY_STRING); |
| if (kas) |
| { |
| svn_wc_notify_action_t new_action; |
| if (kas->action == svn_wc_notify_update_delete |
| && action == svn_wc_notify_update_add) |
| { |
| is_replace = TRUE; |
| new_action = svn_wc_notify_update_replace; |
| } |
| else |
| new_action = kas->action; |
| notify = svn_wc_create_notify(b->wcpath, new_action, pool); |
| notify->kind = kas->kind; |
| notify->content_state = notify->prop_state = kas->state; |
| notify->lock_state = svn_wc_notify_lock_state_inapplicable; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| apr_hash_set(eb->deleted_paths, b->wcpath, |
| APR_HASH_KEY_STRING, NULL); |
| } |
| |
| if (!is_replace) |
| { |
| notify = svn_wc_create_notify(b->wcpath, action, pool); |
| notify->kind = svn_node_file; |
| notify->content_state = content_state; |
| notify->prop_state = prop_state; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. */ |
| static svn_error_t * |
| close_directory(void *dir_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *b = dir_baton; |
| struct edit_baton *eb = b->edit_baton; |
| svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; |
| svn_error_t *err; |
| |
| if (eb->dry_run) |
| svn_hash__clear(svn_client__dry_run_deletions(eb->diff_cmd_baton)); |
| |
| if (b->propchanges->nelts > 0) |
| { |
| svn_wc_adm_access_t *adm_access; |
| err = get_path_access(&adm_access, eb->adm_access, b->wcpath, |
| eb->dry_run, b->pool); |
| |
| if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED) |
| { |
| /* ### maybe try to stat the local b->wcpath? */ |
| /* If the path doesn't exist, then send a 'skipped' notification. */ |
| if (eb->notify_func) |
| { |
| svn_wc_notify_t *notify |
| = svn_wc_create_notify(b->wcpath, svn_wc_notify_skip, pool); |
| notify->kind = svn_node_dir; |
| notify->content_state = notify->prop_state |
| = svn_wc_notify_state_missing; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| } |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| else if (err) |
| return err; |
| |
| /* Don't do the props_changed stuff if this is a dry_run and we don't |
| have an access baton, since in that case the directory will already |
| have been recognised as added, in which case they cannot conflict. */ |
| if (! eb->dry_run || adm_access) |
| SVN_ERR(eb->diff_callbacks->dir_props_changed |
| (adm_access, &prop_state, |
| b->wcpath, |
| b->propchanges, b->pristine_props, |
| b->edit_baton->diff_cmd_baton)); |
| } |
| |
| /* ### Don't notify added directories as they triggered notification |
| in add_directory. Does this mean that directory notification |
| isn't getting all the information? */ |
| if (!b->added && eb->notify_func) |
| { |
| svn_wc_notify_t *notify; |
| apr_hash_index_t *hi; |
| |
| for (hi = apr_hash_first(NULL, eb->deleted_paths); hi; |
| hi = apr_hash_next(hi)) |
| { |
| const void *deleted_path; |
| kind_action_state_t *kas; |
| apr_hash_this(hi, &deleted_path, NULL, (void *)&kas); |
| notify = svn_wc_create_notify(deleted_path, kas->action, pool); |
| notify->kind = kas->kind; |
| notify->content_state = notify->prop_state = kas->state; |
| notify->lock_state = svn_wc_notify_lock_state_inapplicable; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| apr_hash_set(eb->deleted_paths, deleted_path, |
| APR_HASH_KEY_STRING, NULL); |
| } |
| |
| notify = svn_wc_create_notify(b->wcpath, |
| svn_wc_notify_update_update, pool); |
| notify->kind = svn_node_dir; |
| notify->content_state = svn_wc_notify_state_inapplicable; |
| notify->prop_state = prop_state; |
| notify->lock_state = svn_wc_notify_lock_state_inapplicable; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* An editor function. */ |
| static svn_error_t * |
| change_file_prop(void *file_baton, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| struct file_baton *b = file_baton; |
| svn_prop_t *propchange; |
| |
| propchange = apr_array_push(b->propchanges); |
| propchange->name = apr_pstrdup(b->pool, name); |
| propchange->value = value ? svn_string_dup(value, b->pool) : NULL; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. */ |
| static svn_error_t * |
| change_dir_prop(void *dir_baton, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *db = dir_baton; |
| svn_prop_t *propchange; |
| |
| propchange = apr_array_push(db->propchanges); |
| propchange->name = apr_pstrdup(db->pool, name); |
| propchange->value = value ? svn_string_dup(value, db->pool) : NULL; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* An editor function. */ |
| static svn_error_t * |
| close_edit(void *edit_baton, |
| apr_pool_t *pool) |
| { |
| struct edit_baton *eb = edit_baton; |
| |
| svn_pool_destroy(eb->pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* An editor function. */ |
| static svn_error_t * |
| absent_directory(const char *path, |
| void *parent_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct edit_baton *eb = pb->edit_baton; |
| |
| if (eb->notify_func) |
| { |
| svn_wc_notify_t *notify |
| = svn_wc_create_notify(svn_path_join(pb->wcpath, |
| svn_path_basename(path, pool), |
| pool), |
| svn_wc_notify_skip, pool); |
| notify->kind = svn_node_dir; |
| notify->content_state = notify->prop_state |
| = svn_wc_notify_state_missing; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* An editor function. */ |
| static svn_error_t * |
| absent_file(const char *path, |
| void *parent_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct edit_baton *eb = pb->edit_baton; |
| |
| if (eb->notify_func) |
| { |
| svn_wc_notify_t *notify |
| = svn_wc_create_notify(svn_path_join(pb->wcpath, |
| svn_path_basename(path, pool), |
| pool), |
| svn_wc_notify_skip, pool); |
| notify->kind = svn_node_file; |
| notify->content_state = notify->prop_state |
| = svn_wc_notify_state_missing; |
| (*eb->notify_func)(eb->notify_baton, notify, pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Create a repository diff editor and baton. */ |
| svn_error_t * |
| svn_client__get_diff_editor(const char *target, |
| svn_wc_adm_access_t *adm_access, |
| const svn_wc_diff_callbacks2_t *diff_callbacks, |
| void *diff_cmd_baton, |
| svn_depth_t depth, |
| svn_boolean_t dry_run, |
| svn_ra_session_t *ra_session, |
| svn_revnum_t revision, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| const svn_delta_editor_t **editor, |
| void **edit_baton, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create(pool); |
| svn_delta_editor_t *tree_editor = svn_delta_default_editor(subpool); |
| struct edit_baton *eb = apr_palloc(subpool, sizeof(*eb)); |
| |
| eb->target = target; |
| eb->adm_access = adm_access; |
| eb->diff_callbacks = diff_callbacks; |
| eb->diff_cmd_baton = diff_cmd_baton; |
| eb->dry_run = dry_run; |
| eb->ra_session = ra_session; |
| eb->revision = revision; |
| eb->empty_file = NULL; |
| eb->empty_hash = apr_hash_make(subpool); |
| eb->deleted_paths = apr_hash_make(subpool); |
| eb->pool = subpool; |
| eb->notify_func = notify_func; |
| eb->notify_baton = notify_baton; |
| |
| tree_editor->set_target_revision = set_target_revision; |
| tree_editor->open_root = open_root; |
| tree_editor->delete_entry = delete_entry; |
| tree_editor->add_directory = add_directory; |
| tree_editor->open_directory = open_directory; |
| tree_editor->add_file = add_file; |
| tree_editor->open_file = open_file; |
| tree_editor->apply_textdelta = apply_textdelta; |
| tree_editor->close_file = close_file; |
| tree_editor->close_directory = close_directory; |
| tree_editor->change_file_prop = change_file_prop; |
| tree_editor->change_dir_prop = change_dir_prop; |
| tree_editor->close_edit = close_edit; |
| tree_editor->absent_directory = absent_directory; |
| tree_editor->absent_file = absent_file; |
| |
| SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, |
| cancel_baton, |
| tree_editor, |
| eb, |
| editor, |
| edit_baton, |
| pool)); |
| |
| /* We don't destroy subpool, as it's managed by the edit baton. */ |
| return SVN_NO_ERROR; |
| } |