2 * update_editor.c : main editor for checkouts and updates
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
25 #include <apr_pools.h>
28 #include <apr_tables.h>
29 #include <apr_file_io.h>
30 #include <apr_strings.h>
32 #include "svn_types.h"
33 #include "svn_pools.h"
34 #include "svn_delta.h"
35 #include "svn_string.h"
38 #include "svn_error.h"
41 #include "svn_private_config.h"
43 #include "svn_config.h"
46 #include "questions.h"
48 #include "adm_files.h"
53 #include "translate.h"
55 #include "private/svn_wc_private.h"
58 /** Forward declarations **/
60 static svn_error_t
*add_file_with_history(const char *path
,
62 const char *copyfrom_path
,
63 svn_revnum_t copyfrom_rev
,
72 /* For updates, the "destination" of the edit is the ANCHOR (the
73 directory at which the edit is rooted) plus the TARGET (the
74 actual thing we wish to update). For checkouts, ANCHOR holds the
75 whole path, and TARGET is unused. */
79 /* ADM_ACCESS is an access baton that includes the ANCHOR directory */
80 svn_wc_adm_access_t
*adm_access
;
82 /* Array of file extension patterns to preserve as extensions in
83 generated conflict files. */
84 apr_array_header_t
*ext_patterns
;
86 /* The revision we're targeting...or something like that. This
87 starts off as a pointer to the revision to which we are updating,
88 or SVN_INVALID_REVNUM, but by the end of the edit, should be
89 pointing to the final revision. */
90 svn_revnum_t
*target_revision
;
92 /* The requested depth of this edit. */
93 svn_depth_t requested_depth
;
95 /* Is the requested depth merely an operational limitation, or is
96 also the new sticky ambient depth of the update target? */
97 svn_boolean_t depth_is_sticky
;
99 /* Need to know if the user wants us to overwrite the 'now' times on
100 edited/added files with the last-commit-time. */
101 svn_boolean_t use_commit_times
;
103 /* Was the root actually opened (was this a non-empty edit)? */
104 svn_boolean_t root_opened
;
106 /* Was the update-target deleted? This is a special situation. */
107 svn_boolean_t target_deleted
;
109 /* Allow unversioned obstructions when adding a path. */
110 svn_boolean_t allow_unver_obstructions
;
112 /* Non-null if this is a 'switch' operation. */
113 const char *switch_url
;
115 /* The URL to the root of the repository, or NULL. */
118 /* External diff3 to use for merges (can be null, in which case
119 internal merge code is used). */
120 const char *diff3_cmd
;
122 /* Object for gathering info to be accessed after the edit is
124 svn_wc_traversal_info_t
*traversal_info
;
126 /* This editor sends back notifications as it edits. */
127 svn_wc_notify_func2_t notify_func
;
130 /* This editor is normally wrapped in a cancellation editor anyway,
131 so it doesn't bother to check for cancellation itself. However,
132 it needs a cancel_func and cancel_baton available to pass to
133 long-running functions. */
134 svn_cancel_func_t cancel_func
;
137 /* This editor will invoke a interactive conflict-resolution
138 callback, if available. */
139 svn_wc_conflict_resolver_func_t conflict_func
;
140 void *conflict_baton
;
142 /* If the server sends add_file(copyfrom=...) and we don't have the
143 copyfrom file in the working copy, we use this callback to fetch
144 it directly from the repository. */
145 svn_wc_get_file_t fetch_func
;
148 /* Paths that were skipped during the edit, and therefore shouldn't have
149 their revision/url info updated at the end.
150 The keys are pathnames and the values unspecified. */
151 apr_hash_t
*skipped_paths
;
159 /* The path to this directory. */
162 /* Basename of this directory. */
165 /* The repository URL this directory will correspond to. */
168 /* The global edit baton. */
169 struct edit_baton
*edit_baton
;
171 /* Baton for this directory's parent, or NULL if this is the root
173 struct dir_baton
*parent_baton
;
175 /* Gets set iff this is a new directory that is not yet versioned and not
176 yet in the parent's list of entries */
179 /* Set if an unversioned dir of the same name already existed in
181 svn_boolean_t existed
;
183 /* Set if a dir of the same name already exists and is
184 scheduled for addition without history. */
185 svn_boolean_t add_existed
;
187 /* An array of svn_prop_t structures, representing all the property
188 changes to be applied to this directory. */
189 apr_array_header_t
*propchanges
;
191 /* The bump information for this directory. */
192 struct bump_dir_info
*bump_info
;
194 /* The current log file number. */
197 /* The current log buffer. The content of this accumulator may be
198 flushed and run at any time (in pool cleanup), so only append
199 complete sets of operations to it; you may need to build up a
200 buffer of operations and append it atomically with
201 svn_stringbuf_appendstr. */
202 svn_stringbuf_t
*log_accum
;
204 /* The depth of the directory in the wc (or inferred if added). Not
205 used for filtering; we have a separate wrapping editor for that. */
206 svn_depth_t ambient_depth
;
208 /* The pool in which this baton itself is allocated. */
213 /* The bump information is tracked separately from the directory batons.
214 This is a small structure kept in the edit pool, while the heavier
215 directory baton is managed by the editor driver.
217 In a postfix delta case, the directory batons are going to disappear.
218 The files will refer to these structures, rather than the full
222 /* ptr to the bump information for the parent directory */
223 struct bump_dir_info
*parent
;
225 /* how many entries are referring to this bump information? */
228 /* the path of the directory to bump */
231 /* Set if this directory is skipped due to prop conflicts.
232 This does NOT mean that children are skipped. */
233 svn_boolean_t skipped
;
241 svn_txdelta_window_handler_t apply_handler
;
244 struct file_baton
*fb
;
248 /* Return the url for NAME in DIR, allocated in POOL, or null if
249 * unable to obtain a url. If NAME is null, get the url for DIR.
251 * Use ASSOCIATED_ACCESS to retrieve an access baton for PATH, and do
252 * all temporary allocation in POOL.
255 get_entry_url(svn_wc_adm_access_t
*associated_access
,
261 const svn_wc_entry_t
*entry
;
262 svn_wc_adm_access_t
*adm_access
;
264 err
= svn_wc_adm_retrieve(&adm_access
, associated_access
, dir
, pool
);
268 /* Note that `name' itself may be NULL. */
269 err
= svn_wc_entry(&entry
, svn_path_join_many(pool
, dir
, name
, NULL
),
270 adm_access
, FALSE
, pool
);
272 if (err
|| (! entry
) || (! entry
->url
))
274 svn_error_clear(err
);
281 /* Flush accumulated log entries to a log file on disk for DIR_BATON and
282 * increase the log number of the dir baton.
283 * Use POOL for temporary allocations. */
285 flush_log(struct dir_baton
*db
, apr_pool_t
*pool
)
287 if (! svn_stringbuf_isempty(db
->log_accum
))
289 svn_wc_adm_access_t
*adm_access
;
291 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, db
->edit_baton
->adm_access
,
293 SVN_ERR(svn_wc__write_log(adm_access
, db
->log_number
, db
->log_accum
,
296 svn_stringbuf_setempty(db
->log_accum
);
302 /* An APR pool cleanup handler. This runs the log file for a
305 cleanup_dir_baton(void *dir_baton
)
307 struct dir_baton
*db
= dir_baton
;
309 apr_status_t apr_err
;
310 svn_wc_adm_access_t
*adm_access
;
311 apr_pool_t
*pool
= apr_pool_parent_get(db
->pool
);
313 err
= flush_log(db
, pool
);
314 if (! err
&& db
->log_number
> 0)
316 err
= svn_wc_adm_retrieve(&adm_access
, db
->edit_baton
->adm_access
,
321 err
= svn_wc__run_log(adm_access
, NULL
, pool
);
329 apr_err
= err
->apr_err
;
331 apr_err
= APR_SUCCESS
;
332 svn_error_clear(err
);
336 /* An APR pool cleanup handler. This is a child handler, it removes
337 the mail pool handler. */
339 cleanup_dir_baton_child(void *dir_baton
)
341 struct dir_baton
*db
= dir_baton
;
342 apr_pool_cleanup_kill(db
->pool
, db
, cleanup_dir_baton
);
347 /* Return a new dir_baton to represent NAME (a subdirectory of
348 PARENT_BATON). If PATH is NULL, this is the root directory of the
351 make_dir_baton(struct dir_baton
**d_p
,
353 struct edit_baton
*eb
,
354 struct dir_baton
*pb
,
359 struct bump_dir_info
*bdi
;
361 /* Don't do this. Just do NOT do this to me. */
365 /* Okay, no easy out, so allocate and initialize a dir baton. */
366 d
= apr_pcalloc(pool
, sizeof(*d
));
368 /* Construct the PATH and baseNAME of this directory. */
369 d
->path
= apr_pstrdup(pool
, eb
->anchor
);
372 d
->path
= svn_path_join(d
->path
, path
, pool
);
373 d
->name
= svn_path_basename(path
, pool
);
380 /* Figure out the new_URL for this directory. */
383 /* Switches are, shall we say, complex. If this directory is
384 the root directory (it has no parent), then it either gets
385 the SWITCH_URL for its own (if it is both anchor and target)
386 or the parent of the SWITCH_URL (if it is anchor, but there's
390 if (! *eb
->target
) /* anchor is also target */
391 d
->new_URL
= apr_pstrdup(pool
, eb
->switch_url
);
393 d
->new_URL
= svn_path_dirname(eb
->switch_url
, pool
);
395 /* Else this directory is *not* the root (has a parent). If it
396 is the target (there is a target, and this directory has no
397 grandparent), then it gets the SWITCH_URL for its own.
398 Otherwise, it gets a child of its parent's URL. */
401 if (*eb
->target
&& (! pb
->parent_baton
))
402 d
->new_URL
= apr_pstrdup(pool
, eb
->switch_url
);
404 d
->new_URL
= svn_path_url_add_component(pb
->new_URL
,
408 else /* must be an update */
410 /* updates are the odds ones. if we're updating a path already
411 present on disk, we use its original URL. otherwise, we'll
412 telescope based on its parent's URL. */
413 d
->new_URL
= get_entry_url(eb
->adm_access
, d
->path
, NULL
, pool
);
414 if ((! d
->new_URL
) && pb
)
415 d
->new_URL
= svn_path_url_add_component(pb
->new_URL
, d
->name
, pool
);
418 /* the bump information lives in the edit pool */
419 bdi
= apr_palloc(eb
->pool
, sizeof(*bdi
));
420 bdi
->parent
= pb
? pb
->bump_info
: NULL
;
422 bdi
->path
= apr_pstrdup(eb
->pool
, d
->path
);
423 bdi
->skipped
= FALSE
;
425 /* the parent's bump info has one more referer */
427 ++bdi
->parent
->ref_count
;
430 d
->parent_baton
= pb
;
431 d
->pool
= svn_pool_create(pool
);
432 d
->propchanges
= apr_array_make(pool
, 1, sizeof(svn_prop_t
));
435 d
->add_existed
= FALSE
;
438 d
->log_accum
= svn_stringbuf_create("", pool
);
440 /* The caller of this function needs to fill this in. */
441 d
->ambient_depth
= svn_depth_unknown
;
443 apr_pool_cleanup_register(d
->pool
, d
, cleanup_dir_baton
,
444 cleanup_dir_baton_child
);
452 /* Helper for maybe_bump_dir_info():
454 In a single atomic action, (1) remove any 'deleted' entries from a
455 directory, (2) remove any 'absent' entries whose revision numbers
456 are different from the parent's new target revision, (3) remove any
457 'missing' dir entries, and (4) remove the directory's 'incomplete'
460 complete_directory(struct edit_baton
*eb
,
462 svn_boolean_t is_root_dir
,
465 svn_wc_adm_access_t
*adm_access
;
467 svn_wc_entry_t
*entry
;
468 apr_hash_index_t
*hi
;
470 svn_wc_entry_t
*current_entry
;
473 /* If this is the root directory and there is a target, we can't
474 mark this directory complete. */
475 if (is_root_dir
&& *eb
->target
)
478 /* All operations are on the in-memory entries hash. */
479 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
, path
, pool
));
480 SVN_ERR(svn_wc_entries_read(&entries
, adm_access
, TRUE
, pool
));
482 /* Mark THIS_DIR complete. */
483 entry
= apr_hash_get(entries
, SVN_WC_ENTRY_THIS_DIR
, APR_HASH_KEY_STRING
);
485 return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND
, NULL
,
486 _("No '.' entry in: '%s'"),
487 svn_path_local_style(path
, pool
));
488 entry
->incomplete
= FALSE
;
490 /* After a depth upgrade the entry must reflect the new depth.
491 Upgrading to infinity changes the depth of *all* directories,
492 upgrading to something else only changes the target. */
493 if (eb
->depth_is_sticky
&&
494 (eb
->requested_depth
== svn_depth_infinity
495 || (strcmp(path
, svn_path_join(eb
->anchor
, eb
->target
, pool
)) == 0
496 && eb
->requested_depth
> entry
->depth
)))
497 entry
->depth
= eb
->requested_depth
;
499 /* Remove any deleted or missing entries. */
500 subpool
= svn_pool_create(pool
);
501 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
506 svn_pool_clear(subpool
);
507 apr_hash_this(hi
, &key
, NULL
, &val
);
511 /* Any entry still marked as deleted (and not schedule add) can now
512 be removed -- if it wasn't undeleted by the update, then it
513 shouldn't stay in the updated working set. Schedule add items
516 if (current_entry
->deleted
)
518 if (current_entry
->schedule
!= svn_wc_schedule_add
)
519 svn_wc__entry_remove(entries
, name
);
522 svn_wc_entry_t tmpentry
;
523 tmpentry
.deleted
= FALSE
;
524 SVN_ERR(svn_wc__entry_modify(adm_access
, current_entry
->name
,
526 SVN_WC__ENTRY_MODIFY_DELETED
,
530 /* An absent entry might have been reconfirmed as absent, and the way
531 we can tell is by looking at its revision number: a revision
532 number different from the target revision of the update means the
533 update never mentioned the item, so the entry should be
535 else if (current_entry
->absent
536 && (current_entry
->revision
!= *(eb
->target_revision
)))
538 svn_wc__entry_remove(entries
, name
);
540 else if (current_entry
->kind
== svn_node_dir
)
542 const char *child_path
= svn_path_join(path
, name
, subpool
);
544 if ((svn_wc__adm_missing(adm_access
, child_path
))
545 && (! current_entry
->absent
)
546 && (current_entry
->schedule
!= svn_wc_schedule_add
))
548 svn_wc__entry_remove(entries
, name
);
551 svn_wc_notify_t
*notify
552 = svn_wc_create_notify(child_path
,
553 svn_wc_notify_update_delete
,
555 notify
->kind
= current_entry
->kind
;
556 (* eb
->notify_func
)(eb
->notify_baton
, notify
, subpool
);
562 svn_pool_destroy(subpool
);
564 /* An atomic write of the whole entries file. */
565 SVN_ERR(svn_wc__entries_write(entries
, adm_access
, pool
));
572 /* Decrement the bump_dir_info's reference count. If it hits zero,
573 then this directory is "done". This means it is safe to remove the
574 'incomplete' flag attached to the THIS_DIR entry.
576 In addition, when the directory is "done", we loop onto the parent's
577 bump information to possibly mark it as done, too.
580 maybe_bump_dir_info(struct edit_baton
*eb
,
581 struct bump_dir_info
*bdi
,
584 /* Keep moving up the tree of directories until we run out of parents,
585 or a directory is not yet "done". */
586 for ( ; bdi
!= NULL
; bdi
= bdi
->parent
)
588 if (--bdi
->ref_count
> 0)
589 return SVN_NO_ERROR
; /* directory isn't done yet */
591 /* The refcount is zero, so we remove any 'dead' entries from
592 the directory and mark it 'complete'. */
594 SVN_ERR(complete_directory(eb
, bdi
->path
,
595 bdi
->parent
? FALSE
: TRUE
, pool
));
597 /* we exited the for loop because there are no more parents */
604 /* The global edit baton. */
605 struct edit_baton
*edit_baton
;
607 /* The parent directory of this file. */
608 struct dir_baton
*dir_baton
;
610 /* Pool specific to this file_baton. */
613 /* Name of this file (its entry in the directory). */
616 /* Path to this file, either abs or relative to the change-root. */
619 /* The repository URL this file will correspond to. */
622 /* Set if this file is new. */
625 /* Set if this file is new with history. */
626 svn_boolean_t added_with_history
;
628 /* Set if this file is skipped because it was in conflict. */
629 svn_boolean_t skipped
;
631 /* Set if an unversioned file of the same name already existed in
633 svn_boolean_t existed
;
635 /* Set if a file of the same name already exists and is
636 scheduled for addition without history. */
637 svn_boolean_t add_existed
;
639 /* The path to the current text base, if any.
640 This gets set if there are file content changes. */
641 const char *text_base_path
;
643 /* The path to the incoming text base (that is, to a text-base-file-
644 in-progress in the tmp area). This gets set if there are file
646 const char *new_text_base_path
;
648 /* If this file was added with history, this is the path to a copy
649 of the text base of the copyfrom file (in the temporary area). */
650 const char *copied_text_base
;
652 /* If this file was added with history, and the copyfrom had local
653 mods, this is the path to a copy of the user's version with local
654 mods (in the temporary area). */
655 const char *copied_working_text
;
657 /* If this file was added with history, this hash contains the base
658 properties of the copied file. */
659 apr_hash_t
*copied_base_props
;
661 /* If this file was added with history, this hash contains the working
662 properties of the copied file. */
663 apr_hash_t
*copied_working_props
;
665 /* Set if we've received an apply_textdelta for this file. */
666 svn_boolean_t received_textdelta
;
668 /* An array of svn_prop_t structures, representing all the property
669 changes to be applied to this file. Once a file baton is
670 initialized, this is never NULL, but it may have zero elements. */
671 apr_array_header_t
*propchanges
;
673 /* The last-changed-date of the file. This is actually a property
674 that comes through as an 'entry prop', and will be used to set
675 the working file's timestamp if it's added. */
676 const char *last_changed_date
;
678 /* Bump information for the directory this file lives in */
679 struct bump_dir_info
*bump_info
;
681 /* This is initialized to all zeroes when the baton is created, then
682 populated with the MD5 digest of the resultant fulltext after the
683 last window is handled by the handler returned from
684 apply_textdelta(). */
685 unsigned char digest
[APR_MD5_DIGESTSIZE
];
689 /* Make a new file baton in the provided POOL, with PB as the parent baton.
690 PATH is relative to the root of the edit. */
693 make_file_baton(struct file_baton
**f_p
,
694 struct dir_baton
*pb
,
696 svn_boolean_t adding
,
699 struct file_baton
*f
= apr_pcalloc(pool
, sizeof(*f
));
701 /* I rather need this information, yes. */
705 /* Make the file's on-disk name. */
706 f
->path
= svn_path_join(pb
->edit_baton
->anchor
, path
, pool
);
707 f
->name
= svn_path_basename(path
, pool
);
709 /* Figure out the new_URL for this file. */
710 if (pb
->edit_baton
->switch_url
)
712 f
->new_URL
= svn_path_url_add_component(pb
->new_URL
, f
->name
, pool
);
716 f
->new_URL
= get_entry_url(pb
->edit_baton
->adm_access
,
717 pb
->path
, f
->name
, pool
);
721 f
->edit_baton
= pb
->edit_baton
;
722 f
->propchanges
= apr_array_make(pool
, 1, sizeof(svn_prop_t
));
723 f
->bump_info
= pb
->bump_info
;
726 f
->add_existed
= FALSE
;
729 /* No need to initialize f->digest, since we used pcalloc(). */
731 /* the directory's bump info has one more referer now */
732 ++f
->bump_info
->ref_count
;
740 /*** Helpers for the editor callbacks. ***/
743 window_handler(svn_txdelta_window_t
*window
, void *baton
)
745 struct handler_baton
*hb
= baton
;
746 struct file_baton
*fb
= hb
->fb
;
747 svn_error_t
*err
, *err2
;
749 /* Apply this window. We may be done at that point. */
750 err
= hb
->apply_handler(window
, hb
->apply_baton
);
751 if (window
!= NULL
&& !err
)
754 /* Either we're done (window is NULL) or we had an error. In either
755 case, clean up the handler. */
758 if (fb
->copied_text_base
)
759 err2
= svn_io_file_close(hb
->source
, hb
->pool
);
761 err2
= svn_wc__close_text_base(hb
->source
, fb
->path
, 0, hb
->pool
);
766 svn_error_clear(err2
);
768 err2
= svn_wc__close_text_base(hb
->dest
, fb
->path
, 0, hb
->pool
);
774 svn_error_clear(err2
);
779 /* We failed to apply the delta; clean up the temporary file. */
780 svn_error_clear(svn_io_remove_file(fb
->new_text_base_path
, hb
->pool
));
781 fb
->new_text_base_path
= NULL
;
784 svn_pool_destroy(hb
->pool
);
790 /* Prepare directory for dir_baton DB for updating or checking out.
791 * Give it depth DEPTH.
793 * If the path already exists, but is not a working copy for
794 * ANCESTOR_URL and ANCESTOR_REVISION, then an error will be returned.
797 prep_directory(struct dir_baton
*db
,
798 const char *ancestor_url
,
799 svn_revnum_t ancestor_revision
,
804 /* Make sure the directory exists. */
805 SVN_ERR(svn_wc__ensure_directory(db
->path
, pool
));
807 /* Use the repository root of the anchor, but only if it actually is an
808 ancestor of the URL of this directory. */
809 if (db
->edit_baton
->repos
810 && svn_path_is_ancestor(db
->edit_baton
->repos
, ancestor_url
))
811 repos
= db
->edit_baton
->repos
;
815 /* Make sure it's the right working copy, either by creating it so,
816 or by checking that it is so already. */
817 SVN_ERR(svn_wc_ensure_adm3(db
->path
, NULL
,
819 ancestor_revision
, db
->ambient_depth
, pool
));
821 if (! db
->edit_baton
->adm_access
822 || strcmp(svn_wc_adm_access_path(db
->edit_baton
->adm_access
),
825 svn_wc_adm_access_t
*adm_access
;
826 apr_pool_t
*adm_access_pool
827 = db
->edit_baton
->adm_access
828 ? svn_wc_adm_access_pool(db
->edit_baton
->adm_access
)
829 : db
->edit_baton
->pool
;
830 svn_error_t
*err
= svn_wc_adm_open3(&adm_access
,
831 db
->edit_baton
->adm_access
,
832 db
->path
, TRUE
, 0, NULL
, NULL
,
835 /* db->path may be scheduled for addition without history.
836 In that case db->edit_baton->adm_access already has it locked. */
837 if (err
&& err
->apr_err
== SVN_ERR_WC_LOCKED
)
839 svn_error_clear(err
);
840 err
= svn_wc_adm_retrieve(&adm_access
,
841 db
->edit_baton
->adm_access
,
842 db
->path
, adm_access_pool
);
847 if (!db
->edit_baton
->adm_access
)
848 db
->edit_baton
->adm_access
= adm_access
;
855 /* Accumulate tags in LOG_ACCUM to set ENTRY_PROPS for PATH.
856 ENTRY_PROPS is an array of svn_prop_t* entry props.
857 If ENTRY_PROPS contains the removal of a lock token, all entryprops
858 related to a lock will be removed and LOCK_STATE, if non-NULL, will be
859 set to svn_wc_notify_lock_state_unlocked. Else, LOCK_STATE, if non-NULL
860 will be set to svn_wc_lock_state_unchanged. */
862 accumulate_entry_props(svn_stringbuf_t
*log_accum
,
863 svn_wc_notify_lock_state_t
*lock_state
,
864 svn_wc_adm_access_t
*adm_access
,
866 apr_array_header_t
*entry_props
,
870 svn_wc_entry_t tmp_entry
;
871 apr_uint64_t flags
= 0;
874 *lock_state
= svn_wc_notify_lock_state_unchanged
;
876 for (i
= 0; i
< entry_props
->nelts
; ++i
)
878 const svn_prop_t
*prop
= &APR_ARRAY_IDX(entry_props
, i
, svn_prop_t
);
881 /* The removal of the lock-token entryprop means that the lock was
883 if (! strcmp(prop
->name
, SVN_PROP_ENTRY_LOCK_TOKEN
))
885 SVN_ERR(svn_wc__loggy_delete_lock
886 (&log_accum
, adm_access
, path
, pool
));
889 *lock_state
= svn_wc_notify_lock_state_unlocked
;
892 /* A prop value of NULL means the information was not
893 available. We don't remove this field from the entries
894 file; we have convention just leave it empty. So let's
895 just skip those entry props that have no values. */
899 val
= prop
->value
->data
;
901 if (! strcmp(prop
->name
, SVN_PROP_ENTRY_LAST_AUTHOR
))
903 flags
|= SVN_WC__ENTRY_MODIFY_CMT_AUTHOR
;
904 tmp_entry
.cmt_author
= val
;
906 else if (! strcmp(prop
->name
, SVN_PROP_ENTRY_COMMITTED_REV
))
908 flags
|= SVN_WC__ENTRY_MODIFY_CMT_REV
;
909 tmp_entry
.cmt_rev
= SVN_STR_TO_REV(val
);
911 else if (! strcmp(prop
->name
, SVN_PROP_ENTRY_COMMITTED_DATE
))
913 flags
|= SVN_WC__ENTRY_MODIFY_CMT_DATE
;
914 SVN_ERR(svn_time_from_cstring(&tmp_entry
.cmt_date
, val
, pool
));
916 else if (! strcmp(prop
->name
, SVN_PROP_ENTRY_UUID
))
918 flags
|= SVN_WC__ENTRY_MODIFY_UUID
;
919 tmp_entry
.uuid
= val
;
924 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum
, adm_access
, path
,
925 &tmp_entry
, flags
, pool
));
931 /* Accumulate tags in LOG_ACCUM to set WCPROPS for PATH. WCPROPS is
932 an array of svn_prop_t* wc props. */
934 accumulate_wcprops(svn_stringbuf_t
*log_accum
,
935 svn_wc_adm_access_t
*adm_access
,
937 apr_array_header_t
*wcprops
,
942 /* ### The log file will rewrite the props file for each property :( It
943 ### would be better if all the changes could be combined into one
945 for (i
= 0; i
< wcprops
->nelts
; ++i
)
947 const svn_prop_t
*prop
= &APR_ARRAY_IDX(wcprops
, i
, svn_prop_t
);
949 SVN_ERR(svn_wc__loggy_modify_wcprop
950 (&log_accum
, adm_access
, path
,
951 prop
->name
, prop
->value
? prop
->value
->data
: NULL
, pool
));
958 /* Check that when ADD_PATH is joined to BASE_PATH, the resulting path
959 * is still under BASE_PATH in the local filesystem. If not, return
960 * SVN_ERR_WC_OBSTRUCTED_UPDATE; else return success.
962 * This is to prevent the situation where the repository contains,
963 * say, "..\nastyfile". Although that's perfectly legal on some
964 * systems, when checked out onto Win32 it would cause "nastyfile" to
965 * be created in the parent of the current edit directory.
967 * (http://cve.mitre.org/cgi-bin/cvename.cgi?name=2007-3846)
970 check_path_under_root(const char *base_path
,
971 const char *add_path
,
975 apr_status_t path_status
;
977 path_status
= apr_filepath_merge
978 (&full_path
, base_path
, add_path
,
979 APR_FILEPATH_NOTABOVEROOT
| APR_FILEPATH_SECUREROOTTEST
,
982 if (path_status
!= APR_SUCCESS
)
984 return svn_error_createf
985 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
986 _("Path '%s' is not in the working copy"),
987 /* Not using full_path here because it might be NULL or
988 undefined, since apr_filepath_merge() returned error.
989 (Pity we can't pass NULL for &full_path in the first place,
990 but the APR docs don't bless that.) */
991 svn_path_local_style(svn_path_join(base_path
, add_path
, pool
), pool
));
998 /*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
1000 static svn_error_t
*
1001 set_target_revision(void *edit_baton
,
1002 svn_revnum_t target_revision
,
1005 struct edit_baton
*eb
= edit_baton
;
1007 /* Stashing a target_revision in the baton */
1008 *(eb
->target_revision
) = target_revision
;
1009 return SVN_NO_ERROR
;
1013 static svn_error_t
*
1014 open_root(void *edit_baton
,
1015 svn_revnum_t base_revision
, /* This is ignored in co */
1019 struct edit_baton
*eb
= edit_baton
;
1020 struct dir_baton
*d
;
1022 /* Note that something interesting is actually happening in this
1024 eb
->root_opened
= TRUE
;
1026 SVN_ERR(make_dir_baton(&d
, NULL
, eb
, NULL
, FALSE
, pool
));
1031 /* For an update with a NULL target, this is equivalent to open_dir(): */
1032 svn_wc_adm_access_t
*adm_access
;
1033 svn_wc_entry_t tmp_entry
;
1034 const svn_wc_entry_t
*entry
;
1035 apr_uint64_t flags
= SVN_WC__ENTRY_MODIFY_REVISION
|
1036 SVN_WC__ENTRY_MODIFY_URL
| SVN_WC__ENTRY_MODIFY_INCOMPLETE
;
1038 /* Read the depth from the entry. */
1039 SVN_ERR(svn_wc_entry(&entry
, d
->path
, eb
->adm_access
,
1042 d
->ambient_depth
= entry
->depth
;
1044 /* Mark directory as being at target_revision, but incomplete. */
1045 tmp_entry
.revision
= *(eb
->target_revision
);
1046 tmp_entry
.url
= d
->new_URL
;
1047 /* See open_directory() for why this check is necessary. */
1048 if (eb
->repos
&& svn_path_is_ancestor(eb
->repos
, d
->new_URL
))
1050 tmp_entry
.repos
= eb
->repos
;
1051 flags
|= SVN_WC__ENTRY_MODIFY_REPOS
;
1053 tmp_entry
.incomplete
= TRUE
;
1054 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
,
1056 SVN_ERR(svn_wc__entry_modify(adm_access
, NULL
/* THIS_DIR */,
1058 TRUE
/* immediate write */,
1062 return SVN_NO_ERROR
;
1066 /* Helper for delete_entry().
1068 Search an error chain (ERR) for evidence that a local mod was left.
1069 If so, cleanup LOGFILE and return an appropriate error. Otherwise,
1070 just return the original error chain.
1072 static svn_error_t
*
1073 leftmod_error_chain(svn_error_t
*err
,
1074 const char *logfile
,
1078 svn_error_t
*tmp_err
;
1081 return SVN_NO_ERROR
;
1083 /* Advance TMP_ERR to the part of the error chain that reveals that
1084 a local mod was left, or to the NULL end of the chain. */
1085 for (tmp_err
= err
; tmp_err
; tmp_err
= tmp_err
->child
)
1086 if (tmp_err
->apr_err
== SVN_ERR_WC_LEFT_LOCAL_MOD
)
1089 /* If we found a "left a local mod" error, wrap and return it.
1090 Otherwise, we just return our top-most error. */
1093 /* Remove the LOGFILE (and eat up errors from this process). */
1094 svn_error_clear(svn_io_remove_file(logfile
, pool
));
1096 return svn_error_createf
1097 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, tmp_err
,
1098 _("Won't delete locally modified directory '%s'"),
1099 svn_path_local_style(path
, pool
));
1106 static svn_error_t
*
1107 do_entry_deletion(struct edit_baton
*eb
,
1108 const char *parent_path
,
1113 svn_wc_adm_access_t
*adm_access
;
1114 const svn_wc_entry_t
*entry
;
1115 const char *full_path
= svn_path_join(eb
->anchor
, path
, pool
);
1116 svn_stringbuf_t
*log_item
= svn_stringbuf_create("", pool
);
1118 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
,
1119 parent_path
, pool
));
1121 SVN_ERR(svn_wc__loggy_delete_entry(&log_item
, adm_access
, full_path
, pool
));
1123 SVN_ERR(svn_wc__entry_versioned(&entry
, full_path
, adm_access
, FALSE
, pool
));
1125 /* If the thing being deleted is the *target* of this update, then
1126 we need to recreate a 'deleted' entry, so that parent can give
1127 accurate reports about itself in the future. */
1128 if (strcmp(path
, eb
->target
) == 0)
1130 svn_wc_entry_t tmp_entry
;
1132 tmp_entry
.revision
= *(eb
->target_revision
);
1134 (entry
->kind
== svn_node_file
) ? svn_node_file
: svn_node_dir
;
1135 tmp_entry
.deleted
= TRUE
;
1137 SVN_ERR(svn_wc__loggy_entry_modify(&log_item
, adm_access
,
1138 full_path
, &tmp_entry
,
1139 SVN_WC__ENTRY_MODIFY_REVISION
1140 | SVN_WC__ENTRY_MODIFY_KIND
1141 | SVN_WC__ENTRY_MODIFY_DELETED
,
1144 eb
->target_deleted
= TRUE
;
1147 SVN_ERR(svn_wc__write_log(adm_access
, *log_number
, log_item
, pool
));
1151 /* The SVN_WC__LOG_DELETE_ENTRY log item will cause
1152 * svn_wc_remove_from_revision_control() to be run. But that
1153 * function checks whether the deletion target's URL is child of
1154 * its parent directory's URL, and if it's not, then the entry
1155 * in parent won't be deleted (because presumably the child
1156 * represents a disjoint working copy, i.e., it is a wc_root).
1158 * However, during a switch this works against us, because by
1159 * the time we get here, the parent's URL has already been
1160 * changed. So we manually remove the child from revision
1161 * control after the delete-entry item has been written in the
1162 * parent's log, but before it is run, so the only work left for
1163 * the log item is to remove the entry in the parent directory.
1166 if (entry
->kind
== svn_node_dir
)
1168 svn_wc_adm_access_t
*child_access
;
1169 const char *logfile_path
1170 = svn_wc__adm_path(parent_path
, FALSE
, pool
,
1171 svn_wc__logfile_path(*log_number
, pool
), NULL
);
1173 SVN_ERR(svn_wc_adm_retrieve
1174 (&child_access
, eb
->adm_access
,
1177 SVN_ERR(leftmod_error_chain
1178 (svn_wc_remove_from_revision_control
1180 SVN_WC_ENTRY_THIS_DIR
,
1182 TRUE
, /* instant error */
1186 logfile_path
, parent_path
, pool
));
1190 SVN_ERR(svn_wc__run_log(adm_access
, NULL
, pool
));
1193 if (eb
->notify_func
)
1196 svn_wc_create_notify(full_path
,
1197 svn_wc_notify_update_delete
, pool
), pool
);
1199 return SVN_NO_ERROR
;
1203 static svn_error_t
*
1204 delete_entry(const char *path
,
1205 svn_revnum_t revision
,
1209 struct dir_baton
*pb
= parent_baton
;
1211 SVN_ERR(check_path_under_root(pb
->path
, svn_path_basename(path
, pool
),
1213 return do_entry_deletion(pb
->edit_baton
, pb
->path
, path
, &pb
->log_number
,
1218 static svn_error_t
*
1219 add_directory(const char *path
,
1221 const char *copyfrom_path
,
1222 svn_revnum_t copyfrom_revision
,
1226 struct dir_baton
*pb
= parent_baton
;
1227 struct edit_baton
*eb
= pb
->edit_baton
;
1228 struct dir_baton
*db
;
1229 svn_node_kind_t kind
;
1231 SVN_ERR(make_dir_baton(&db
, path
, eb
, pb
, TRUE
, pool
));
1234 if (strcmp(eb
->target
, path
) == 0)
1236 /* The target of the edit is being added, give it the requested
1237 depth of the edit (but convert svn_depth_unknown to
1238 svn_depth_infinity). */
1239 db
->ambient_depth
= (eb
->requested_depth
== svn_depth_unknown
)
1240 ? svn_depth_infinity
: eb
->requested_depth
;
1242 else if (eb
->requested_depth
== svn_depth_immediates
1243 || (eb
->requested_depth
== svn_depth_unknown
1244 && pb
->ambient_depth
== svn_depth_immediates
))
1246 db
->ambient_depth
= svn_depth_empty
;
1250 db
->ambient_depth
= svn_depth_infinity
;
1254 /* Flush the log for the parent directory before going into this subtree. */
1255 SVN_ERR(flush_log(pb
, pool
));
1257 /* Semantic check. Either both "copyfrom" args are valid, or they're
1258 NULL and SVN_INVALID_REVNUM. A mixture is illegal semantics. */
1259 if ((copyfrom_path
&& (! SVN_IS_VALID_REVNUM(copyfrom_revision
)))
1260 || ((! copyfrom_path
) && (SVN_IS_VALID_REVNUM(copyfrom_revision
))))
1263 SVN_ERR(check_path_under_root(pb
->path
, db
->name
, pool
));
1264 SVN_ERR(svn_io_check_path(db
->path
, &kind
, db
->pool
));
1266 /* The path can exist, but it must be a directory... */
1267 if (kind
== svn_node_file
|| kind
== svn_node_unknown
)
1268 return svn_error_createf
1269 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1270 _("Failed to add directory '%s': a non-directory object of the "
1271 "same name already exists"),
1272 svn_path_local_style(db
->path
, pool
));
1274 if (kind
== svn_node_dir
)
1276 /* ...Ok, it's a directory but it can't be versioned or
1277 scheduled for addition with history. */
1278 svn_wc_adm_access_t
*adm_access
;
1280 /* Test the obstructing dir to see if it's versioned. */
1281 svn_error_t
*err
= svn_wc_adm_open3(&adm_access
, NULL
,
1285 if (err
&& err
->apr_err
!= SVN_ERR_WC_NOT_DIRECTORY
)
1287 /* Something quite unexepected has happened. */
1290 else if (err
) /* Not a versioned dir. */
1292 svn_error_clear(err
);
1293 if (eb
->allow_unver_obstructions
)
1295 /* Obstructing dir is not versioned, just need to flag it as
1296 existing then we are done here. */
1301 return svn_error_createf
1302 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1303 _("Failed to add directory '%s': an unversioned "
1304 "directory of the same name already exists"),
1305 svn_path_local_style(db
->path
, pool
));
1308 else /* Obstructing dir *is* versioned or scheduled for addition. */
1310 const svn_wc_entry_t
*entry
;
1311 SVN_ERR(svn_wc_entry(&entry
, db
->path
, adm_access
, FALSE
, pool
));
1313 /* Anything other than a dir scheduled for addition without
1314 history is an error. */
1316 && entry
->schedule
== svn_wc_schedule_add
1319 db
->add_existed
= TRUE
;
1323 return svn_error_createf
1324 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1325 _("Failed to add directory '%s': a versioned "
1326 "directory of the same name already exists"),
1327 svn_path_local_style(db
->path
, pool
));
1332 /* It may not be named the same as the administrative directory. */
1333 if (svn_wc_is_adm_dir(svn_path_basename(path
, pool
), pool
))
1334 return svn_error_createf
1335 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1336 _("Failed to add directory '%s': object of the same name as the "
1337 "administrative directory"),
1338 svn_path_local_style(db
->path
, pool
));
1340 /* Either we got real copyfrom args... */
1341 if (copyfrom_path
|| SVN_IS_VALID_REVNUM(copyfrom_revision
))
1343 /* ### todo: for now, this editor doesn't know how to deal with
1344 copyfrom args. Someday it will interpet them as an update
1345 optimization, and actually copy one part of the wc to another.
1346 Then it will recursively "normalize" all the ancestry in the
1347 copied tree. Someday! */
1348 return svn_error_createf
1349 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1350 _("Failed to add directory '%s': "
1351 "copyfrom arguments not yet supported"),
1352 svn_path_local_style(db
->path
, pool
));
1354 else /* ...or we got invalid copyfrom args. */
1356 svn_wc_adm_access_t
*adm_access
;
1357 svn_wc_entry_t tmp_entry
;
1358 apr_uint64_t modify_flags
= SVN_WC__ENTRY_MODIFY_KIND
|
1359 SVN_WC__ENTRY_MODIFY_DELETED
| SVN_WC__ENTRY_MODIFY_ABSENT
;
1361 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
,
1362 pb
->path
, db
->pool
));
1364 /* Immediately create an entry for the new directory in the parent.
1365 Note that the parent must already be either added or opened, and
1366 thus it's in an 'incomplete' state just like the new dir.
1367 The entry may already exist if the new directory is already
1368 scheduled for addition without history, in that case set
1369 its schedule to normal. */
1370 tmp_entry
.kind
= svn_node_dir
;
1371 /* Note that there may already exist a 'ghost' entry in the
1372 parent with the same name, in a 'deleted' or 'absent' state.
1373 If so, it's fine to overwrite it... but we need to make sure
1374 we get rid of the state flag when doing so: */
1375 tmp_entry
.deleted
= FALSE
;
1376 tmp_entry
.absent
= FALSE
;
1378 if (db
->add_existed
)
1380 tmp_entry
.schedule
= svn_wc_schedule_normal
;
1381 modify_flags
|= SVN_WC__ENTRY_MODIFY_SCHEDULE
|
1382 SVN_WC__ENTRY_MODIFY_FORCE
;
1385 SVN_ERR(svn_wc__entry_modify(adm_access
, db
->name
, &tmp_entry
,
1387 TRUE
/* immediate write */, pool
));
1389 if (db
->add_existed
)
1391 /* Immediately tweak the schedule for "this dir" so it too
1392 is no longer scheduled for addition. Change rev from 0
1393 to the target revision allowing prep_directory() to do
1394 its thing without error. */
1395 modify_flags
= SVN_WC__ENTRY_MODIFY_SCHEDULE
1396 | SVN_WC__ENTRY_MODIFY_FORCE
| SVN_WC__ENTRY_MODIFY_REVISION
;
1398 SVN_ERR(svn_wc_adm_retrieve(&adm_access
,
1399 db
->edit_baton
->adm_access
,
1401 tmp_entry
.revision
= *(eb
->target_revision
);
1405 tmp_entry
.url
= svn_path_url_add_component(eb
->switch_url
,
1407 modify_flags
|= SVN_WC__ENTRY_MODIFY_URL
;
1410 SVN_ERR(svn_wc__entry_modify(adm_access
, NULL
, &tmp_entry
,
1412 TRUE
/* immediate write */, pool
));
1416 SVN_ERR(prep_directory(db
,
1418 *(eb
->target_revision
),
1421 /* If this add was obstructed by dir scheduled for addition without
1422 history let close_file() handle the notification because there
1423 might be properties to deal with. */
1424 if (eb
->notify_func
&& !(db
->add_existed
))
1426 svn_wc_notify_t
*notify
= svn_wc_create_notify(
1429 svn_wc_notify_exists
: svn_wc_notify_update_add
,
1431 notify
->kind
= svn_node_dir
;
1432 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
1435 return SVN_NO_ERROR
;
1439 static svn_error_t
*
1440 open_directory(const char *path
,
1442 svn_revnum_t base_revision
,
1446 struct dir_baton
*db
, *pb
= parent_baton
;
1447 struct edit_baton
*eb
= pb
->edit_baton
;
1448 const svn_wc_entry_t
*entry
;
1449 svn_wc_entry_t tmp_entry
;
1450 apr_uint64_t flags
= SVN_WC__ENTRY_MODIFY_REVISION
|
1451 SVN_WC__ENTRY_MODIFY_URL
| SVN_WC__ENTRY_MODIFY_INCOMPLETE
;
1453 svn_wc_adm_access_t
*adm_access
;
1455 SVN_ERR(make_dir_baton(&db
, path
, eb
, pb
, FALSE
, pool
));
1458 /* Flush the log for the parent directory before going into this subtree. */
1459 SVN_ERR(flush_log(pb
, pool
));
1461 SVN_ERR(check_path_under_root(pb
->path
, db
->name
, pool
));
1463 /* Skip this directory if it has property conflicts. */
1464 SVN_ERR(svn_wc_entry(&entry
, db
->path
, eb
->adm_access
, FALSE
, pool
));
1467 /* Text conflicts can't happen for a directory, but we need to supply
1469 svn_boolean_t text_conflicted
;
1470 svn_boolean_t prop_conflicted
;
1472 db
->ambient_depth
= entry
->depth
;
1474 SVN_ERR(svn_wc_conflicted_p(&text_conflicted
, &prop_conflicted
,
1475 db
->path
, entry
, pool
));
1476 assert(! text_conflicted
);
1477 if (prop_conflicted
)
1479 db
->bump_info
->skipped
= TRUE
;
1480 apr_hash_set(eb
->skipped_paths
, apr_pstrdup(eb
->pool
, db
->path
),
1481 APR_HASH_KEY_STRING
, (void*)1);
1482 if (eb
->notify_func
)
1484 svn_wc_notify_t
*notify
1485 = svn_wc_create_notify(db
->path
, svn_wc_notify_skip
, pool
);
1486 notify
->kind
= svn_node_dir
;
1487 notify
->prop_state
= svn_wc_notify_state_conflicted
;
1488 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
1490 return SVN_NO_ERROR
;
1494 /* Mark directory as being at target_revision and URL, but incomplete. */
1495 tmp_entry
.revision
= *(eb
->target_revision
);
1496 tmp_entry
.url
= db
->new_URL
;
1497 /* In some situations, the URL of this directory does not have the same
1498 repository root as the anchor of the update; we can't just blindly
1499 use the that repository root here, so make sure it is really an
1501 if (eb
->repos
&& svn_path_is_ancestor(eb
->repos
, db
->new_URL
))
1503 tmp_entry
.repos
= eb
->repos
;
1504 flags
|= SVN_WC__ENTRY_MODIFY_REPOS
;
1506 tmp_entry
.incomplete
= TRUE
;
1508 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
,
1510 SVN_ERR(svn_wc__entry_modify(adm_access
, NULL
/* THIS_DIR */,
1512 TRUE
/* immediate write */,
1515 return SVN_NO_ERROR
;
1519 static svn_error_t
*
1520 change_dir_prop(void *dir_baton
,
1522 const svn_string_t
*value
,
1525 svn_prop_t
*propchange
;
1526 struct dir_baton
*db
= dir_baton
;
1528 if (db
->bump_info
->skipped
)
1529 return SVN_NO_ERROR
;
1531 propchange
= apr_array_push(db
->propchanges
);
1532 propchange
->name
= apr_pstrdup(db
->pool
, name
);
1533 propchange
->value
= value
? svn_string_dup(value
, db
->pool
) : NULL
;
1535 return SVN_NO_ERROR
;
1540 /* If any of the svn_prop_t objects in PROPCHANGES represents a change
1541 to the SVN_PROP_EXTERNALS property, return that change, else return
1542 null. If PROPCHANGES contains more than one such change, return
1544 static const svn_prop_t
*
1545 externals_prop_changed(apr_array_header_t
*propchanges
)
1549 for (i
= 0; i
< propchanges
->nelts
; i
++)
1551 const svn_prop_t
*p
= &(APR_ARRAY_IDX(propchanges
, i
, svn_prop_t
));
1552 if (strcmp(p
->name
, SVN_PROP_EXTERNALS
) == 0)
1559 static svn_error_t
*
1560 close_directory(void *dir_baton
,
1563 struct dir_baton
*db
= dir_baton
;
1564 svn_wc_notify_state_t prop_state
= svn_wc_notify_state_unknown
;
1565 apr_array_header_t
*entry_props
, *wc_props
, *regular_props
;
1566 svn_wc_adm_access_t
*adm_access
;
1568 SVN_ERR(svn_categorize_props(db
->propchanges
, &entry_props
, &wc_props
,
1569 ®ular_props
, pool
));
1571 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, db
->edit_baton
->adm_access
,
1572 db
->path
, db
->pool
));
1574 /* If this directory has property changes stored up, now is the time
1575 to deal with them. */
1576 if (regular_props
->nelts
|| entry_props
->nelts
|| wc_props
->nelts
)
1578 /* Make a temporary log accumulator for dirprop changes.*/
1579 svn_stringbuf_t
*dirprop_log
= svn_stringbuf_create("", pool
);
1581 if (regular_props
->nelts
)
1583 /* If recording traversal info, then see if the
1584 SVN_PROP_EXTERNALS property on this directory changed,
1585 and record before and after for the change. */
1586 if (db
->edit_baton
->traversal_info
)
1588 svn_wc_traversal_info_t
*ti
= db
->edit_baton
->traversal_info
;
1589 const svn_prop_t
*change
= externals_prop_changed(regular_props
);
1593 const svn_string_t
*new_val_s
= change
->value
;
1594 const svn_string_t
*old_val_s
;
1596 SVN_ERR(svn_wc_prop_get
1597 (&old_val_s
, SVN_PROP_EXTERNALS
,
1598 db
->path
, adm_access
, db
->pool
));
1600 if ((new_val_s
== NULL
) && (old_val_s
== NULL
))
1601 ; /* No value before, no value after... so do nothing. */
1602 else if (new_val_s
&& old_val_s
1603 && (svn_string_compare(old_val_s
, new_val_s
)))
1604 ; /* Value did not change... so do nothing. */
1605 else if (old_val_s
|| new_val_s
)
1606 /* something changed, record the change */
1608 const char *d_path
= apr_pstrdup(ti
->pool
, db
->path
);
1610 apr_hash_set(ti
->depths
, d_path
, APR_HASH_KEY_STRING
,
1611 svn_depth_to_word(db
->ambient_depth
));
1613 /* We can't assume that ti came pre-loaded with
1614 the old values of the svn:externals property.
1615 Yes, most callers will have already
1616 initialized ti by sending it through
1617 svn_wc_crawl_revisions, but we shouldn't
1618 count on that here -- so we set both the old
1619 and new values again. */
1621 apr_hash_set(ti
->externals_old
, d_path
,
1622 APR_HASH_KEY_STRING
,
1623 apr_pstrmemdup(ti
->pool
, old_val_s
->data
,
1626 apr_hash_set(ti
->externals_new
, d_path
,
1627 APR_HASH_KEY_STRING
,
1628 apr_pstrmemdup(ti
->pool
, new_val_s
->data
,
1634 /* Merge pending properties into temporary files (ignoring
1636 SVN_ERR_W(svn_wc__merge_props(&prop_state
,
1637 adm_access
, db
->path
,
1638 NULL
/* use baseprops */,
1640 regular_props
, TRUE
, FALSE
,
1641 db
->edit_baton
->conflict_func
,
1642 db
->edit_baton
->conflict_baton
,
1643 db
->pool
, &dirprop_log
),
1644 _("Couldn't do property merge"));
1647 SVN_ERR(accumulate_entry_props(dirprop_log
, NULL
,
1648 adm_access
, db
->path
,
1649 entry_props
, pool
));
1651 SVN_ERR(accumulate_wcprops(dirprop_log
, adm_access
,
1652 db
->path
, wc_props
, pool
));
1654 /* Add the dirprop loggy entries to the baton's log
1656 svn_stringbuf_appendstr(db
->log_accum
, dirprop_log
);
1659 /* Flush and run the log. */
1660 SVN_ERR(flush_log(db
, pool
));
1661 SVN_ERR(svn_wc__run_log(adm_access
, db
->edit_baton
->diff3_cmd
, db
->pool
));
1664 /* We're done with this directory, so remove one reference from the
1665 bump information. This may trigger a number of actions. See
1666 maybe_bump_dir_info() for more information. */
1667 SVN_ERR(maybe_bump_dir_info(db
->edit_baton
, db
->bump_info
, db
->pool
));
1669 /* Notify of any prop changes on this directory -- but do nothing
1670 if it's an added or skipped directory, because notification has already
1671 happened in that case - unless the add was obstructed by a dir
1672 scheduled for addition without history, in which case we handle
1673 notification here). */
1674 if (! db
->bump_info
->skipped
&& (db
->add_existed
|| (! db
->added
))
1675 && (db
->edit_baton
->notify_func
))
1677 svn_wc_notify_t
*notify
1678 = svn_wc_create_notify(db
->path
,
1679 db
->existed
|| db
->add_existed
1680 ? svn_wc_notify_exists
1681 : svn_wc_notify_update_update
,
1683 notify
->kind
= svn_node_dir
;
1684 notify
->prop_state
= prop_state
;
1685 (*db
->edit_baton
->notify_func
)(db
->edit_baton
->notify_baton
,
1689 return SVN_NO_ERROR
;
1693 /* Common code for 'absent_file' and 'absent_directory'. */
1694 static svn_error_t
*
1695 absent_file_or_dir(const char *path
,
1696 svn_node_kind_t kind
,
1700 const char *name
= svn_path_basename(path
, pool
);
1701 struct dir_baton
*pb
= parent_baton
;
1702 struct edit_baton
*eb
= pb
->edit_baton
;
1703 svn_wc_adm_access_t
*adm_access
;
1704 apr_hash_t
*entries
;
1705 const svn_wc_entry_t
*ent
;
1706 svn_wc_entry_t tmp_entry
;
1708 /* Extra check: an item by this name may not exist, but there may
1709 still be one scheduled for addition. That's a genuine
1711 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
, pb
->path
, pool
));
1712 SVN_ERR(svn_wc_entries_read(&entries
, adm_access
, FALSE
, pool
));
1713 ent
= apr_hash_get(entries
, name
, APR_HASH_KEY_STRING
);
1714 if (ent
&& (ent
->schedule
== svn_wc_schedule_add
))
1715 return svn_error_createf
1716 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1717 _("Failed to mark '%s' absent: item of the same name is already "
1718 "scheduled for addition"),
1719 svn_path_local_style(path
, pool
));
1721 /* Immediately create an entry for the new item in the parent. Note
1722 that the parent must already be either added or opened, and thus
1723 it's in an 'incomplete' state just like the new item. */
1724 tmp_entry
.kind
= kind
;
1726 /* Note that there may already exist a 'ghost' entry in the parent
1727 with the same name, in a 'deleted' state. If so, it's fine to
1728 overwrite it... but we need to make sure we get rid of the
1729 'deleted' flag when doing so: */
1730 tmp_entry
.deleted
= FALSE
;
1732 /* Post-update processing knows to leave this entry if its revision
1733 is equal to the target revision of the overall update. */
1734 tmp_entry
.revision
= *(eb
->target_revision
);
1736 /* And, of course, marking as absent is the whole point. */
1737 tmp_entry
.absent
= TRUE
;
1739 SVN_ERR(svn_wc__entry_modify(adm_access
, name
, &tmp_entry
,
1740 (SVN_WC__ENTRY_MODIFY_KIND
|
1741 SVN_WC__ENTRY_MODIFY_REVISION
|
1742 SVN_WC__ENTRY_MODIFY_DELETED
|
1743 SVN_WC__ENTRY_MODIFY_ABSENT
),
1744 TRUE
/* immediate write */, pool
));
1746 return SVN_NO_ERROR
;
1750 static svn_error_t
*
1751 absent_file(const char *path
,
1755 return absent_file_or_dir(path
, svn_node_file
, parent_baton
, pool
);
1759 static svn_error_t
*
1760 absent_directory(const char *path
,
1764 return absent_file_or_dir(path
, svn_node_dir
, parent_baton
, pool
);
1769 static svn_error_t
*
1770 add_file(const char *path
,
1772 const char *copyfrom_path
,
1773 svn_revnum_t copyfrom_rev
,
1777 struct dir_baton
*pb
= parent_baton
;
1778 struct edit_baton
*eb
= pb
->edit_baton
;
1779 struct file_baton
*fb
;
1780 const svn_wc_entry_t
*entry
;
1781 svn_node_kind_t kind
;
1782 svn_wc_adm_access_t
*adm_access
;
1783 apr_pool_t
*subpool
;
1785 if (copyfrom_path
|| SVN_IS_VALID_REVNUM(copyfrom_rev
))
1788 if (! (copyfrom_path
&& SVN_IS_VALID_REVNUM(copyfrom_rev
)))
1789 return svn_error_create(SVN_ERR_WC_INVALID_OP_ON_CWD
, NULL
,
1790 _("Bad copyfrom arguments received"));
1792 return add_file_with_history(path
, parent_baton
,
1793 copyfrom_path
, copyfrom_rev
,
1797 /* The file_pool can stick around for a *long* time, so we want to
1798 use a subpool for any temporary allocations. */
1799 subpool
= svn_pool_create(pool
);
1801 SVN_ERR(make_file_baton(&fb
, pb
, path
, TRUE
, pool
));
1805 SVN_ERR(check_path_under_root(fb
->dir_baton
->path
, fb
->name
, subpool
));
1807 /* It is interesting to note: everything below is just validation. We
1808 aren't actually doing any "work" or fetching any persistent data. */
1810 SVN_ERR(svn_io_check_path(fb
->path
, &kind
, subpool
));
1811 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
,
1812 pb
->path
, subpool
));
1813 SVN_ERR(svn_wc_entry(&entry
, fb
->path
, adm_access
, FALSE
, subpool
));
1815 /* Sanity checks. */
1817 /* When adding, there should be nothing with this name unless unversioned
1818 obstructions are permitted or the obstruction is scheduled for addition
1820 if (kind
!= svn_node_none
)
1822 if (eb
->allow_unver_obstructions
1823 || (entry
&& entry
->schedule
== svn_wc_schedule_add
))
1825 if (entry
&& entry
->copied
)
1827 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE
,
1829 _("Failed to add file '%s': a "
1830 "file of the same name is "
1831 "already scheduled for addition "
1833 svn_path_local_style(fb
->path
,
1837 /* The name can exist, but it better *really* be a file. */
1838 if (kind
!= svn_node_file
)
1839 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE
,
1841 _("Failed to add file '%s': "
1842 "a non-file object of the same "
1843 "name already exists"),
1844 svn_path_local_style(fb
->path
,
1848 fb
->add_existed
= TRUE
; /* Flag as addition without history. */
1850 fb
->existed
= TRUE
; /* Flag as unversioned obstruction. */
1854 return svn_error_createf
1855 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1856 _("Failed to add file '%s': object of the same name "
1857 "already exists"), svn_path_local_style(fb
->path
, pool
));
1860 /* sussman sez: If we're trying to add a file that's already in
1861 `entries' (but not on disk), that's okay. It's probably because
1862 the user deleted the working version and ran 'svn up' as a means
1863 of getting the file back.
1865 It certainly doesn't hurt to re-add the file. We can't possibly
1866 get the entry showing up twice in `entries', since it's a hash;
1867 and we know that we won't lose any local mods. Let the existing
1868 entry be overwritten. */
1870 svn_pool_destroy(subpool
);
1872 return SVN_NO_ERROR
;
1876 static svn_error_t
*
1877 open_file(const char *path
,
1879 svn_revnum_t base_revision
,
1883 struct dir_baton
*pb
= parent_baton
;
1884 struct edit_baton
*eb
= pb
->edit_baton
;
1885 struct file_baton
*fb
;
1886 const svn_wc_entry_t
*entry
;
1887 svn_node_kind_t kind
;
1888 svn_wc_adm_access_t
*adm_access
;
1889 svn_boolean_t text_conflicted
;
1890 svn_boolean_t prop_conflicted
;
1892 /* the file_pool can stick around for a *long* time, so we want to use
1893 a subpool for any temporary allocations. */
1894 apr_pool_t
*subpool
= svn_pool_create(pool
);
1896 SVN_ERR(make_file_baton(&fb
, pb
, path
, FALSE
, pool
));
1899 SVN_ERR(check_path_under_root(fb
->dir_baton
->path
, fb
->name
, subpool
));
1901 /* It is interesting to note: everything below is just validation. We
1902 aren't actually doing any "work" or fetching any persistent data. */
1904 SVN_ERR(svn_io_check_path(fb
->path
, &kind
, subpool
));
1905 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
,
1906 pb
->path
, subpool
));
1907 SVN_ERR(svn_wc_entry(&entry
, fb
->path
, adm_access
, FALSE
, subpool
));
1909 /* Sanity checks. */
1911 /* If replacing, make sure the .svn entry already exists. */
1913 return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE
, NULL
,
1914 _("File '%s' in directory '%s' "
1915 "is not a versioned resource"),
1917 svn_path_local_style(pb
->path
, pool
));
1919 /* If the file is in conflict, don't mess with it. */
1920 SVN_ERR(svn_wc_conflicted_p(&text_conflicted
, &prop_conflicted
,
1921 pb
->path
, entry
, pool
));
1922 if (text_conflicted
|| prop_conflicted
)
1925 apr_hash_set(eb
->skipped_paths
, apr_pstrdup(eb
->pool
, fb
->path
),
1926 APR_HASH_KEY_STRING
, (void*)1);
1927 if (eb
->notify_func
)
1929 svn_wc_notify_t
*notify
1930 = svn_wc_create_notify(fb
->path
, svn_wc_notify_skip
, pool
);
1931 notify
->kind
= svn_node_file
;
1932 notify
->content_state
= text_conflicted
1933 ? svn_wc_notify_state_conflicted
1934 : svn_wc_notify_state_unknown
;
1935 notify
->prop_state
= prop_conflicted
1936 ? svn_wc_notify_state_conflicted
1937 : svn_wc_notify_state_unknown
;
1938 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
1942 svn_pool_destroy(subpool
);
1944 return SVN_NO_ERROR
;
1947 /* Fill out FB->text_base_path and FB->new_text_base_path to the
1948 permanent and temporary text-base paths respectively, or (if the
1949 entry is replaced with history) to the permanent and temporary
1950 revert-base paths respectively.
1952 If REPLACED_P and USE_REVERT_BASE are non-NULL, set *REPLACED_P and
1953 *USE_REVERT_BASE_P to whether or not the entry is replaced, and to
1954 whether or not it needs to use the revert base (i.e., it's replaced
1955 with history), respectively. If CHECKSUM_P is non-NULL and the
1956 path already has an entry, set *CHECKSUM_P to the entry's checksum.
1958 Use POOL for temporary allocation and for *CHECKSUM_P (if
1959 applicable), but allocate FB->text_base_path and
1960 FB->new_text_base_path in FB->pool. */
1961 static svn_error_t
*
1962 choose_base_paths(const char **checksum_p
,
1963 svn_boolean_t
*replaced_p
,
1964 svn_boolean_t
*use_revert_base_p
,
1965 struct file_baton
*fb
,
1968 struct edit_baton
*eb
= fb
->edit_baton
;
1969 svn_wc_adm_access_t
*adm_access
;
1970 const svn_wc_entry_t
*ent
;
1971 svn_boolean_t replaced
, use_revert_base
;
1973 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
,
1974 svn_path_dirname(fb
->path
, pool
), pool
));
1975 SVN_ERR(svn_wc_entry(&ent
, fb
->path
, adm_access
, FALSE
, pool
));
1977 replaced
= ent
&& ent
->schedule
== svn_wc_schedule_replace
;
1978 use_revert_base
= replaced
&& (ent
->copyfrom_url
!= NULL
);
1979 if (use_revert_base
)
1981 fb
->text_base_path
= svn_wc__text_revert_path(fb
->path
, FALSE
, fb
->pool
);
1982 fb
->new_text_base_path
= svn_wc__text_revert_path(fb
->path
, TRUE
,
1987 fb
->text_base_path
= svn_wc__text_base_path(fb
->path
, FALSE
, fb
->pool
);
1988 fb
->new_text_base_path
= svn_wc__text_base_path(fb
->path
, TRUE
,
1996 *checksum_p
= ent
->checksum
;
1999 *replaced_p
= replaced
;
2000 if (use_revert_base_p
)
2001 *use_revert_base_p
= use_revert_base
;
2003 return SVN_NO_ERROR
;
2008 static svn_error_t
*
2009 apply_textdelta(void *file_baton
,
2010 const char *base_checksum
,
2012 svn_txdelta_window_handler_t
*handler
,
2013 void **handler_baton
)
2015 struct file_baton
*fb
= file_baton
;
2016 apr_pool_t
*handler_pool
= svn_pool_create(fb
->pool
);
2017 struct handler_baton
*hb
= apr_palloc(handler_pool
, sizeof(*hb
));
2019 const char *checksum
;
2020 svn_boolean_t replaced
;
2021 svn_boolean_t use_revert_base
;
2025 *handler
= svn_delta_noop_window_handler
;
2026 *handler_baton
= NULL
;
2027 return SVN_NO_ERROR
;
2030 fb
->received_textdelta
= TRUE
;
2032 /* Before applying incoming svndiff data to text base, make sure
2033 text base hasn't been corrupted, and that its checksum
2034 matches the expected base checksum. */
2035 SVN_ERR(choose_base_paths(&checksum
, &replaced
, &use_revert_base
,
2038 /* Only compare checksums if this file has an entry, and the entry has
2039 a checksum. If there's no entry, it just means the file is
2040 created in this update, so there won't be any previously recorded
2041 checksum to compare against. If no checksum, well, for backwards
2042 compatibility we assume that no checksum always matches. */
2045 unsigned char digest
[APR_MD5_DIGESTSIZE
];
2046 const char *hex_digest
;
2048 SVN_ERR(svn_io_file_checksum(digest
, fb
->text_base_path
, pool
));
2049 hex_digest
= svn_md5_digest_to_cstring_display(digest
, pool
);
2051 /* Compare the base_checksum here, rather than in the window
2052 handler, because there's no guarantee that the handler will
2053 see every byte of the base file. */
2056 if (strcmp(hex_digest
, base_checksum
) != 0)
2057 return svn_error_createf
2058 (SVN_ERR_WC_CORRUPT_TEXT_BASE
, NULL
,
2059 _("Checksum mismatch for '%s'; expected: '%s', actual: '%s'"),
2060 svn_path_local_style(fb
->text_base_path
, pool
), base_checksum
,
2064 if (! replaced
&& strcmp(hex_digest
, checksum
) != 0)
2066 return svn_error_createf
2067 (SVN_ERR_WC_CORRUPT_TEXT_BASE
, NULL
,
2068 _("Checksum mismatch for '%s'; recorded: '%s', actual: '%s'"),
2069 svn_path_local_style(fb
->text_base_path
, pool
), checksum
,
2074 /* Open the text base for reading, unless this is an added file. */
2077 kff todo: what we really need to do here is:
2079 1. See if there's a file or dir by this name already here.
2080 2. See if it's under revision control.
2081 3. If both are true, open text-base.
2082 4. If only 1 is true, bail, because we can't go destroying user's
2083 files (or as an alternative to bailing, move it to some tmp
2084 name and somehow tell the user, but communicating with the
2085 user without erroring is a whole callback system we haven't
2086 finished inventing yet.)
2091 if (use_revert_base
)
2092 SVN_ERR(svn_wc__open_revert_base(&hb
->source
, fb
->path
,
2096 SVN_ERR(svn_wc__open_text_base(&hb
->source
, fb
->path
, APR_READ
,
2102 if (fb
->copied_text_base
)
2103 SVN_ERR(svn_io_file_open(&hb
->source
, fb
->copied_text_base
,
2104 APR_READ
, APR_OS_DEFAULT
, handler_pool
));
2109 /* Open the text base for writing (this will get us a temporary file). */
2111 if (use_revert_base
)
2112 err
= svn_wc__open_revert_base(&hb
->dest
, fb
->path
,
2113 (APR_WRITE
| APR_TRUNCATE
| APR_CREATE
),
2116 err
= svn_wc__open_text_base(&hb
->dest
, fb
->path
,
2117 (APR_WRITE
| APR_TRUNCATE
| APR_CREATE
),
2122 svn_pool_destroy(handler_pool
);
2126 /* Prepare to apply the delta. */
2127 svn_txdelta_apply(svn_stream_from_aprfile(hb
->source
, handler_pool
),
2128 svn_stream_from_aprfile(hb
->dest
, handler_pool
),
2129 fb
->digest
, fb
->new_text_base_path
, handler_pool
,
2130 &hb
->apply_handler
, &hb
->apply_baton
);
2132 hb
->pool
= handler_pool
;
2135 /* We're all set. */
2136 *handler_baton
= hb
;
2137 *handler
= window_handler
;
2139 return SVN_NO_ERROR
;
2145 static svn_error_t
*
2146 change_file_prop(void *file_baton
,
2148 const svn_string_t
*value
,
2151 struct file_baton
*fb
= file_baton
;
2152 struct edit_baton
*eb
= fb
->edit_baton
;
2153 svn_prop_t
*propchange
;
2156 return SVN_NO_ERROR
;
2158 /* Push a new propchange to the file baton's array of propchanges */
2159 propchange
= apr_array_push(fb
->propchanges
);
2160 propchange
->name
= apr_pstrdup(fb
->pool
, name
);
2161 propchange
->value
= value
? svn_string_dup(value
, fb
->pool
) : NULL
;
2163 /* Special case: If use-commit-times config variable is set we
2164 cache the last-changed-date propval so we can use it to set
2165 the working file's timestamp. */
2166 if (eb
->use_commit_times
2167 && (strcmp(name
, SVN_PROP_ENTRY_COMMITTED_DATE
) == 0)
2169 fb
->last_changed_date
= apr_pstrdup(fb
->pool
, value
->data
);
2171 return SVN_NO_ERROR
;
2175 /* Write log commands to merge PROP_CHANGES into the existing
2176 properties of FILE_PATH. PROP_CHANGES can contain regular
2177 properties as well as entryprops and wcprops. Update *PROP_STATE
2178 to reflect the result of the regular prop merge. Make *LOCK_STATE
2179 reflect the possible removal of a lock token from FILE_PATH's
2180 entryprops. BASE_PROPS and WORKING_PROPS are hashes of the base and
2181 working props of the file; if NULL they are read from the wc.
2183 CONFICT_FUNC/BATON is a callback which allows the client to
2184 possibly resolve a property conflict interactively.
2186 ADM_ACCESS is the access baton for FILE_PATH. Append log commands to
2187 LOG_ACCUM. Use POOL for temporary allocations. */
2188 static svn_error_t
*
2189 merge_props(svn_stringbuf_t
*log_accum
,
2190 svn_wc_notify_state_t
*prop_state
,
2191 svn_wc_notify_lock_state_t
*lock_state
,
2192 svn_wc_adm_access_t
*adm_access
,
2193 const char *file_path
,
2194 const apr_array_header_t
*prop_changes
,
2195 apr_hash_t
*base_props
,
2196 apr_hash_t
*working_props
,
2197 svn_wc_conflict_resolver_func_t conflict_func
,
2198 void *conflict_baton
,
2201 apr_array_header_t
*regular_props
= NULL
, *wc_props
= NULL
,
2202 *entry_props
= NULL
;
2204 /* Sort the property list into three arrays, based on kind. */
2205 SVN_ERR(svn_categorize_props(prop_changes
,
2206 &entry_props
, &wc_props
, ®ular_props
,
2209 /* Always initialize to unknown state. */
2210 *prop_state
= svn_wc_notify_state_unknown
;
2212 /* Merge the 'regular' props into the existing working proplist. */
2215 /* This will merge the old and new props into a new prop db, and
2216 write <cp> commands to the logfile to install the merged
2218 SVN_ERR(svn_wc__merge_props(prop_state
,
2219 adm_access
, file_path
,
2220 NULL
/* update, not merge */,
2223 regular_props
, TRUE
, FALSE
,
2224 conflict_func
, conflict_baton
,
2228 /* If there are any ENTRY PROPS, make sure those get appended to the
2229 growing log as fields for the file's entry.
2231 Note that no merging needs to happen; these kinds of props aren't
2232 versioned, so if the property is present, we overwrite the value. */
2234 SVN_ERR(accumulate_entry_props(log_accum
, lock_state
,
2235 adm_access
, file_path
,
2236 entry_props
, pool
));
2238 *lock_state
= svn_wc_notify_lock_state_unchanged
;
2240 /* This writes a whole bunch of log commands to install wcprops. */
2242 SVN_ERR(accumulate_wcprops(log_accum
, adm_access
,
2243 file_path
, wc_props
, pool
));
2245 return SVN_NO_ERROR
;
2248 /* Append, to LOG_ACCUM, log commands to update the entry for NAME in
2249 ADM_ACCESS with a NEW_REVISION and a NEW_URL (if non-NULL), making sure
2250 the entry refers to a file and has no absent or deleted state.
2251 Use POOL for temporary allocations. */
2252 static svn_error_t
*
2253 loggy_tweak_entry(svn_stringbuf_t
*log_accum
,
2254 svn_wc_adm_access_t
*adm_access
,
2256 svn_revnum_t new_revision
,
2257 const char *new_URL
,
2260 /* Write log entry which will bump the revision number. Also, just
2261 in case we're overwriting an existing phantom 'deleted' or
2262 'absent' entry, be sure to remove the hiddenness. */
2263 svn_wc_entry_t tmp_entry
;
2264 apr_uint64_t modify_flags
= SVN_WC__ENTRY_MODIFY_KIND
2265 | SVN_WC__ENTRY_MODIFY_REVISION
2266 | SVN_WC__ENTRY_MODIFY_DELETED
2267 | SVN_WC__ENTRY_MODIFY_ABSENT
2268 | SVN_WC__ENTRY_MODIFY_TEXT_TIME
2269 | SVN_WC__ENTRY_MODIFY_WORKING_SIZE
;
2272 tmp_entry
.revision
= new_revision
;
2273 tmp_entry
.kind
= svn_node_file
;
2274 tmp_entry
.deleted
= FALSE
;
2275 tmp_entry
.absent
= FALSE
;
2276 /* Indicate the file was locally modified and we didn't get to
2277 calculate the true value, but we can't set it to UNKNOWN (-1),
2278 because that would indicate absense of this value.
2279 If it isn't locally modified,
2280 we'll overwrite with the actual value later. */
2281 tmp_entry
.working_size
= SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN
;
2282 /* The same is true for the TEXT_TIME field, except that that doesn't
2283 have an explicid 'changed' value, so we set the value to 'undefined'. */
2284 tmp_entry
.text_time
= 0;
2286 /* Possibly install a *non*-inherited URL in the entry. */
2289 tmp_entry
.url
= new_URL
;
2290 modify_flags
|= SVN_WC__ENTRY_MODIFY_URL
;
2293 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum
, adm_access
,
2294 path
, &tmp_entry
, modify_flags
,
2297 return SVN_NO_ERROR
;
2301 /* This is the small planet. It has the complex responsibility of
2302 * "integrating" a new revision of a file into a working copy.
2304 * Given a file_baton FB for a file either already under version control, or
2305 * prepared (see below) to join version control, fully install a
2306 * new revision of the file.
2308 * By "install", we mean: create a new text-base and prop-base, merge
2309 * any textual and property changes into the working file, and finally
2310 * update all metadata so that the working copy believes it has a new
2311 * working revision of the file. All of this work includes being
2312 * sensitive to eol translation, keyword substitution, and performing
2313 * all actions accumulated to FB->DIR_BATON->LOG_ACCUM.
2315 * If there's a new text base, FB->NEW_TEXT_BASE_PATH must be the full
2316 * pathname of the new text base, somewhere in the administrative area
2317 * of the working file. The temporary text base will be removed after
2318 * a successful run of the generated log commands.
2320 * Set *CONTENT_STATE, *PROP_STATE and *LOCK_STATE to the state of the
2321 * contents, properties and repository lock, respectively, after the
2322 * installation. If an error is returned, the value of these three
2323 * variables is undefined.
2325 * POOL is used for all bookkeeping work during the installation.
2327 static svn_error_t
*
2328 merge_file(svn_wc_notify_state_t
*content_state
,
2329 svn_wc_notify_state_t
*prop_state
,
2330 svn_wc_notify_lock_state_t
*lock_state
,
2331 struct file_baton
*fb
,
2334 const char *parent_dir
;
2335 struct edit_baton
*eb
= fb
->edit_baton
;
2336 svn_stringbuf_t
*log_accum
= svn_stringbuf_create("", pool
);
2337 svn_wc_adm_access_t
*adm_access
;
2338 svn_boolean_t is_locally_modified
;
2339 svn_boolean_t is_replaced
= FALSE
;
2340 svn_boolean_t magic_props_changed
;
2341 enum svn_wc_merge_outcome_t merge_outcome
= svn_wc_merge_unchanged
;
2342 const svn_wc_entry_t
*entry
;
2344 /* Accumulated entry modifications. */
2345 svn_wc_entry_t tmp_entry
;
2346 apr_uint64_t flags
= 0;
2349 When this function is called on file F, we assume the following
2352 - The new pristine text of F, if any, is present at
2353 fb->new_text_base_path
2355 - The .svn/entries file still reflects the old version of F.
2357 - fb->old_text_base_path is the old pristine F.
2358 (This is only set if there's a new text base).
2360 The goal is to update the local working copy of F to reflect
2361 the changes received from the repository, preserving any local
2365 /* Start by splitting the file path, getting an access baton for the parent,
2366 and an entry for the file if any. */
2367 svn_path_split(fb
->path
, &parent_dir
, NULL
, pool
);
2368 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, eb
->adm_access
,
2371 SVN_ERR(svn_wc_entry(&entry
, fb
->path
, adm_access
, FALSE
, pool
));
2372 if (! entry
&& ! fb
->added
)
2373 return svn_error_createf(
2374 SVN_ERR_UNVERSIONED_RESOURCE
, NULL
,
2375 _("'%s' is not under version control"),
2376 svn_path_local_style(fb
->path
, pool
));
2378 /* Determine if any of the propchanges are the "magic" ones that
2379 might require changing the working file. */
2380 magic_props_changed
= svn_wc__has_magic_property(fb
->propchanges
);
2382 /* Install all kinds of properties. It is important to do this before
2383 any file content merging, since that process might expand keywords, in
2384 which case we want the new entryprops to be in place. */
2385 SVN_ERR(merge_props(log_accum
, prop_state
, lock_state
, adm_access
,
2386 fb
->path
, fb
->propchanges
,
2387 fb
->copied_base_props
, fb
->copied_working_props
,
2388 eb
->conflict_func
, eb
->conflict_baton
, pool
));
2390 /* Has the user made local mods to the working file?
2391 Note that this compares to the current pristine file, which is
2392 different from fb->old_text_base_path if we have a replaced-with-history
2393 file. However, in the case we had an obstruction, we check against the
2394 new text base. (And if we're doing an add-with-history and we've already
2395 saved a copy of a locally-modified file, then there certainly are mods.) */
2396 if (fb
->copied_working_text
)
2397 is_locally_modified
= TRUE
;
2398 else if (! fb
->existed
)
2399 SVN_ERR(svn_wc__text_modified_internal_p(&is_locally_modified
, fb
->path
,
2400 FALSE
, adm_access
, FALSE
, pool
));
2401 else if (fb
->new_text_base_path
)
2402 SVN_ERR(svn_wc__versioned_file_modcheck(&is_locally_modified
, fb
->path
,
2404 fb
->new_text_base_path
,
2407 is_locally_modified
= FALSE
;
2409 if (entry
&& entry
->schedule
== svn_wc_schedule_replace
)
2412 if (fb
->add_existed
)
2414 /* Tweak schedule for the file's entry so it is no longer
2415 scheduled for addition. */
2416 tmp_entry
.schedule
= svn_wc_schedule_normal
;
2417 flags
|= (SVN_WC__ENTRY_MODIFY_SCHEDULE
|
2418 SVN_WC__ENTRY_MODIFY_FORCE
);
2421 /* Set the new revision and URL in the entry and clean up some other
2423 SVN_ERR(loggy_tweak_entry(log_accum
, adm_access
, fb
->path
,
2424 *eb
->target_revision
, fb
->new_URL
, pool
));
2426 /* For 'textual' merging, we implement this matrix.
2428 Text file Binary File
2429 -----------------------------------------------
2430 "Local Mods" && | svn_wc_merge uses diff3, | svn_wc_merge |
2431 (!fb->existed || | possibly makes backups & | makes backups, |
2432 fb->add_existed) | marks file as conflicted.| marks conflicted |
2433 -----------------------------------------------
2434 "Local Mods" && | Just leave obstructing file as-is. |
2436 -----------------------------------------------
2437 No Mods | Just overwrite working file. |
2439 -----------------------------------------------
2441 So the first thing we do is figure out where we are in the
2443 if (fb
->new_text_base_path
)
2445 if (! is_locally_modified
&& ! is_replaced
)
2447 /* If there are no local mods, who cares whether it's a text
2448 or binary file! Just write a log command to overwrite
2449 any working file with the new text-base. If newline
2450 conversion or keyword substitution is activated, this
2451 will happen as well during the copy.
2452 For replaced files, though, we want to merge in the changes
2453 even if the file is not modified compared to the (non-revert)
2455 SVN_ERR(svn_wc__loggy_copy(&log_accum
, NULL
, adm_access
,
2456 svn_wc__copy_translate
,
2457 fb
->new_text_base_path
,
2458 fb
->path
, FALSE
, pool
));
2460 else /* working file or obstruction is locally modified... */
2462 svn_node_kind_t wfile_kind
= svn_node_unknown
;
2464 SVN_ERR(svn_io_check_path(fb
->path
, &wfile_kind
, pool
));
2465 if (wfile_kind
== svn_node_none
&& ! fb
->added_with_history
)
2467 /* working file is missing?!
2468 Just copy the new text-base to the file. */
2469 SVN_ERR(svn_wc__loggy_copy(&log_accum
, NULL
, adm_access
,
2470 svn_wc__copy_translate
,
2471 fb
->new_text_base_path
,
2472 fb
->path
, FALSE
, pool
));
2474 else if (! fb
->existed
)
2475 /* Working file exists and has local mods
2476 or is scheduled for addition but is not an obstruction. */
2478 /* Now we need to let loose svn_wc__merge_internal() to merge
2479 the textual changes into the working file. */
2480 const char *oldrev_str
, *newrev_str
, *mine_str
;
2481 const char *merge_left
;
2482 const char *path_ext
= "";
2484 /* If we have any file extensions we're supposed to
2485 preserve in generated conflict file names, then find
2486 this path's extension. But then, if it isn't one of
2487 the ones we want to keep in conflict filenames,
2488 pretend it doesn't have an extension at all. */
2489 if (eb
->ext_patterns
&& eb
->ext_patterns
->nelts
)
2491 svn_path_splitext(NULL
, &path_ext
, fb
->path
, pool
);
2493 && svn_cstring_match_glob_list(path_ext
,
2498 /* Create strings representing the revisions of the
2499 old and new text-bases. */
2500 /* Either an old version, or an add-with-history */
2501 if (fb
->added_with_history
)
2502 oldrev_str
= apr_psprintf(pool
, ".copied%s%s",
2503 *path_ext
? "." : "",
2504 *path_ext
? path_ext
: "");
2506 oldrev_str
= apr_psprintf(pool
, ".r%ld%s%s",
2508 *path_ext
? "." : "",
2509 *path_ext
? path_ext
: "");
2511 newrev_str
= apr_psprintf(pool
, ".r%ld%s%s",
2512 *eb
->target_revision
,
2513 *path_ext
? "." : "",
2514 *path_ext
? path_ext
: "");
2515 mine_str
= apr_psprintf(pool
, ".mine%s%s",
2516 *path_ext
? "." : "",
2517 *path_ext
? path_ext
: "");
2519 if (fb
->add_existed
&& ! is_replaced
)
2521 SVN_ERR(svn_wc_create_tmp_file2(NULL
, &merge_left
,
2522 svn_wc_adm_access_path(
2524 svn_io_file_del_none
,
2527 else if (fb
->copied_text_base
)
2528 merge_left
= fb
->copied_text_base
;
2530 merge_left
= fb
->text_base_path
;
2532 /* Merge the changes from the old textbase to the new
2533 textbase into the file we're updating.
2534 Remember that this function wants full paths! */
2535 SVN_ERR(svn_wc__merge_internal
2536 (&log_accum
, &merge_outcome
,
2538 fb
->new_text_base_path
,
2540 fb
->copied_working_text
,
2542 oldrev_str
, newrev_str
, mine_str
,
2543 FALSE
, eb
->diff3_cmd
, NULL
, fb
->propchanges
,
2544 eb
->conflict_func
, eb
->conflict_baton
, pool
));
2546 /* If we created a temporary left merge file, get rid of it. */
2547 if (merge_left
!= fb
->text_base_path
)
2548 SVN_ERR(svn_wc__loggy_remove(&log_accum
, adm_access
,
2551 /* And clean up add-with-history-related temp file too. */
2552 if (fb
->copied_working_text
)
2553 SVN_ERR(svn_wc__loggy_remove(&log_accum
, adm_access
,
2554 fb
->copied_working_text
, pool
));
2556 } /* end: working file exists and has mods */
2557 } /* end: working file has mods */
2558 } /* end: "textual" merging process */
2561 apr_hash_t
*keywords
;
2563 SVN_ERR(svn_wc__get_keywords(&keywords
, fb
->path
,
2564 adm_access
, NULL
, pool
));
2565 if (magic_props_changed
|| keywords
)
2566 /* no new text base, but... */
2568 /* Special edge-case: it's possible that this file installation
2569 only involves propchanges, but that some of those props still
2570 require a retranslation of the working file.
2572 OR that the file doesn't involve propchanges which by themselves
2573 require retranslation, but receiving a change bumps the revision
2574 number which requires re-expansion of keywords... */
2576 const char *tmptext
;
2578 /* Copy and DEtranslate the working file to a temp text-base.
2579 Note that detranslation is done according to the old props. */
2580 SVN_ERR(svn_wc_translated_file2(&tmptext
, fb
->path
, fb
->path
,
2582 SVN_WC_TRANSLATE_TO_NF
2583 | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP
,
2586 /* A log command that copies the tmp-text-base and REtranslates
2587 it back to the working file.
2588 Now, since this is done during the execution of the log file, this
2589 retranslation is actually done according to the new props. */
2590 SVN_ERR(svn_wc__loggy_copy(&log_accum
, NULL
, adm_access
,
2591 svn_wc__copy_translate
,
2592 tmptext
, fb
->path
, FALSE
, pool
));
2595 if (*lock_state
== svn_wc_notify_lock_state_unlocked
)
2596 /* If a lock was removed and we didn't update the text contents, we
2597 might need to set the file read-only. */
2598 SVN_ERR(svn_wc__loggy_maybe_set_readonly(&log_accum
, adm_access
,
2602 /* Deal with installation of the new textbase, if appropriate. */
2603 if (fb
->new_text_base_path
)
2605 SVN_ERR(svn_wc__loggy_move(&log_accum
, NULL
,
2606 adm_access
, fb
->new_text_base_path
,
2607 fb
->text_base_path
, FALSE
, pool
));
2608 SVN_ERR(svn_wc__loggy_set_readonly(&log_accum
, adm_access
,
2609 fb
->text_base_path
, pool
));
2611 /* If the file is replaced don't write the checksum. Checksum is blank
2612 on replaced files. */
2615 tmp_entry
.checksum
= svn_md5_digest_to_cstring(fb
->digest
, pool
);
2616 flags
|= SVN_WC__ENTRY_MODIFY_CHECKSUM
;
2620 /* Do the entry modifications we've accumulated. */
2621 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum
, adm_access
,
2622 fb
->path
, &tmp_entry
, flags
, pool
));
2624 /* Log commands to handle text-timestamp and working-size */
2625 if (!is_locally_modified
)
2627 /* Adjust working copy file unless this file is an allowed
2629 if (fb
->last_changed_date
&& !fb
->existed
)
2630 SVN_ERR(svn_wc__loggy_set_timestamp(&log_accum
, adm_access
,
2631 fb
->path
, fb
->last_changed_date
,
2634 if (fb
->new_text_base_path
|| magic_props_changed
)
2636 /* Adjust entries file to match working file */
2637 SVN_ERR(svn_wc__loggy_set_entry_timestamp_from_wc
2638 (&log_accum
, adm_access
,
2639 fb
->path
, SVN_WC__ENTRY_ATTR_TEXT_TIME
, pool
));
2641 SVN_ERR(svn_wc__loggy_set_entry_working_size_from_wc
2642 (&log_accum
, adm_access
, fb
->path
, pool
));
2645 /* Clean up add-with-history temp file. */
2646 if (fb
->copied_text_base
)
2647 SVN_ERR(svn_wc__loggy_remove(&log_accum
, adm_access
,
2648 fb
->copied_text_base
,
2652 /* Set the returned content state. */
2654 /* This is kind of interesting. Even if no new text was
2655 installed (i.e., new_text_path was null), we could still
2656 report a pre-existing conflict state. Say a file, already
2657 in a state of textual conflict, receives prop mods during an
2658 update. Then we'll notify that it has text conflicts. This
2659 seems okay to me. I guess. I dunno. You? */
2661 if (merge_outcome
== svn_wc_merge_conflict
)
2662 *content_state
= svn_wc_notify_state_conflicted
;
2663 else if (fb
->new_text_base_path
)
2665 if (is_locally_modified
)
2666 *content_state
= svn_wc_notify_state_merged
;
2668 *content_state
= svn_wc_notify_state_changed
;
2671 *content_state
= svn_wc_notify_state_unchanged
;
2673 /* Now that we've built up *all* of the loggy commands for this
2674 file, add them to the directory's log accumulator in one fell
2676 svn_stringbuf_appendstr(fb
->dir_baton
->log_accum
, log_accum
);
2678 return SVN_NO_ERROR
;
2682 /* Mostly a wrapper around merge_file. */
2683 static svn_error_t
*
2684 close_file(void *file_baton
,
2685 const char *text_checksum
,
2688 struct file_baton
*fb
= file_baton
;
2689 struct edit_baton
*eb
= fb
->edit_baton
;
2690 svn_wc_notify_state_t content_state
, prop_state
;
2691 svn_wc_notify_lock_state_t lock_state
;
2695 SVN_ERR(maybe_bump_dir_info(eb
, fb
->bump_info
, pool
));
2696 return SVN_NO_ERROR
;
2699 /* Was this an add-with-history, with no apply_textdelta? */
2700 if (fb
->added_with_history
&& ! fb
->received_textdelta
)
2702 assert(! fb
->text_base_path
&& ! fb
->new_text_base_path
2703 && fb
->copied_text_base
);
2705 /* Set up the base paths like apply_textdelta does. */
2706 SVN_ERR(choose_base_paths(NULL
, NULL
, NULL
, fb
, pool
));
2708 /* Now simulate applying a trivial delta. */
2709 SVN_ERR(svn_io_copy_file(fb
->copied_text_base
,
2710 fb
->new_text_base_path
,
2712 SVN_ERR(svn_io_file_checksum(fb
->digest
,
2713 fb
->new_text_base_path
,
2717 /* window-handler assembles new pristine text in .svn/tmp/text-base/ */
2718 if (fb
->new_text_base_path
&& text_checksum
)
2720 const char *real_sum
= svn_md5_digest_to_cstring(fb
->digest
, pool
);
2722 if (real_sum
&& (strcmp(text_checksum
, real_sum
) != 0))
2723 return svn_error_createf
2724 (SVN_ERR_CHECKSUM_MISMATCH
, NULL
,
2725 _("Checksum mismatch for '%s'; expected: '%s', actual: '%s'"),
2726 svn_path_local_style(fb
->path
, pool
), text_checksum
, real_sum
);
2729 SVN_ERR(merge_file(&content_state
, &prop_state
, &lock_state
, fb
, pool
));
2731 /* We have one less referrer to the directory's bump information. */
2732 SVN_ERR(maybe_bump_dir_info(eb
, fb
->bump_info
, pool
));
2734 if (((content_state
!= svn_wc_notify_state_unchanged
) ||
2735 (prop_state
!= svn_wc_notify_state_unchanged
) ||
2736 (lock_state
!= svn_wc_notify_lock_state_unchanged
))
2739 svn_wc_notify_t
*notify
;
2740 svn_wc_notify_action_t action
= svn_wc_notify_update_update
;
2742 if (fb
->existed
|| fb
->add_existed
)
2744 if (content_state
!= svn_wc_notify_state_conflicted
)
2745 action
= svn_wc_notify_exists
;
2749 action
= svn_wc_notify_update_add
;
2752 notify
= svn_wc_create_notify(fb
->path
, action
, pool
);
2753 notify
->kind
= svn_node_file
;
2754 notify
->content_state
= content_state
;
2755 notify
->prop_state
= prop_state
;
2756 notify
->lock_state
= lock_state
;
2757 /* ### use merge_file() mimetype here */
2758 (*eb
->notify_func
)(eb
->notify_baton
, notify
, pool
);
2760 return SVN_NO_ERROR
;
2765 /* Beginning at DEST_DIR (and its associated entry DEST_ENTRY) within
2766 a working copy, search the working copy for an pre-existing
2767 versioned file which is exactly equal to COPYFROM_PATH@COPYFROM_REV.
2769 If the file isn't found, set *RETURN_PATH to NULL.
2771 If the file is found, return the absolute path to it in
2772 *RETURN_PATH, its entry in *RETURN_ENTRY, and a (read-only)
2773 access_t for its parent in *RETURN_ACCESS.
2775 static svn_error_t
*
2776 locate_copyfrom(const char *copyfrom_path
,
2777 svn_revnum_t copyfrom_rev
,
2778 const char *dest_dir
,
2779 const svn_wc_entry_t
*dest_entry
,
2780 const char **return_path
,
2781 const svn_wc_entry_t
**return_entry
,
2782 svn_wc_adm_access_t
**return_access
,
2785 const char *dest_fs_path
, *ancestor_fs_path
, *ancestor_url
, *file_url
;
2786 const char *copyfrom_parent
, *copyfrom_file
;
2787 const char *abs_dest_dir
, *extra_components
;
2788 const svn_wc_entry_t
*ancestor_entry
, *file_entry
;
2789 svn_wc_adm_access_t
*ancestor_access
;
2790 apr_size_t levels_up
;
2791 svn_stringbuf_t
*cwd
, *cwd_parent
;
2792 svn_node_kind_t kind
;
2794 apr_pool_t
*subpool
= svn_pool_create(pool
);
2796 /* Be pessimistic. This function is basically a series of tests
2797 that gives dozens of ways to fail our search, returning
2798 SVN_NO_ERROR in each case. If we make it all the way to the
2799 bottom, we have a real discovery to return. */
2800 *return_path
= NULL
;
2802 if ((! dest_entry
->repos
) || (! dest_entry
->url
))
2803 return svn_error_create(SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND
, NULL
,
2804 _("Destination directory of add-with-history "
2805 "is missing a URL"));
2807 svn_path_split(copyfrom_path
, ©from_parent
, ©from_file
, pool
);
2808 SVN_ERR(svn_path_get_absolute(&abs_dest_dir
, dest_dir
, pool
));
2810 /* Subtract the dest_dir's URL from the repository "root" URL to get
2811 the absolute FS path represented by dest_dir. */
2812 dest_fs_path
= svn_path_is_child(dest_entry
->repos
, dest_entry
->url
, pool
);
2815 if (strcmp(dest_entry
->repos
, dest_entry
->url
) == 0)
2816 dest_fs_path
= ""; /* the urls are identical; that's ok. */
2818 return svn_error_create(SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND
, NULL
,
2819 _("Destination URLs are broken"));
2821 dest_fs_path
= apr_pstrcat(pool
, "/", dest_fs_path
, NULL
);
2822 dest_fs_path
= svn_path_canonicalize(dest_fs_path
, pool
);
2824 /* Find nearest FS ancestor dir of current FS path and copyfrom_parent */
2825 ancestor_fs_path
= svn_path_get_longest_ancestor(dest_fs_path
,
2826 copyfrom_parent
, pool
);
2827 if (strlen(ancestor_fs_path
) == 0)
2828 return SVN_NO_ERROR
;
2830 /* Move 'up' the working copy to what ought to be the common ancestor dir. */
2831 levels_up
= svn_path_component_count(dest_fs_path
)
2832 - svn_path_component_count(ancestor_fs_path
);
2833 cwd
= svn_stringbuf_create(dest_dir
, pool
);
2834 svn_path_remove_components(cwd
, levels_up
);
2836 /* Open up this hypothetical common ancestor directory. */
2837 SVN_ERR(svn_io_check_path(cwd
->data
, &kind
, subpool
));
2838 if (kind
!= svn_node_dir
)
2839 return SVN_NO_ERROR
;
2840 err
= svn_wc_adm_open3(&ancestor_access
, NULL
, cwd
->data
,
2841 FALSE
, /* open read-only, please */
2842 0, /* open only this directory */
2843 NULL
, NULL
, subpool
);
2844 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
)
2846 /* The common ancestor directory isn't version-controlled. */
2847 svn_error_clear(err
);
2848 return SVN_NO_ERROR
;
2853 SVN_ERR(svn_wc_entry(&ancestor_entry
, cwd
->data
, ancestor_access
,
2856 /* If we got this far, we know that the ancestor dir exists, and
2857 that it's a working copy too. But is it from the same
2858 repository? And does it represent the URL we expect it to? */
2859 if (dest_entry
->uuid
&& ancestor_entry
->uuid
2860 && (strcmp(dest_entry
->uuid
, ancestor_entry
->uuid
) != 0))
2861 return SVN_NO_ERROR
;
2863 ancestor_url
= apr_pstrcat(subpool
,
2864 dest_entry
->repos
, ancestor_fs_path
, NULL
);
2865 if (strcmp(ancestor_url
, ancestor_entry
->url
) != 0)
2866 return SVN_NO_ERROR
;
2868 svn_pool_clear(subpool
); /* clean up adm_access junk. */
2870 /* Add the remaining components to cwd, then 'drill down' to where
2871 we hope the copyfrom_path file exists. */
2872 extra_components
= svn_path_is_child(ancestor_fs_path
,
2873 copyfrom_path
, pool
);
2874 svn_path_add_component(cwd
, extra_components
);
2875 cwd_parent
= svn_stringbuf_create(cwd
->data
, pool
);
2876 svn_path_remove_component(cwd_parent
);
2878 /* First: does the proposed file path even exist? */
2879 SVN_ERR(svn_io_check_path(cwd
->data
, &kind
, subpool
));
2880 if (kind
!= svn_node_file
)
2881 return SVN_NO_ERROR
;
2883 /* Next: is the file's parent-dir under version control? */
2884 err
= svn_wc_adm_open3(&ancestor_access
, NULL
, cwd_parent
->data
,
2885 FALSE
, /* open read-only, please */
2886 0, /* open only the parent dir */
2888 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
)
2890 svn_error_clear(err
);
2892 /* There's an unversioned directory (and file) in the exact
2893 correct place in the working copy. Chances are high that
2894 this file (or some parent) was deleted by 'svn update' --
2895 perhaps as part of a move operation -- and this file was left
2896 behind becouse it had local edits. If that's true, we may
2897 want this thing copied over to the new place.
2899 Unfortunately, we have no way of knowing if this file is the
2900 one we're looking for. Guessing incorrectly can be really
2901 hazardous, breaking the entire update.: we might find out
2902 when the server fails to apply a subsequent txdelta against
2903 it. Or, if the server doesn't try to do that now, what if a
2904 future update fails to apply? For now, the only safe thing
2905 to do is return no results. :-/
2907 return SVN_NO_ERROR
;
2912 /* The candidate file is under version control; but is it
2913 really the file we're looking for? <wave hand in circle> */
2914 SVN_ERR(svn_wc_entry(&file_entry
, cwd
->data
, ancestor_access
,
2917 /* Parent dir is versioned, but file is not. Be safe and
2918 return no results (see large discourse above.) */
2919 return SVN_NO_ERROR
;
2921 /* Is the repos UUID and file's URL what we expect it to be? */
2922 if (file_entry
->uuid
&& dest_entry
->uuid
2923 && (strcmp(file_entry
->uuid
, dest_entry
->uuid
) != 0))
2924 return SVN_NO_ERROR
;
2926 file_url
= apr_pstrcat(subpool
, file_entry
->repos
, copyfrom_path
, NULL
);
2927 if (strcmp(file_url
, file_entry
->url
) != 0)
2928 return SVN_NO_ERROR
;
2930 /* Do we actually have valid revisions for the file? (See Issue
2932 if (! (SVN_IS_VALID_REVNUM(file_entry
->cmt_rev
)
2933 && SVN_IS_VALID_REVNUM(file_entry
->revision
)))
2934 return SVN_NO_ERROR
;
2936 /* Do we have the the right *version* of the file? */
2937 if (! ((file_entry
->cmt_rev
<= copyfrom_rev
)
2938 && (copyfrom_rev
<= file_entry
->revision
)))
2939 return SVN_NO_ERROR
;
2941 /* Success! We found the exact file we wanted! */
2942 *return_path
= apr_pstrdup(pool
, cwd
->data
);
2943 *return_entry
= file_entry
;
2944 *return_access
= ancestor_access
;
2946 svn_pool_clear(subpool
);
2947 return SVN_NO_ERROR
;
2951 /* Given a set of properties PROPS_IN, find all regular properties
2952 and shallowly copy them into a new set (allocate the new set in
2953 POOL, but the set's members retain their original allocations). */
2955 copy_regular_props(apr_hash_t
*props_in
,
2958 apr_hash_t
*props_out
= apr_hash_make(pool
);
2959 apr_hash_index_t
*hi
;
2961 for (hi
= apr_hash_first(pool
, props_in
); hi
; hi
= apr_hash_next(hi
))
2965 const char *propname
;
2966 svn_string_t
*propval
;
2967 apr_hash_this(hi
, &key
, NULL
, &val
);
2971 if (svn_property_kind(NULL
, propname
) == svn_prop_regular_kind
)
2972 apr_hash_set(props_out
, propname
, APR_HASH_KEY_STRING
, propval
);
2978 /* Similar to add_file(), but not actually part of the editor vtable.
2980 Attempt to locate COPYFROM_PATH@COPYFROM_REV within the existing
2981 working copy. If found, copy it to PATH, and install it as a
2982 normal versioned file. (Local edits are copied as well.) If not
2983 found, then resort to fetching the file in a special RA request.
2985 After the file is fully installed, call the editor's open_file() on
2986 it, so that any subsequent apply_textdelta() commands coming from
2987 the server can further alter the file.
2989 static svn_error_t
*
2990 add_file_with_history(const char *path
,
2992 const char *copyfrom_path
,
2993 svn_revnum_t copyfrom_rev
,
2998 struct file_baton
*tfb
;
2999 struct dir_baton
*pb
= parent_baton
;
3000 struct edit_baton
*eb
= pb
->edit_baton
;
3001 svn_wc_adm_access_t
*adm_access
, *src_access
;
3002 const char *src_path
;
3003 const svn_wc_entry_t
*src_entry
;
3004 apr_hash_t
*base_props
, *working_props
;
3005 const svn_wc_entry_t
*path_entry
;
3008 /* The file_pool can stick around for a *long* time, so we want to
3009 use a subpool for any temporary allocations. */
3010 apr_pool_t
*subpool
= svn_pool_create(pool
);
3012 /* First, fake an add_file() call. Notice that we don't send any
3013 copyfrom args, lest we end up infinitely recursing. :-) */
3014 SVN_ERR(add_file(path
, parent_baton
, NULL
, SVN_INVALID_REVNUM
, pool
, &fb
));
3015 tfb
= (struct file_baton
*)fb
;
3016 tfb
->added_with_history
= TRUE
;
3018 /* Attempt to locate the copyfrom_path in the working copy first. */
3019 SVN_ERR(svn_wc_entry(&path_entry
, pb
->path
, eb
->adm_access
, FALSE
, subpool
));
3020 err
= locate_copyfrom(copyfrom_path
, copyfrom_rev
,
3021 pb
->path
, path_entry
,
3022 &src_path
, &src_entry
, &src_access
, subpool
);
3023 if (err
&& err
->apr_err
== SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND
)
3024 svn_error_clear(err
);
3028 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, pb
->edit_baton
->adm_access
,
3029 pb
->path
, subpool
));
3030 /* Make a unique file name for the copyfrom text-base. */
3031 SVN_ERR(svn_wc_create_tmp_file2(NULL
, &tfb
->copied_text_base
,
3032 svn_wc_adm_access_path(adm_access
),
3033 svn_io_file_del_none
,
3036 if (src_path
!= NULL
) /* Found a file to copy */
3038 /* Copy the existing file's text-base over to the (temporary)
3039 new text-base, where the file baton expects it to be. Get
3040 the text base and props from the usual place or from the
3041 revert place, depending on scheduling. */
3043 const char *src_text_base_path
;
3045 if (src_entry
->schedule
== svn_wc_schedule_replace
3046 && src_entry
->copyfrom_url
)
3048 src_text_base_path
= svn_wc__text_revert_path(src_path
,
3050 SVN_ERR(svn_wc__load_props(NULL
, NULL
, &base_props
,
3051 src_access
, src_path
, pool
));
3052 /* The old working props are lost, just like the old
3053 working file text is. Just use the base props. */
3054 working_props
= base_props
;
3058 src_text_base_path
= svn_wc__text_base_path(src_path
,
3060 SVN_ERR(svn_wc__load_props(&base_props
, &working_props
, NULL
,
3061 src_access
, src_path
, pool
));
3064 SVN_ERR(svn_io_copy_file(src_text_base_path
, tfb
->copied_text_base
,
3067 else /* Couldn't find a file to copy */
3069 apr_file_t
*textbase_file
;
3070 svn_stream_t
*textbase_stream
;
3072 /* Fall back to fetching it from the repository instead. */
3074 if (! eb
->fetch_func
)
3075 return svn_error_create(SVN_ERR_WC_INVALID_OP_ON_CWD
, NULL
,
3076 _("No fetch_func supplied to update_editor"));
3078 /* Fetch the repository file's text-base and base-props;
3079 svn_stream_close() automatically closes the text-base file for us. */
3080 SVN_ERR(svn_io_file_open(&textbase_file
, tfb
->copied_text_base
,
3081 (APR_WRITE
| APR_TRUNCATE
| APR_CREATE
),
3082 APR_OS_DEFAULT
, subpool
));
3083 textbase_stream
= svn_stream_from_aprfile2(textbase_file
, FALSE
, pool
);
3085 /* copyfrom_path is a absolute path, fetch_func requires a path relative
3086 to the root of the repository so skip the first '/'. */
3087 SVN_ERR(eb
->fetch_func(eb
->fetch_baton
, copyfrom_path
+ 1, copyfrom_rev
,
3089 NULL
, &base_props
, pool
));
3090 SVN_ERR(svn_stream_close(textbase_stream
));
3091 working_props
= base_props
;
3094 /* Loop over whatever props we have in memory, and add all
3095 regular props to hashes in the baton. Skip entry and wc
3096 properties, these are only valid for the original file. */
3097 tfb
->copied_base_props
= copy_regular_props(base_props
, pool
);
3098 tfb
->copied_working_props
= copy_regular_props(working_props
, pool
);
3100 if (src_path
!= NULL
)
3102 /* If we copied an existing file over, we need copy its working
3103 text and props too, to preserve any local mods. */
3104 svn_boolean_t text_changed
;
3106 SVN_ERR(svn_wc_text_modified_p(&text_changed
, src_path
, FALSE
,
3107 src_access
, subpool
));
3111 /* Make a unique file name for the copied_working_text. */
3112 SVN_ERR(svn_wc_create_tmp_file2(NULL
, &tfb
->copied_working_text
,
3113 svn_wc_adm_access_path(adm_access
),
3114 svn_io_file_del_none
,
3117 SVN_ERR(svn_io_copy_file(src_path
, tfb
->copied_working_text
, TRUE
,
3122 svn_pool_destroy(subpool
);
3125 return SVN_NO_ERROR
;
3129 static svn_error_t
*
3130 close_edit(void *edit_baton
,
3133 struct edit_baton
*eb
= edit_baton
;
3134 const char *target_path
= svn_path_join(eb
->anchor
, eb
->target
, pool
);
3137 /* If there is a target and that target is missing, then it
3138 apparently wasn't re-added by the update process, so we'll
3139 pretend that the editor deleted the entry. The helper function
3140 do_entry_deletion() will take care of the necessary steps. */
3141 if ((*eb
->target
) && (svn_wc__adm_missing(eb
->adm_access
, target_path
)))
3142 SVN_ERR(do_entry_deletion(eb
, eb
->anchor
, eb
->target
, &log_number
,
3145 /* The editor didn't even open the root; we have to take care of
3146 some cleanup stuffs. */
3147 if (! eb
->root_opened
)
3149 /* We need to "un-incomplete" the root directory. */
3150 SVN_ERR(complete_directory(eb
, eb
->anchor
, TRUE
, pool
));
3154 /* By definition, anybody "driving" this editor for update or switch
3155 purposes at a *minimum* must have called set_target_revision() at
3156 the outset, and close_edit() at the end -- even if it turned out
3157 that no changes ever had to be made, and open_root() was never
3158 called. That's fine. But regardless, when the edit is over,
3159 this editor needs to make sure that *all* paths have had their
3160 revisions bumped to the new target revision. */
3162 /* Make sure our update target now has the new working revision.
3163 Also, if this was an 'svn switch', then rewrite the target's
3164 url. All of this tweaking might happen recursively! Note
3165 that if eb->target is NULL, that's okay (albeit "sneaky",
3168 /* Extra check: if the update did nothing but make its target
3169 'deleted', then do *not* run cleanup on the target, as it
3170 will only remove the deleted entry! */
3171 if (! eb
->target_deleted
)
3172 SVN_ERR(svn_wc__do_update_cleanup(target_path
,
3174 eb
->requested_depth
,
3177 *(eb
->target_revision
),
3180 TRUE
, eb
->skipped_paths
,
3183 /* The edit is over, free its pool.
3184 ### No, this is wrong. Who says this editor/baton won't be used
3185 again? But the change is not merely to remove this call. We
3186 should also make eb->pool not be a subpool (see make_editor),
3187 and change callers of svn_client_{checkout,update,switch} to do
3188 better pool management. ### */
3189 svn_pool_destroy(eb
->pool
);
3191 return SVN_NO_ERROR
;
3196 /*** Returning editors. ***/
3198 /* Helper for the three public editor-supplying functions. */
3199 static svn_error_t
*
3200 make_editor(svn_revnum_t
*target_revision
,
3201 svn_wc_adm_access_t
*adm_access
,
3204 svn_boolean_t use_commit_times
,
3205 const char *switch_url
,
3207 svn_boolean_t depth_is_sticky
,
3208 svn_boolean_t allow_unver_obstructions
,
3209 svn_wc_notify_func2_t notify_func
,
3211 svn_cancel_func_t cancel_func
,
3213 svn_wc_conflict_resolver_func_t conflict_func
,
3214 void *conflict_baton
,
3215 svn_wc_get_file_t fetch_func
,
3217 const char *diff3_cmd
,
3218 apr_array_header_t
*preserved_exts
,
3219 const svn_delta_editor_t
**editor
,
3221 svn_wc_traversal_info_t
*traversal_info
,
3224 struct edit_baton
*eb
;
3226 apr_pool_t
*subpool
= svn_pool_create(pool
);
3227 svn_delta_editor_t
*tree_editor
= svn_delta_default_editor(subpool
);
3228 const svn_delta_editor_t
*inner_editor
;
3229 const svn_wc_entry_t
*entry
;
3231 /* An unknown depth can't be sticky. */
3232 if (depth
== svn_depth_unknown
)
3233 depth_is_sticky
= FALSE
;
3235 /* Get the anchor entry, so we can fetch the repository root. */
3236 SVN_ERR(svn_wc_entry(&entry
, anchor
, adm_access
, FALSE
, pool
));
3238 /* Disallow a switch operation to change the repository root of the target,
3239 if that is known. */
3240 if (switch_url
&& entry
&& entry
->repos
&&
3241 ! svn_path_is_ancestor(entry
->repos
, switch_url
))
3242 return svn_error_createf
3243 (SVN_ERR_WC_INVALID_SWITCH
, NULL
,
3245 "is not the same repository as\n"
3246 "'%s'"), switch_url
, entry
->repos
);
3248 /* Construct an edit baton. */
3249 eb
= apr_pcalloc(subpool
, sizeof(*eb
));
3251 eb
->use_commit_times
= use_commit_times
;
3252 eb
->target_revision
= target_revision
;
3253 eb
->switch_url
= switch_url
;
3254 eb
->repos
= entry
? entry
->repos
: NULL
;
3255 eb
->adm_access
= adm_access
;
3256 eb
->anchor
= anchor
;
3257 eb
->target
= target
;
3258 eb
->requested_depth
= depth
;
3259 eb
->depth_is_sticky
= depth_is_sticky
;
3260 eb
->notify_func
= notify_func
;
3261 eb
->notify_baton
= notify_baton
;
3262 eb
->traversal_info
= traversal_info
;
3263 eb
->diff3_cmd
= diff3_cmd
;
3264 eb
->cancel_func
= cancel_func
;
3265 eb
->cancel_baton
= cancel_baton
;
3266 eb
->conflict_func
= conflict_func
;
3267 eb
->conflict_baton
= conflict_baton
;
3268 eb
->fetch_func
= fetch_func
;
3269 eb
->fetch_baton
= fetch_baton
;
3270 eb
->allow_unver_obstructions
= allow_unver_obstructions
;
3271 eb
->skipped_paths
= apr_hash_make(subpool
);
3272 eb
->ext_patterns
= preserved_exts
;
3274 /* Construct an editor. */
3275 tree_editor
->set_target_revision
= set_target_revision
;
3276 tree_editor
->open_root
= open_root
;
3277 tree_editor
->delete_entry
= delete_entry
;
3278 tree_editor
->add_directory
= add_directory
;
3279 tree_editor
->open_directory
= open_directory
;
3280 tree_editor
->change_dir_prop
= change_dir_prop
;
3281 tree_editor
->close_directory
= close_directory
;
3282 tree_editor
->absent_directory
= absent_directory
;
3283 tree_editor
->add_file
= add_file
;
3284 tree_editor
->open_file
= open_file
;
3285 tree_editor
->apply_textdelta
= apply_textdelta
;
3286 tree_editor
->change_file_prop
= change_file_prop
;
3287 tree_editor
->close_file
= close_file
;
3288 tree_editor
->absent_file
= absent_file
;
3289 tree_editor
->close_edit
= close_edit
;
3291 /* Fiddle with the type system. */
3292 inner_editor
= tree_editor
;
3295 /* If our requested depth is sticky, we'll raise an error if asked
3296 to make our target more shallow, which is currently unsupported.
3298 Otherwise, if our requested depth is *not* sticky, then we need
3299 to limit the scope of our operation to the ambient depths present
3300 in the working copy already. If a depth was explicitly
3301 requested, libsvn_delta/depth_filter_editor.c will ensure that we
3302 never see editor calls that extend beyond the scope of the
3303 requested depth. But even what we do so might extend beyond the
3304 scope of our ambient depth. So we use another filtering editor
3305 to avoid modifying the ambient working copy depth when not asked
3306 to do so. (This can also be skipped if the server understands
3307 consider letting the depth RA capability percolate down to this
3309 if (depth_is_sticky
)
3311 const svn_wc_entry_t
*target_entry
;
3312 SVN_ERR(svn_wc_entry(&target_entry
, svn_path_join(anchor
, target
, pool
),
3313 adm_access
, FALSE
, pool
));
3314 if (target_entry
&& (target_entry
->depth
> depth
))
3315 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
3316 _("Shallowing of working copy depths is not "
3321 SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor
,
3331 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func
,
3339 return SVN_NO_ERROR
;
3344 svn_wc_get_update_editor3(svn_revnum_t
*target_revision
,
3345 svn_wc_adm_access_t
*anchor
,
3347 svn_boolean_t use_commit_times
,
3349 svn_boolean_t depth_is_sticky
,
3350 svn_boolean_t allow_unver_obstructions
,
3351 svn_wc_notify_func2_t notify_func
,
3353 svn_cancel_func_t cancel_func
,
3355 svn_wc_conflict_resolver_func_t conflict_func
,
3356 void *conflict_baton
,
3357 svn_wc_get_file_t fetch_func
,
3359 const char *diff3_cmd
,
3360 apr_array_header_t
*preserved_exts
,
3361 const svn_delta_editor_t
**editor
,
3363 svn_wc_traversal_info_t
*traversal_info
,
3366 return make_editor(target_revision
, anchor
, svn_wc_adm_access_path(anchor
),
3367 target
, use_commit_times
, NULL
, depth
, depth_is_sticky
,
3368 allow_unver_obstructions
, notify_func
, notify_baton
,
3369 cancel_func
, cancel_baton
, conflict_func
, conflict_baton
,
3370 fetch_func
, fetch_baton
,
3371 diff3_cmd
, preserved_exts
, editor
, edit_baton
,
3372 traversal_info
, pool
);
3377 svn_wc_get_update_editor2(svn_revnum_t
*target_revision
,
3378 svn_wc_adm_access_t
*anchor
,
3380 svn_boolean_t use_commit_times
,
3381 svn_boolean_t recurse
,
3382 svn_wc_notify_func2_t notify_func
,
3384 svn_cancel_func_t cancel_func
,
3386 const char *diff3_cmd
,
3387 const svn_delta_editor_t
**editor
,
3389 svn_wc_traversal_info_t
*traversal_info
,
3392 return svn_wc_get_update_editor3(target_revision
, anchor
, target
,
3394 SVN_DEPTH_INFINITY_OR_FILES(recurse
), FALSE
,
3395 FALSE
, notify_func
, notify_baton
,
3396 cancel_func
, cancel_baton
, NULL
, NULL
,
3398 diff3_cmd
, NULL
, editor
, edit_baton
,
3399 traversal_info
, pool
);
3403 svn_wc_get_update_editor(svn_revnum_t
*target_revision
,
3404 svn_wc_adm_access_t
*anchor
,
3406 svn_boolean_t use_commit_times
,
3407 svn_boolean_t recurse
,
3408 svn_wc_notify_func_t notify_func
,
3410 svn_cancel_func_t cancel_func
,
3412 const char *diff3_cmd
,
3413 const svn_delta_editor_t
**editor
,
3415 svn_wc_traversal_info_t
*traversal_info
,
3418 svn_wc__compat_notify_baton_t
*nb
= apr_palloc(pool
, sizeof(*nb
));
3419 nb
->func
= notify_func
;
3420 nb
->baton
= notify_baton
;
3422 return svn_wc_get_update_editor3(target_revision
, anchor
, target
,
3424 SVN_DEPTH_INFINITY_OR_FILES(recurse
), FALSE
,
3425 FALSE
, svn_wc__compat_call_notify_func
, nb
,
3426 cancel_func
, cancel_baton
, NULL
, NULL
,
3428 diff3_cmd
, NULL
, editor
, edit_baton
,
3429 traversal_info
, pool
);
3433 svn_wc_get_switch_editor3(svn_revnum_t
*target_revision
,
3434 svn_wc_adm_access_t
*anchor
,
3436 const char *switch_url
,
3437 svn_boolean_t use_commit_times
,
3439 svn_boolean_t depth_is_sticky
,
3440 svn_boolean_t allow_unver_obstructions
,
3441 svn_wc_notify_func2_t notify_func
,
3443 svn_cancel_func_t cancel_func
,
3445 svn_wc_conflict_resolver_func_t conflict_func
,
3446 void *conflict_baton
,
3447 const char *diff3_cmd
,
3448 apr_array_header_t
*preserved_exts
,
3449 const svn_delta_editor_t
**editor
,
3451 svn_wc_traversal_info_t
*traversal_info
,
3456 return make_editor(target_revision
, anchor
, svn_wc_adm_access_path(anchor
),
3457 target
, use_commit_times
, switch_url
,
3458 depth
, depth_is_sticky
, allow_unver_obstructions
,
3459 notify_func
, notify_baton
, cancel_func
, cancel_baton
,
3460 conflict_func
, conflict_baton
,
3461 NULL
, NULL
, /* TODO(sussman): add fetch callback here */
3462 diff3_cmd
, preserved_exts
,
3463 editor
, edit_baton
, traversal_info
, pool
);
3467 svn_wc_get_switch_editor2(svn_revnum_t
*target_revision
,
3468 svn_wc_adm_access_t
*anchor
,
3470 const char *switch_url
,
3471 svn_boolean_t use_commit_times
,
3472 svn_boolean_t recurse
,
3473 svn_wc_notify_func2_t notify_func
,
3475 svn_cancel_func_t cancel_func
,
3477 const char *diff3_cmd
,
3478 const svn_delta_editor_t
**editor
,
3480 svn_wc_traversal_info_t
*traversal_info
,
3485 return svn_wc_get_switch_editor3(target_revision
, anchor
, target
,
3486 switch_url
, use_commit_times
,
3487 SVN_DEPTH_INFINITY_OR_FILES(recurse
), FALSE
,
3488 FALSE
, notify_func
, notify_baton
,
3489 cancel_func
, cancel_baton
,
3490 NULL
, NULL
, diff3_cmd
,
3491 NULL
, editor
, edit_baton
, traversal_info
,
3496 svn_wc_get_switch_editor(svn_revnum_t
*target_revision
,
3497 svn_wc_adm_access_t
*anchor
,
3499 const char *switch_url
,
3500 svn_boolean_t use_commit_times
,
3501 svn_boolean_t recurse
,
3502 svn_wc_notify_func_t notify_func
,
3504 svn_cancel_func_t cancel_func
,
3506 const char *diff3_cmd
,
3507 const svn_delta_editor_t
**editor
,
3509 svn_wc_traversal_info_t
*traversal_info
,
3512 svn_wc__compat_notify_baton_t
*nb
= apr_palloc(pool
, sizeof(*nb
));
3513 nb
->func
= notify_func
;
3514 nb
->baton
= notify_baton
;
3516 return svn_wc_get_switch_editor3(target_revision
, anchor
, target
,
3517 switch_url
, use_commit_times
,
3518 SVN_DEPTH_INFINITY_OR_FILES(recurse
), FALSE
,
3519 FALSE
, svn_wc__compat_call_notify_func
, nb
,
3520 cancel_func
, cancel_baton
,
3521 NULL
, NULL
, diff3_cmd
,
3522 NULL
, editor
, edit_baton
, traversal_info
,
3527 svn_wc_traversal_info_t
*
3528 svn_wc_init_traversal_info(apr_pool_t
*pool
)
3530 svn_wc_traversal_info_t
*ti
= apr_palloc(pool
, sizeof(*ti
));
3533 ti
->externals_old
= apr_hash_make(pool
);
3534 ti
->externals_new
= apr_hash_make(pool
);
3535 ti
->depths
= apr_hash_make(pool
);
3542 svn_wc_edited_externals(apr_hash_t
**externals_old
,
3543 apr_hash_t
**externals_new
,
3544 svn_wc_traversal_info_t
*traversal_info
)
3546 *externals_old
= traversal_info
->externals_old
;
3547 *externals_new
= traversal_info
->externals_new
;
3552 svn_wc_traversed_depths(apr_hash_t
**depths
,
3553 svn_wc_traversal_info_t
*traversal_info
)
3555 *depths
= traversal_info
->depths
;
3561 Note the following actions, where X is the thing we wish to update,
3562 P is a directory whose repository URL is the parent of
3563 X's repository URL, N is directory whose repository URL is *not*
3564 the parent directory of X (including the case where N is not a
3565 versioned resource at all):
3567 1. `svn up .' from inside X.
3568 2. `svn up ...P/X' from anywhere.
3569 3. `svn up ...N/X' from anywhere.
3571 For the purposes of the discussion, in the '...N/X' situation, X is
3572 said to be a "working copy (WC) root" directory.
3574 Now consider the four cases for X's type (file/dir) in the working
3575 copy vs. the repository:
3577 A. dir in working copy, dir in repos.
3578 B. dir in working copy, file in repos.
3579 C. file in working copy, dir in repos.
3580 D. file in working copy, file in repos.
3582 Here are the results we expect for each combination of the above:
3584 1A. Successfully update X.
3585 1B. Error (you don't want to remove your current working
3586 directory out from underneath the application).
3587 1C. N/A (you can't be "inside X" if X is a file).
3588 1D. N/A (you can't be "inside X" if X is a file).
3590 2A. Successfully update X.
3591 2B. Successfully update X.
3592 2C. Successfully update X.
3593 2D. Successfully update X.
3595 3A. Successfully update X.
3596 3B. Error (you can't create a versioned file X inside a
3597 non-versioned directory).
3598 3C. N/A (you can't have a versioned file X in directory that is
3599 not its repository parent).
3600 3D. N/A (you can't have a versioned file X in directory that is
3601 not its repository parent).
3603 To summarize, case 2 always succeeds, and cases 1 and 3 always fail
3604 (or can't occur) *except* when the target is a dir that remains a
3605 dir after the update.
3607 ACCOMPLISHING THE GOAL
3609 Updates are accomplished by driving an editor, and an editor is
3610 "rooted" on a directory. So, in order to update a file, we need to
3611 break off the basename of the file, rooting the editor in that
3612 file's parent directory, and then updating only that file, not the
3613 other stuff in its parent directory.
3615 Secondly, we look at the case where we wish to update a directory.
3616 This is typically trivial. However, one problematic case, exists
3617 when we wish to update a directory that has been removed from the
3618 repository and replaced with a file of the same name. If we root
3619 our edit at the initial directory, there is no editor mechanism for
3620 deleting that directory and replacing it with a file (this would be
3621 like having an editor now anchored on a file, which is disallowed).
3623 All that remains is to have a function with the knowledge required
3624 to properly decide where to root our editor, and what to act upon
3625 with that now-rooted editor. Given a path to be updated, this
3626 function should conditionally split that path into an "anchor" and
3627 a "target", where the "anchor" is the directory at which the update
3628 editor is rooted (meaning, editor->open_root() is called with
3629 this directory in mind), and the "target" is the actual intended
3630 subject of the update.
3632 svn_wc_get_actual_target() is that function.
3634 So, what are the conditions?
3636 Case I: Any time X is '.' (implying it is a directory), we won't
3637 lop off a basename. So we'll root our editor at X, and update all
3640 Cases II & III: Any time we are trying to update some path ...N/X,
3641 we again will not lop off a basename. We can't root an editor at
3642 ...N with X as a target, either because ...N isn't a versioned
3643 resource at all (Case II) or because X is X is not a child of ...N
3644 in the repository (Case III). We root at X, and update X.
3646 Cases IV-???: We lop off a basename when we are updating a
3647 path ...P/X, rooting our editor at ...P and updating X, or when X
3648 is missing from disk.
3650 These conditions apply whether X is a file or directory.
3654 As it turns out, commits need to have a similar check in place,
3655 too, specifically for the case where a single directory is being
3656 committed (we have to anchor at that directory's parent in case the
3657 directory itself needs to be modified) */
3658 static svn_error_t
*
3659 check_wc_root(svn_boolean_t
*wc_root
,
3660 svn_node_kind_t
*kind
,
3662 svn_wc_adm_access_t
*adm_access
,
3665 const char *parent
, *base_name
;
3666 const svn_wc_entry_t
*p_entry
, *entry
;
3668 svn_wc_adm_access_t
*p_access
;
3670 /* Go ahead and initialize our return value to the most common
3671 (code-wise) values. */
3674 /* Get our ancestry. In the event that the path is unversioned,
3675 treat it as if it were a file so that the anchor will be the
3676 parent directory. */
3677 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, FALSE
, pool
));
3679 *kind
= entry
? entry
->kind
: svn_node_file
;
3681 /* If PATH is the current working directory, we have no choice but
3682 to consider it a WC root (we can't examine its parent at all) */
3683 if (svn_path_is_empty(path
))
3684 return SVN_NO_ERROR
;
3686 /* If this is the root folder (of a drive), it should be the WC
3688 if (svn_dirent_is_root(path
, strlen(path
)))
3689 return SVN_NO_ERROR
;
3691 /* If we cannot get an entry for PATH's parent, PATH is a WC root. */
3693 svn_path_split(path
, &parent
, &base_name
, pool
);
3694 SVN_ERR(svn_wc__adm_retrieve_internal(&p_access
, adm_access
, parent
,
3698 /* For historical reasons we cannot rely on the caller having opened
3699 the parent, so try it here. I'd like this bit to go away. */
3700 err
= svn_wc_adm_probe_open3(&p_access
, NULL
, parent
, FALSE
, 0,
3704 err
= svn_wc_entry(&p_entry
, parent
, p_access
, FALSE
, pool
);
3706 if (err
|| (! p_entry
))
3708 svn_error_clear(err
);
3709 return SVN_NO_ERROR
;
3712 /* If the parent directory has no url information, something is
3713 messed up. Bail with an error. */
3715 return svn_error_createf
3716 (SVN_ERR_ENTRY_MISSING_URL
, NULL
,
3717 _("'%s' has no ancestry information"),
3718 svn_path_local_style(parent
, pool
));
3720 /* If PATH's parent in the WC is not its parent in the repository,
3721 PATH is a WC root. */
3722 if (entry
&& entry
->url
3723 && (strcmp(svn_path_url_add_component(p_entry
->url
, base_name
, pool
),
3725 return SVN_NO_ERROR
;
3727 /* If PATH's parent in the repository is not its parent in the WC,
3728 PATH is a WC root. */
3729 SVN_ERR(svn_wc_entry(&p_entry
, path
, p_access
, FALSE
, pool
));
3731 return SVN_NO_ERROR
;
3733 /* If we have not determined that PATH is a WC root by now, it must
3736 return SVN_NO_ERROR
;
3741 svn_wc_is_wc_root(svn_boolean_t
*wc_root
,
3743 svn_wc_adm_access_t
*adm_access
,
3746 return check_wc_root(wc_root
, NULL
, path
, adm_access
, pool
);
3751 svn_wc_get_actual_target(const char *path
,
3752 const char **anchor
,
3753 const char **target
,
3756 svn_wc_adm_access_t
*adm_access
;
3757 svn_boolean_t is_wc_root
;
3758 svn_node_kind_t kind
;
3760 SVN_ERR(svn_wc_adm_probe_open3(&adm_access
, NULL
, path
, FALSE
, 0,
3762 SVN_ERR(check_wc_root(&is_wc_root
, &kind
, path
, adm_access
, pool
));
3763 SVN_ERR(svn_wc_adm_close(adm_access
));
3765 /* If PATH is not a WC root, or if it is a file, lop off a basename. */
3766 if ((! is_wc_root
) || (kind
== svn_node_file
))
3768 svn_path_split(path
, anchor
, target
, pool
);
3772 *anchor
= apr_pstrdup(pool
, path
);
3776 return SVN_NO_ERROR
;
3779 /* Write, to LOG_ACCUM, commands to install properties for an added DST_PATH.
3780 NEW_BASE_PROPS and NEW_PROPS are base and working properties, respectively.
3781 BASE_PROPS can contain entryprops and wcprops as well. ADM_ACCESS must
3782 be an access baton for DST_PATH.
3783 Use @a POOL for temporary allocations. */
3784 static svn_error_t
*
3785 install_added_props(svn_stringbuf_t
*log_accum
,
3786 svn_wc_adm_access_t
*adm_access
,
3787 const char *dst_path
,
3788 apr_hash_t
*new_base_props
,
3789 apr_hash_t
*new_props
,
3792 apr_array_header_t
*regular_props
= NULL
, *wc_props
= NULL
,
3793 *entry_props
= NULL
;
3795 /* Categorize the base properties. */
3797 apr_array_header_t
*prop_array
;
3800 /* Diff an empty prop has against the new base props gives us an array
3802 SVN_ERR(svn_prop_diffs(&prop_array
, new_base_props
,
3803 apr_hash_make(pool
), pool
));
3804 SVN_ERR(svn_categorize_props(prop_array
,
3805 &entry_props
, &wc_props
, ®ular_props
,
3808 /* Put regular props back into a hash table. */
3809 new_base_props
= apr_hash_make(pool
);
3810 for (i
= 0; i
< regular_props
->nelts
; ++i
)
3812 const svn_prop_t
*prop
= &APR_ARRAY_IDX(regular_props
, i
, svn_prop_t
);
3814 apr_hash_set(new_base_props
, prop
->name
, APR_HASH_KEY_STRING
,
3819 /* Install base and working props. */
3820 SVN_ERR(svn_wc__install_props(&log_accum
, adm_access
, dst_path
,
3822 new_props
? new_props
: new_base_props
,
3825 /* Install the entry props. */
3826 SVN_ERR(accumulate_entry_props(log_accum
, NULL
,
3827 adm_access
, dst_path
,
3828 entry_props
, pool
));
3830 /* This writes a whole bunch of log commands to install wcprops. */
3831 SVN_ERR(accumulate_wcprops(log_accum
, adm_access
,
3832 dst_path
, wc_props
, pool
));
3834 return SVN_NO_ERROR
;
3838 svn_wc_add_repos_file2(const char *dst_path
,
3839 svn_wc_adm_access_t
*adm_access
,
3840 const char *new_text_base_path
,
3841 const char *new_text_path
,
3842 apr_hash_t
*new_base_props
,
3843 apr_hash_t
*new_props
,
3844 const char *copyfrom_url
,
3845 svn_revnum_t copyfrom_rev
,
3848 const char *new_URL
;
3849 const char *adm_path
= svn_wc_adm_access_path(adm_access
);
3850 const char *tmp_text_base_path
=
3851 svn_wc__text_base_path(dst_path
, TRUE
, pool
);
3852 const char *text_base_path
=
3853 svn_wc__text_base_path(dst_path
, FALSE
, pool
);
3854 const svn_wc_entry_t
*ent
;
3855 const svn_wc_entry_t
*dst_entry
;
3856 svn_stringbuf_t
*log_accum
;
3857 const char *dir_name
, *base_name
;
3859 svn_path_split(dst_path
, &dir_name
, &base_name
, pool
);
3861 /* Fabricate the anticipated new URL of the target and check the
3862 copyfrom URL to be in the same repository. */
3864 SVN_ERR(svn_wc__entry_versioned(&ent
, dir_name
, adm_access
, FALSE
, pool
));
3866 new_URL
= svn_path_url_add_component(ent
->url
, base_name
, pool
);
3868 if (copyfrom_url
&& ent
->repos
&&
3869 ! svn_path_is_ancestor(ent
->repos
, copyfrom_url
))
3870 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
3871 _("Copyfrom-url '%s' has different repository"
3873 copyfrom_url
, ent
->repos
);
3876 /* Accumulate log commands in this buffer until we're ready to close
3878 log_accum
= svn_stringbuf_create("", pool
);
3880 /* If we're replacing the file then we need to save the destination files
3881 text base and prop base before replacing it. This allows us to revert
3882 the entire change. */
3883 SVN_ERR(svn_wc_entry(&dst_entry
, dst_path
, adm_access
, FALSE
, pool
));
3884 if (dst_entry
&& dst_entry
->schedule
== svn_wc_schedule_delete
)
3886 const char *dst_rtext
= svn_wc__text_revert_path(dst_path
, FALSE
,
3888 const char *dst_txtb
= svn_wc__text_base_path(dst_path
, FALSE
, pool
);
3890 SVN_ERR(svn_wc__loggy_move(&log_accum
, NULL
,
3891 adm_access
, dst_txtb
, dst_rtext
,
3893 SVN_ERR(svn_wc__loggy_revert_props_create(&log_accum
,
3894 dst_path
, adm_access
,
3898 /* Schedule this for addition first, before the entry exists.
3899 * Otherwise we'll get bounced out with an error about scheduling
3900 * an already-versioned item for addition.
3903 svn_wc_entry_t tmp_entry
;
3904 apr_uint64_t modify_flags
= SVN_WC__ENTRY_MODIFY_SCHEDULE
;
3906 tmp_entry
.schedule
= svn_wc_schedule_add
;
3910 assert(SVN_IS_VALID_REVNUM(copyfrom_rev
));
3912 tmp_entry
.copyfrom_url
= copyfrom_url
;
3913 tmp_entry
.copyfrom_rev
= copyfrom_rev
;
3914 tmp_entry
.copied
= TRUE
;
3916 modify_flags
|= SVN_WC__ENTRY_MODIFY_COPYFROM_URL
3917 | SVN_WC__ENTRY_MODIFY_COPYFROM_REV
3918 | SVN_WC__ENTRY_MODIFY_COPIED
;
3921 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum
, adm_access
,
3922 dst_path
, &tmp_entry
,
3923 modify_flags
, pool
));
3926 /* Set the new revision number and URL in the entry and clean up some other
3928 SVN_ERR(loggy_tweak_entry(log_accum
, adm_access
, dst_path
,
3929 dst_entry
? dst_entry
->revision
: ent
->revision
,
3932 SVN_ERR(install_added_props(log_accum
, adm_access
, dst_path
,
3933 new_base_props
, new_props
, pool
));
3935 /* Make sure the text base is where our log file can refer to it. */
3936 if (strcmp(tmp_text_base_path
, new_text_base_path
) != 0)
3937 SVN_ERR(svn_io_file_move(new_text_base_path
, tmp_text_base_path
,
3940 /* Install working file. */
3943 /* If the caller gave us a new working file, move it in place. */
3944 const char *tmp_text_path
;
3946 /* Move new text to temporary file in adm_access. */
3947 SVN_ERR(svn_wc_create_tmp_file2(NULL
, &tmp_text_path
, adm_path
,
3948 svn_io_file_del_none
, pool
));
3950 SVN_ERR(svn_io_file_move(new_text_path
, tmp_text_path
, pool
));
3952 /* Translate/rename new temporary text file to working text. */
3953 if (svn_wc__has_special_property(new_base_props
))
3955 SVN_ERR(svn_wc__loggy_copy(&log_accum
, NULL
, adm_access
,
3956 svn_wc__copy_translate_special_only
,
3958 dst_path
, FALSE
, pool
));
3959 /* Remove the copy-source, making it look like a move */
3960 SVN_ERR(svn_wc__loggy_remove(&log_accum
, adm_access
,
3961 tmp_text_path
, pool
));
3964 SVN_ERR(svn_wc__loggy_move(&log_accum
, NULL
, adm_access
,
3965 tmp_text_path
, dst_path
,
3968 SVN_ERR(svn_wc__loggy_maybe_set_readonly(&log_accum
, adm_access
,
3973 /* No working file provided by the caller, copy and translate the
3975 SVN_ERR(svn_wc__loggy_copy(&log_accum
, NULL
, adm_access
,
3976 svn_wc__copy_translate
,
3977 tmp_text_base_path
, dst_path
, FALSE
,
3979 SVN_ERR(svn_wc__loggy_set_entry_timestamp_from_wc
3980 (&log_accum
, adm_access
,
3981 dst_path
, SVN_WC__ENTRY_ATTR_TEXT_TIME
, pool
));
3982 SVN_ERR(svn_wc__loggy_set_entry_working_size_from_wc
3983 (&log_accum
, adm_access
, dst_path
, pool
));
3986 /* Install new text base. */
3988 unsigned char digest
[APR_MD5_DIGESTSIZE
];
3989 svn_wc_entry_t tmp_entry
;
3991 /* Write out log commands to set up the new text base and its
3993 SVN_ERR(svn_wc__loggy_move(&log_accum
, NULL
,
3994 adm_access
, tmp_text_base_path
,
3995 text_base_path
, FALSE
, pool
));
3996 SVN_ERR(svn_wc__loggy_set_readonly(&log_accum
, adm_access
,
3997 text_base_path
, pool
));
3999 SVN_ERR(svn_io_file_checksum(digest
, tmp_text_base_path
, pool
));
4001 tmp_entry
.checksum
= svn_md5_digest_to_cstring(digest
, pool
);
4002 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum
, adm_access
,
4003 dst_path
, &tmp_entry
,
4004 SVN_WC__ENTRY_MODIFY_CHECKSUM
,
4009 /* Write our accumulation of log entries into a log file */
4010 SVN_ERR(svn_wc__write_log(adm_access
, 0, log_accum
, pool
));
4012 SVN_ERR(svn_wc__run_log(adm_access
, NULL
, pool
));
4014 return SVN_NO_ERROR
;
4019 svn_wc_add_repos_file(const char *dst_path
,
4020 svn_wc_adm_access_t
*adm_access
,
4021 const char *new_text_path
,
4022 apr_hash_t
*new_props
,
4023 const char *copyfrom_url
,
4024 svn_revnum_t copyfrom_rev
,
4027 return svn_wc_add_repos_file2(dst_path
, adm_access
,
4028 new_text_path
, NULL
,
4030 copyfrom_url
, copyfrom_rev
,