| /* |
| * status_editor.c : editor that implement a 'dry run' update |
| * and tweaks status structures accordingly. |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2002 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #include <apr_pools.h> |
| #include <apr_hash.h> |
| |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_delta.h" |
| #include "svn_string.h" |
| #include "svn_path.h" |
| #include "svn_error.h" |
| #include "svn_io.h" |
| #include "svn_hash.h" |
| #include "svn_wc.h" |
| #include "svn_private_config.h" |
| |
| #include "wc.h" |
| |
| |
| |
| struct edit_baton |
| { |
| /* For status, the "destination" of the edit and whether to honor |
| any paths that are 'below'. */ |
| svn_stringbuf_t *path; |
| svn_boolean_t descend; |
| |
| /* The youngest revision in the repository. This is a reference |
| because this editor returns youngest rev to the driver directly, |
| as well as in each statushash entry. */ |
| svn_revnum_t *youngest_revision; |
| |
| /* The hash of status structures we're editing. */ |
| apr_hash_t *statushash; |
| |
| /* The pool that will be used to add new structures to the hash, |
| presumably the same one it's already been using. */ |
| apr_pool_t *hashpool; |
| |
| /* The pool which the editor uses for the whole tree-walk.*/ |
| apr_pool_t *pool; |
| }; |
| |
| |
| |
| |
| |
| /*** Helper ***/ |
| |
| |
| /* Look up the key PATH in EDIT_BATON->STATUSHASH. |
| |
| If the value doesn't yet exist, create a new status struct using |
| EDIT_BATON->HASHPOOL. |
| |
| Set the status structure's "network" fields to REPOS_TEXT_STATUS, |
| REPOS_PROP_STATUS. If either of these fields is 0, it will be |
| ignored. */ |
| static svn_error_t * |
| tweak_statushash (void *edit_baton, |
| const char *path, |
| enum svn_wc_status_kind repos_text_status, |
| enum svn_wc_status_kind repos_prop_status) |
| { |
| svn_wc_status_t *statstruct; |
| struct edit_baton *eb = (struct edit_baton *) edit_baton; |
| apr_hash_t *statushash = eb->statushash; |
| apr_pool_t *pool = eb->hashpool; |
| |
| /* If you want temporary debugging info... */ |
| /* { |
| apr_hash_index_t *hi; |
| char buf[200]; |
| |
| printf("---Tweaking statushash: editing path `%s'\n", path); |
| |
| for (hi = apr_hash_first (pool, statushash); |
| hi; |
| hi = apr_hash_next (hi)) |
| { |
| const void *key; |
| void *val; |
| apr_ssize_t klen; |
| |
| apr_hash_this (hi, &key, &klen, &val); |
| snprintf(buf, klen+1, (const char *)key); |
| printf(" %s\n", buf); |
| } |
| fflush(stdout); |
| } |
| */ |
| |
| /* Is PATH already a hash-key? */ |
| statstruct = (svn_wc_status_t *) apr_hash_get (statushash, path, |
| APR_HASH_KEY_STRING); |
| /* If not, make it so. */ |
| if (! statstruct) |
| { |
| svn_stringbuf_t *pathkey = svn_stringbuf_create (path, pool); |
| |
| /* Use the public API to get a statstruct: */ |
| SVN_ERR (svn_wc_status (&statstruct, pathkey, pool)); |
| |
| /* Put the path/struct into the hash. */ |
| apr_hash_set (statushash, pathkey->data, pathkey->len, statstruct); |
| } |
| |
| /* Tweak the structure's repos fields. */ |
| if (repos_text_status) |
| statstruct->repos_text_status = repos_text_status; |
| if (repos_prop_status) |
| statstruct->repos_prop_status = repos_prop_status; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| |
| /*** batons ***/ |
| |
| struct dir_baton |
| { |
| /* The path to this directory. */ |
| svn_stringbuf_t *path; |
| |
| /* Basename of this directory. */ |
| svn_stringbuf_t *name; |
| |
| /* The global edit baton. */ |
| struct edit_baton *edit_baton; |
| |
| /* Baton for this directory's parent, or NULL if this is the root |
| directory. */ |
| struct dir_baton *parent_baton; |
| |
| /* 'svn status' shouldn't print status lines for things that are |
| added; we're only interest in asking if objects that the user |
| *already* has are up-to-date or not. Thus if this flag is set, |
| the next two will be ignored. :-) */ |
| svn_boolean_t added; |
| |
| /* Gets set iff there's a change to this directory's properties, to |
| guide us when syncing adm files later. */ |
| svn_boolean_t prop_changed; |
| |
| /* This means (in terms of 'svn status') that some child was deleted |
| or added to the directory */ |
| svn_boolean_t text_changed; |
| |
| /* The pool in which this baton itself is allocated. */ |
| apr_pool_t *pool; |
| }; |
| |
| |
| |
| /* Create a new dir_baton for subdir PATH. */ |
| static struct dir_baton * |
| make_dir_baton (const char *path, |
| struct edit_baton *edit_baton, |
| struct dir_baton *parent_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct edit_baton *eb = edit_baton; |
| struct dir_baton *d = apr_pcalloc (pool, sizeof (*d)); |
| svn_stringbuf_t *full_path = svn_stringbuf_dup (eb->path, pool); |
| |
| /* Don't do this. Just do NOT do this to me. */ |
| if (pb && (! path)) |
| abort(); |
| |
| /* Construct the full path of this directory. */ |
| if (pb) |
| svn_path_add_component_nts (full_path, path); |
| |
| /* Finish populating the baton members. */ |
| d->path = full_path; |
| d->name = path ? svn_stringbuf_create (svn_path_basename (path, |
| pool), |
| pool) : NULL; |
| d->edit_baton = edit_baton; |
| d->parent_baton = parent_baton; |
| d->pool = pool; |
| |
| return d; |
| } |
| |
| |
| struct file_baton |
| { |
| /* The global edit baton. */ |
| struct edit_baton *edit_baton; |
| |
| /* Baton for this file's parent directory. */ |
| struct dir_baton *dir_baton; |
| |
| /* Pool specific to this file_baton. */ |
| apr_pool_t *pool; |
| |
| /* Name of this file (its entry in the directory). */ |
| const svn_stringbuf_t *name; |
| |
| /* Path to this file, either abs or relative to the change-root. */ |
| svn_stringbuf_t *path; |
| |
| /* 'svn status' shouldn't print status lines for things that are |
| added; we're only interest in asking if objects that the user |
| *already* has are up-to-date or not. Thus if this flag is set, |
| the next two will be ignored. :-) */ |
| svn_boolean_t added; |
| |
| /* This gets set if the file underwent a text change, which guides |
| the code that syncs up the adm dir and working copy. */ |
| svn_boolean_t text_changed; |
| |
| /* This gets set if the file underwent a prop change, which guides |
| the code that syncs up the adm dir and working copy. */ |
| svn_boolean_t prop_changed; |
| |
| }; |
| |
| |
| /* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool. |
| NAME is just one component, not a path. */ |
| static struct file_baton * |
| make_file_baton (struct dir_baton *parent_dir_baton, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *pb = parent_dir_baton; |
| struct edit_baton *eb = pb->edit_baton; |
| struct file_baton *f = apr_pcalloc (pool, sizeof (*f)); |
| svn_stringbuf_t *full_path = svn_stringbuf_dup (eb->path, pool); |
| |
| /* Construct the full path of this directory. */ |
| if (pb) |
| svn_path_add_component_nts (full_path, path); |
| |
| /* Finish populating the baton members. */ |
| f->path = full_path; |
| f->name = svn_stringbuf_create (svn_path_basename (path, pool), |
| pool); |
| f->pool = pool; |
| f->dir_baton = pb; |
| f->edit_baton = eb; |
| |
| return f; |
| } |
| |
| |
| /*----------------------------------------------------------------------*/ |
| |
| /*** The callbacks we'll plug into an svn_delta_edit_fns_t structure. ***/ |
| |
| static svn_error_t * |
| set_target_revision (void *edit_baton, |
| svn_revnum_t target_revision) |
| { |
| struct edit_baton *eb = edit_baton; |
| *(eb->youngest_revision) = target_revision; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| open_root (void *edit_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **dir_baton) |
| { |
| struct edit_baton *eb = edit_baton; |
| *dir_baton = make_dir_baton (NULL, eb, NULL, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| delete_entry (const char *path, |
| svn_revnum_t revision, |
| void *parent_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *db = parent_baton; |
| struct edit_baton *eb = db->edit_baton; |
| apr_hash_t *entries; |
| svn_stringbuf_t *full_path = svn_stringbuf_dup (eb->path, pool); |
| const char *name = svn_path_basename (path, pool); |
| |
| svn_path_add_component_nts (full_path, path); |
| |
| /* Note: when something is deleted, it's okay to tweak the |
| statushash immediately. No need to wait until close_file or |
| close_dir, because there's no risk of having to honor the 'added' |
| flag. We already know this item exists in the working copy. */ |
| |
| /* Read the parent's entries file. If the deleted thing is not |
| versioned in this working copy, it was probably deleted via this |
| working copy. No need to report such a thing. */ |
| /* ### use svn_wc_entry() instead? */ |
| SVN_ERR (svn_wc_entries_read (&entries, db->path, pool)); |
| if (apr_hash_get (entries, name, APR_HASH_KEY_STRING)) |
| SVN_ERR (tweak_statushash (db->edit_baton, |
| full_path->data, |
| svn_wc_status_deleted, 0)); |
| |
| /* Mark the parent dir regardless -- it lost an entry. */ |
| SVN_ERR (tweak_statushash (db->edit_baton, |
| db->path->data, |
| svn_wc_status_modified, 0)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| 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 dir_baton *new_db; |
| |
| new_db = make_dir_baton (path, pb->edit_baton, pb, pool); |
| |
| /* Make this dir as added. */ |
| new_db->added = TRUE; |
| |
| /* Mark the parent as changed; it gained an entry. */ |
| pb->text_changed = TRUE; |
| |
| *child_baton = new_db; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| 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; |
| *child_baton = make_dir_baton (path, pb->edit_baton, pb, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| 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; |
| if (svn_wc_is_normal_prop (name)) |
| db->prop_changed = TRUE; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| static svn_error_t * |
| close_directory (void *dir_baton) |
| { |
| struct dir_baton *db = dir_baton; |
| |
| /* If nothing has changed, return. */ |
| if (! (db->added || db->prop_changed || db->text_changed)) |
| return SVN_NO_ERROR; |
| |
| /* If this directory was added, add the directory to the status hash. */ |
| if (db->added) |
| SVN_ERR (tweak_statushash (db->edit_baton, |
| db->path->data, |
| svn_wc_status_added, |
| db->prop_changed ? svn_wc_status_added : 0)); |
| |
| /* Else, mark the existing directory in the statushash. */ |
| else |
| SVN_ERR (tweak_statushash (db->edit_baton, |
| db->path->data, |
| db->text_changed ? svn_wc_status_modified : 0, |
| db->prop_changed ? svn_wc_status_modified : 0)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| 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 *new_fb = make_file_baton (pb, path, pool); |
| |
| /* Mark parent dir as changed */ |
| pb->text_changed = TRUE; |
| |
| /* Make this file as added. */ |
| new_fb->added = TRUE; |
| |
| *file_baton = new_fb; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| 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 *new_fb = make_file_baton (pb, path, pool); |
| |
| /* Mark parent dir as changed */ |
| pb->text_changed = TRUE; |
| |
| *file_baton = new_fb; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| apply_textdelta (void *file_baton, |
| svn_txdelta_window_handler_t *handler, |
| void **handler_baton) |
| { |
| struct file_baton *fb = file_baton; |
| |
| /* Mark file as having textual mods. */ |
| fb->text_changed = TRUE; |
| |
| /* Send back a NULL window handler -- we don't need the actual diffs. */ |
| *handler_baton = NULL; |
| *handler = NULL; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| 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 *fb = file_baton; |
| if (svn_wc_is_normal_prop (name)) |
| fb->prop_changed = TRUE; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| close_file (void *file_baton) |
| { |
| struct file_baton *fb = file_baton; |
| |
| /* If nothing has changed, return. */ |
| if (! (fb->added || fb->prop_changed || fb->text_changed)) |
| return SVN_NO_ERROR; |
| |
| /* If this is a new file, add it to the statushash. */ |
| if (fb->added) |
| SVN_ERR (tweak_statushash (fb->edit_baton, |
| fb->path->data, |
| svn_wc_status_added, |
| fb->prop_changed ? svn_wc_status_added : 0)); |
| /* Else, mark the existing file in the statushash. */ |
| else |
| SVN_ERR (tweak_statushash (fb->edit_baton, |
| fb->path->data, |
| fb->text_changed ? svn_wc_status_modified : 0, |
| fb->prop_changed ? svn_wc_status_modified : 0)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| close_edit (void *edit_baton) |
| { |
| /* The edit is over, free its pool. */ |
| svn_pool_destroy (((struct edit_baton *) edit_baton)->pool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /*** Returning editors. ***/ |
| |
| |
| /*** Public API ***/ |
| |
| svn_error_t * |
| svn_wc_get_status_editor (const svn_delta_editor_t **editor, |
| void **edit_baton, |
| svn_stringbuf_t *path, |
| svn_boolean_t descend, |
| apr_hash_t *statushash, |
| svn_revnum_t *youngest, |
| apr_pool_t *pool) |
| { |
| struct edit_baton *eb; |
| svn_stringbuf_t *anchor, *target, *tempbuf; |
| apr_pool_t *subpool = svn_pool_create (pool); |
| svn_delta_editor_t *tree_editor = svn_delta_default_editor (pool); |
| |
| /* Construct an edit baton. */ |
| eb = apr_palloc (subpool, sizeof (*eb)); |
| eb->pool = subpool; |
| eb->hashpool = pool; |
| eb->statushash = statushash; |
| eb->descend = descend; |
| eb->youngest_revision = youngest; |
| |
| /* Anchor target analysis, to make this editor able to match |
| hash-keys already in the hash. (svn_wc_statuses is ignorant of |
| anchor/target issues.) */ |
| SVN_ERR (svn_wc_get_actual_target (path, &anchor, &target, pool)); |
| tempbuf = svn_stringbuf_dup (anchor, pool); |
| if (target) |
| svn_path_add_component (tempbuf, target); |
| |
| if (! svn_stringbuf_compare (path, tempbuf)) |
| eb->path = svn_stringbuf_create ("", pool); |
| else |
| eb->path = anchor; |
| |
| /* Construct an editor. */ |
| 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->change_dir_prop = change_dir_prop; |
| tree_editor->close_directory = close_directory; |
| tree_editor->add_file = add_file; |
| tree_editor->open_file = open_file; |
| tree_editor->apply_textdelta = apply_textdelta; |
| tree_editor->change_file_prop = change_file_prop; |
| tree_editor->close_file = close_file; |
| tree_editor->close_edit = close_edit; |
| |
| *edit_baton = eb; |
| *editor = tree_editor; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: |
| */ |
| |